chore(security): pin Actions and harden test lock (#273)

* chore(security): pin Actions and update aiohttp

* fix(security): generate universal test lock

* fix(security): harden relock safeguards
This commit is contained in:
malkuthro
2026-08-06 20:02:37 +02:00
committed by GitHub
parent 7b00a551d9
commit 2babbafb6b
14 changed files with 180 additions and 130 deletions
+11
View File
@@ -0,0 +1,11 @@
version: 2
updates:
- package-ecosystem: github-actions
directory: /
open-pull-requests-limit: 1
schedule:
interval: weekly
groups:
github-actions:
patterns:
- "*"
+14 -9
View File
@@ -17,13 +17,13 @@ jobs:
name: Lint & Smoke
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7.0.0
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/setup-python@v6.2.0
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.11"
- uses: actions/setup-node@v6.4.0
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: "22"
@@ -46,9 +46,9 @@ jobs:
name: Security Gate
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7.0.0
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/setup-python@v6.2.0
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.11"
@@ -61,13 +61,18 @@ jobs:
- name: Dependency audit
run: pip-audit -r constraints-test.txt
- name: Audit all platform dependency pins
run: |
sed -E 's/[[:space:]]*;.*$//' constraints-test.txt > /tmp/constraints-all-platforms.txt
pip-audit --no-deps -r /tmp/constraints-all-platforms.txt
preflight:
name: Pre-flight Release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7.0.0
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/setup-python@v6.2.0
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.11"
@@ -89,9 +94,9 @@ jobs:
matrix:
os: [ubuntu-latest, windows-latest]
steps:
- uses: actions/checkout@v7.0.0
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/setup-python@v6.2.0
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.11"
+3 -3
View File
@@ -41,16 +41,16 @@ jobs:
build-mode: none
steps:
- name: Checkout
uses: actions/checkout@v7.0.0
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
uses: github/codeql-action/init@c4dd10e44af883a891fe31ced449bcb4a6728b9b # v3.37.6
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
config-file: ./.github/codeql/codeql-config.yml
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
uses: github/codeql-action/analyze@c4dd10e44af883a891fe31ced449bcb4a6728b9b # v3.37.6
with:
category: "/language:${{ matrix.language }}"
+2 -2
View File
@@ -12,11 +12,11 @@ jobs:
if: ${{ github.repository_owner == 'malkuthro' }}
steps:
- name: Check out code
uses: actions/checkout@v7.0.0
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
submodules: true
- name: Publish Custom Node
uses: Comfy-Org/publish-node-action@v1
uses: Comfy-Org/publish-node-action@0eb6cd742945443bee7f79eeddd4f732780029a1 # v1 branch head
with:
## Add your own personal access token to your Github Repository secrets and reference it here.
personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }}
+2 -2
View File
@@ -14,10 +14,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out source
uses: actions/checkout@v7.0.0
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Set up Python
uses: actions/setup-python@v6.2.0
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.11"
+3 -3
View File
@@ -28,7 +28,7 @@ jobs:
issues: write # publish-node-action opens an issue on failure
steps:
- name: Check out the tag
uses: actions/checkout@v7.0.0
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
submodules: true
fetch-depth: 0 # full history so the tag-on-main ancestry check resolves
@@ -51,7 +51,7 @@ jobs:
exit 1
fi
- name: Publish Custom Node
uses: Comfy-Org/publish-node-action@v1
uses: Comfy-Org/publish-node-action@0eb6cd742945443bee7f79eeddd4f732780029a1 # v1 branch head
with:
personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }}
@@ -64,7 +64,7 @@ jobs:
contents: write # create the GitHub Release
steps:
- name: Check out the tag
uses: actions/checkout@v7.0.0
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Extract CHANGELOG section
run: |
set -euo pipefail
+6
View File
@@ -6,6 +6,12 @@ The format is inspired by Keep a Changelog and SemVer.
## [Unreleased]
### Security
- **Supply-chain hardening.** Raised the test-dependency floors to patched
`aiohttp` and Pillow releases, regenerated the audited universal Python 3.11
test lock with a forced upgrade, pinned every GitHub Action to an immutable
commit SHA, and enabled one grouped weekly Dependabot update for those pins.
### Added
- **Independent output type for published setups.** A `Koolook_PublishRouter`
is auto-detected as the setup's **output selector**: whichever writer branches
+6 -5
View File
@@ -28,7 +28,8 @@
resolve of `.[test]` + its full transitive closure), then **audit** it
with `pip-audit`; a known CVE fails the bootstrap. Idempotent (no-op if
`.venv` exists) — pass `-Force` / `--force` to recreate, `-Relock` /
`--relock` to re-resolve and rewrite the lock, `-NoAudit` / `--no-audit`
`--relock` to re-resolve and upgrade the lock (requires `uv`, which is
checked before an existing `.venv` is replaced), `-NoAudit` / `--no-audit`
to skip the audit (e.g. offline). Do not install into system Python or
user-site packages. If `.venv` cannot be used, stop and explain the
blocker before installing anywhere broader.
@@ -47,7 +48,7 @@
Running the bootstrap script above is **pre-approved** — run it
automatically whenever `.venv` is missing or `pytest` isn't importable,
without asking first. It is safe by construction: it installs only the
without asking first. Normal bootstrap is safe by construction: it installs only the
already-reviewed, pinned `constraints-test.txt` set and `pip-audit`-gates
the result, so it can never silently pull an unreviewed package or version.
This **supersedes the global `/warmup` skill's `MISSING_VENV` hard-stop**:
@@ -56,9 +57,9 @@ and continue — do not surface it as a blocker.
Two things still need your attention, and only these:
- **Bootstrap exits non-zero** (pip-audit flagged a CVE, or no Python was
found) — surface *that* as the blocker. A failed audit is the one signal
that genuinely warrants a human.
- **Bootstrap exits non-zero** (pip-audit flagged a CVE, no Python was found,
or a requested relock cannot find `uv`) — surface *that* as the blocker. A
failed audit is the one signal that genuinely warrants a human.
- **Dependency changes** — editing the `[test]` extras in `pyproject.toml`
or regenerating `constraints-test.txt` (`--relock`) — still go through a
reviewed PR diff. The lock file *is* the review surface; never relock
+21 -28
View File
@@ -1,28 +1,17 @@
# Locked test dependency set for ComfyUI-Koolook.
#
# Pinned resolve of the `[test]` extras in pyproject.toml plus their full
# transitive closure. The bootstrap scripts install with `-c
# constraints-test.txt`, so every fresh .venv is reproducible and
# pip-audit-verifiable.
#
# DO NOT hand-edit the version pins. To change the set: edit the `[test]`
# extras in pyproject.toml, then regenerate this file with
# bash scripts/bootstrap_test_env.sh --force --relock (POSIX)
# scripts\bootstrap_test_env.ps1 -Force -Relock (Windows)
# and commit the diff -- that diff is the dependency-change review surface.
#
aiohappyeyeballs==2.6.2
aiohttp==3.14.1
# This file was autogenerated by uv via the following command:
# uv pip compile pyproject.toml --extra test --universal --python-version 3.11 --no-annotate --no-emit-package pip --output-file constraints-test.txt
aiohappyeyeballs==2.7.1
aiohttp==3.14.3
aiosignal==1.4.0
attrs==26.1.0
boolean.py==5.0
CacheControl==0.14.4
certifi==2026.6.17
charset-normalizer==3.4.7
colorama==0.4.6
boolean-py==5.0
cachecontrol==0.14.4
certifi==2026.7.22
charset-normalizer==3.4.9
colorama==0.4.6 ; sys_platform == 'win32'
cyclonedx-python-lib==11.11.0
defusedxml==0.7.1
filelock==3.29.4
filelock==3.32.2
frozenlist==1.8.0
idna==3.18
iniconfig==2.3.0
@@ -32,22 +21,26 @@ mdurl==0.1.2
msgpack==1.2.1
multidict==6.7.1
packageurl-python==0.17.6
packaging==26.2
packaging==26.3
pillow==12.3.0
pip-api==0.0.34
pip_audit==2.10.1
pip-audit==2.10.1
pip-requirements-parser==32.0.1
platformdirs==4.10.0
platformdirs==4.11.0
pluggy==1.6.0
propcache==0.5.2
py-serializable==2.1.0
Pygments==2.20.0
pygments==2.20.0
pyparsing==3.3.2
pytest==9.1.0
pytest==9.1.1
requests==2.34.2
rich==15.0.0
sortedcontainers==2.4.0
tomli==2.4.1
tomli_w==1.2.0
tomli-w==1.2.0
typing-extensions==4.16.0 ; python_full_version < '3.13'
urllib3==2.7.0
yarl==1.24.2
yarl==1.24.5
# The following packages were excluded from the output:
# pip
+23 -4
View File
@@ -12,13 +12,33 @@ check `constraints-test.txt` with `pip-audit`.
`pip-audit` on the installed environment.
- CI audits the committed lock with `pip-audit -r constraints-test.txt` on each
pull request and on a weekly schedule, so newly disclosed CVEs in old pins are
caught even when nobody has touched the dependency files.
caught even when nobody has touched the dependency files. It also strips
environment markers into a temporary manifest and audits that manifest, so
Windows-only and Linux-only pins are each checked.
- Bandit remains a separate first-party-code scan. A green Bandit job does not
mean the dependency lock is clean.
## GitHub Actions
Every third-party `uses:` reference in `.github/workflows/` is pinned to an
immutable commit SHA, with its source release or branch reference left in a
comment for readability. When updating an Action, resolve that reference to a
commit with the GitHub API, review the change in the PR, then update both the
SHA and its comment together. Do not use mutable major-version tags such as
`@v3` or `@v7` in committed workflows.
Dependabot groups all GitHub Action updates into one weekly pull request. Python
test dependencies are updated through the deliberate universal relock below, so
the complete cross-platform lock diff stays one reviewable security unit.
## Regenerating the lock
Regenerate the lock only as an intentional dependency-change PR:
Regenerate the lock only as an intentional dependency-change PR. The bootstrap
uses `uv` to resolve a universal Python 3.11 lock, including the conditional
dependencies needed by both CI platforms. The scripts check that `uv` is
available before replacing an existing `.venv`, then use `--upgrade` so the
relock can move stale vulnerable pins. Install `uv` before relocking if it is
not already available.
```powershell
scripts\bootstrap_test_env.ps1 -Force -Relock
@@ -29,8 +49,7 @@ bash scripts/bootstrap_test_env.sh --force --relock
```
Review and commit the resulting `constraints-test.txt` diff. Do not hand-edit
individual pins unless you are immediately re-running the relock command to
prove the full resolved set.
individual pins.
## Responding to a CVE
+3 -4
View File
@@ -71,7 +71,6 @@ addopts = ["--import-mode=importlib"]
# ComfyUI custom node is not installed as a package in the workflow.
# Locally use ``.venv/bin/pip install -e '.[test]'`` from the repo root.
# ``aiohttp`` is a runtime dep of ``koolook_routes`` and must be importable
# even when only a pure helper is under test. The lower bound is pinned to
# the CVE-2026-54273..54280 fix line (>=3.14.1) so the declared range can't
# resolve a vulnerable aiohttp; the lock (constraints-test.txt) matches.
test = ["pytest>=8", "aiohttp>=3.14.1", "pip-audit>=2.7", "Pillow>=10"]
# even when only a pure helper is under test. Minimum versions exclude known
# vulnerabilities; constraints-test.txt pins the exact audited test set.
test = ["pytest>=8", "aiohttp>=3.14.3", "pip-audit>=2.7", "Pillow>=12.3.0"]
+24 -30
View File
@@ -19,14 +19,26 @@ $ErrorActionPreference = "Stop"
$Constraints = "constraints-test.txt"
if (Test-Path .venv) {
if (-not $Force) {
if ($Relock) {
Write-Host "-Relock requires -Force (the lock is rewritten from a fresh resolve); nothing was changed."
}
Write-Host ".venv already exists. Pass -Force to recreate."
exit 0
if ((Test-Path .venv) -and (-not $Force)) {
if ($Relock) {
Write-Host "-Relock requires -Force (the lock is rewritten from a fresh resolve); nothing was changed."
}
Write-Host ".venv already exists. Pass -Force to recreate."
exit 0
}
if ($Relock) {
if (-not (Get-Command uv -ErrorAction SilentlyContinue)) {
throw "-Relock requires uv to generate the universal Python 3.11 lock; nothing was changed."
}
Write-Host "Resolving upgraded universal Python 3.11 test lock ..."
uv pip compile pyproject.toml --extra test --universal --python-version 3.11 --upgrade --no-annotate --no-emit-package pip --output-file $Constraints
if ($LASTEXITCODE -ne 0) {
throw "uv could not generate $Constraints."
}
}
if (Test-Path .venv) {
Write-Host "Removing existing .venv ..."
Remove-Item -Recurse -Force .venv
}
@@ -37,31 +49,13 @@ python -m venv .venv
Write-Host "Upgrading pip + setuptools ..."
.\.venv\Scripts\python -m pip install --quiet --upgrade pip setuptools
if ((Test-Path $Constraints) -and (-not $Relock)) {
Write-Host "Installing project + test extras (locked via $Constraints) ..."
.\.venv\Scripts\python -m pip install --quiet -e ".[test]" -c $Constraints
} else {
Write-Host "Resolving + installing project + test extras ..."
.\.venv\Scripts\python -m pip install --quiet -e ".[test]"
Write-Host "Writing locked set to $Constraints ..."
@'
# Locked test dependency set for ComfyUI-Koolook.
#
# Pinned resolve of the `[test]` extras in pyproject.toml plus their full
# transitive closure. The bootstrap scripts install with `-c
# constraints-test.txt`, so every fresh .venv is reproducible and
# pip-audit-verifiable.
#
# DO NOT hand-edit the version pins. To change the set: edit the `[test]`
# extras in pyproject.toml, then regenerate this file with
# bash scripts/bootstrap_test_env.sh --force --relock (POSIX)
# scripts\bootstrap_test_env.ps1 -Force -Relock (Windows)
# and commit the diff -- that diff is the dependency-change review surface.
#
'@ | Out-File -Encoding ascii $Constraints
.\.venv\Scripts\python -m pip list --format=freeze --exclude pip --exclude setuptools --exclude wheel --exclude koolook | Out-File -Encoding ascii -Append $Constraints
if (-not (Test-Path $Constraints)) {
throw "$Constraints is missing. Run: scripts\\bootstrap_test_env.ps1 -Force -Relock"
}
Write-Host "Installing project + test extras (locked via $Constraints) ..."
.\.venv\Scripts\python -m pip install --quiet -e ".[test]" -c $Constraints
if (-not $NoAudit) {
Write-Host "Auditing installed set (pip-audit) ..."
.\.venv\Scripts\pip-audit --skip-editable
+24 -30
View File
@@ -41,14 +41,25 @@ if [ -z "$PYTHON_BIN" ]; then
fi
fi
if [ -d .venv ]; then
if [ -z "$FORCE" ]; then
if [ -n "$RELOCK" ]; then
echo "--relock requires --force (the lock is rewritten from a fresh resolve); nothing was changed." >&2
fi
echo ".venv already exists. Pass --force to recreate."
exit 0
if [ -d .venv ] && [ -z "$FORCE" ]; then
if [ -n "$RELOCK" ]; then
echo "--relock requires --force (the lock is rewritten from a fresh resolve); nothing was changed." >&2
fi
echo ".venv already exists. Pass --force to recreate."
exit 0
fi
if [ -n "$RELOCK" ]; then
if ! command -v uv >/dev/null 2>&1; then
echo "--relock requires uv to generate the universal Python 3.11 lock; nothing was changed." >&2
exit 1
fi
echo "Resolving upgraded universal Python 3.11 test lock ..."
uv pip compile pyproject.toml --extra test --universal --python-version 3.11 \
--upgrade --no-annotate --no-emit-package pip --output-file "$CONSTRAINTS"
fi
if [ -d .venv ]; then
echo "Removing existing .venv ..."
rm -rf .venv
fi
@@ -59,31 +70,14 @@ echo "Creating .venv ..."
echo "Upgrading pip + setuptools ..."
.venv/bin/python -m pip install --quiet --upgrade pip setuptools
if [ -f "$CONSTRAINTS" ] && [ -z "$RELOCK" ]; then
echo "Installing project + test extras (locked via $CONSTRAINTS) ..."
.venv/bin/python -m pip install --quiet -e '.[test]' -c "$CONSTRAINTS"
else
echo "Resolving + installing project + test extras ..."
.venv/bin/python -m pip install --quiet -e '.[test]'
echo "Writing locked set to $CONSTRAINTS ..."
cat > "$CONSTRAINTS" <<'EOF'
# Locked test dependency set for ComfyUI-Koolook.
#
# Pinned resolve of the `[test]` extras in pyproject.toml plus their full
# transitive closure. The bootstrap scripts install with `-c
# constraints-test.txt`, so every fresh .venv is reproducible and
# pip-audit-verifiable.
#
# DO NOT hand-edit the version pins. To change the set: edit the `[test]`
# extras in pyproject.toml, then regenerate this file with
# bash scripts/bootstrap_test_env.sh --force --relock (POSIX)
# scripts\bootstrap_test_env.ps1 -Force -Relock (Windows)
# and commit the diff -- that diff is the dependency-change review surface.
#
EOF
.venv/bin/python -m pip list --format=freeze --exclude pip --exclude setuptools --exclude wheel --exclude koolook >> "$CONSTRAINTS"
if [ ! -f "$CONSTRAINTS" ]; then
echo "$CONSTRAINTS is missing. Run: bash scripts/bootstrap_test_env.sh --force --relock" >&2
exit 1
fi
echo "Installing project + test extras (locked via $CONSTRAINTS) ..."
.venv/bin/python -m pip install --quiet -e '.[test]' -c "$CONSTRAINTS"
if [ -n "$AUDIT" ]; then
echo "Auditing installed set (pip-audit) ..."
if .venv/bin/pip-audit --skip-editable; then
+38 -10
View File
@@ -35,7 +35,7 @@ def _pinned_names() -> dict[str, str]:
continue
assert "==" in line, f"constraint not pinned with '==': {line!r}"
name, version = line.split("==", 1)
pins[_canonical(name)] = version.strip()
pins[_canonical(name)] = version.split(";", 1)[0].strip()
return pins
@@ -66,12 +66,12 @@ def test_every_top_level_test_extra_is_pinned():
def test_no_duplicate_pins():
names: list[str] = []
pins: list[str] = []
for raw in CONSTRAINTS.read_text(encoding="utf-8").splitlines():
line = raw.strip()
if line and not line.startswith("#"):
names.append(_canonical(line.split("==", 1)[0]))
dupes = sorted({name for name in names if names.count(name) > 1})
pins.append(re.sub(r"\s+", " ", line))
dupes = sorted({pin for pin in pins if pins.count(pin) > 1})
assert not dupes, f"duplicate pins in constraints-test.txt: {dupes}"
@@ -84,15 +84,41 @@ def test_bootstrap_upgrades_setuptools_before_audit():
assert "--upgrade pip setuptools" in sh
def test_bootstrap_relock_does_not_inspect_editable_git_metadata():
"""Relock should work from cross-drive git worktrees on Windows."""
def test_bootstrap_relock_checks_uv_before_replacing_venv():
"""A missing relock prerequisite must not discard a usable test env."""
ps1 = (REPO_ROOT / "scripts" / "bootstrap_test_env.ps1").read_text(encoding="utf-8")
sh = (REPO_ROOT / "scripts" / "bootstrap_test_env.sh").read_text(encoding="utf-8")
assert "pip list --format=freeze" in ps1
assert "pip list --format=freeze" in sh
assert "pip freeze --exclude-editable" not in ps1
assert "pip freeze --exclude-editable" not in sh
assert ps1.index("Get-Command uv") < ps1.index("Remove-Item -Recurse -Force .venv")
assert sh.index("command -v uv") < sh.index("rm -rf .venv")
assert "pip list --format=freeze" not in ps1
assert "pip list --format=freeze" not in sh
def test_bootstrap_relock_generates_a_universal_ci_lock():
"""Relocks include conditional dependencies for both CI platforms."""
ps1 = (REPO_ROOT / "scripts" / "bootstrap_test_env.ps1").read_text(encoding="utf-8")
sh = (REPO_ROOT / "scripts" / "bootstrap_test_env.sh").read_text(encoding="utf-8")
for script in (ps1, sh):
assert "uv pip compile pyproject.toml --extra test --universal" in script
assert "--python-version 3.11" in script
assert "--upgrade" in script
assert "--no-emit-package pip" in script
def test_constraints_cover_conditional_ci_dependencies():
pins = _pinned_names()
assert "typing-extensions" in pins
assert "colorama" in pins
def test_marker_versions_are_parsed_without_environment_markers():
pins = _pinned_names()
assert pins["typing-extensions"] == "4.16.0"
assert pins["colorama"] == "0.4.6"
def test_ci_audits_committed_lock_on_prs_and_schedule():
@@ -100,6 +126,8 @@ def test_ci_audits_committed_lock_on_prs_and_schedule():
assert "schedule:" in ci
assert "pip-audit -r constraints-test.txt" in ci
assert "pip-audit --no-deps -r /tmp/constraints-all-platforms.txt" in ci
assert "sed -E 's/[[:space:]]*;.*$//' constraints-test.txt" in ci
def test_ci_pytest_installs_from_committed_lock():