mirror of
https://github.com/rookiestar28/ComfyUI-OpenClaw.git
synced 2026-08-14 00:48:07 +00:00
fix(deps): harden frontend dependency validation
This commit is contained in:
@@ -210,8 +210,9 @@ jobs:
|
||||
python scripts/check_supply_chain_hardening.py
|
||||
- name: Frontend Audit (npm)
|
||||
run: |
|
||||
# Audit only production dependencies, ignore dev
|
||||
npm audit --production
|
||||
# Development tooling is part of the build/test trust boundary.
|
||||
npm ci
|
||||
npm audit --audit-level=high
|
||||
- name: Install backend deps
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
|
||||
@@ -9,7 +9,7 @@ All pull requests must pass the repository SOP gate before merge.
|
||||
| Secret detection | `pre-commit run detect-secrets --all-files` | Prevent secret leakage |
|
||||
| Pre-commit hooks | `pre-commit run --all-files --show-diff-on-failure` | Enforce formatting and static checks |
|
||||
| Production dependency boundary | `python scripts/verify_production_dependencies.py` | Parse tracked production imports without importing modules; block ownership, direction, cycle, and dynamic-import drift |
|
||||
| Frontend dependency audit | `npm audit --production` | Fail on production dependency vulnerabilities in the shipped Node dependency surface |
|
||||
| Frontend dependency audit | `npm ci` then `npm audit --audit-level=high` | Reconcile the lockfile and fail on high/critical vulnerabilities across production and development dependencies |
|
||||
| Backend dependency audit | `pip-audit -r requirements.txt` | Audit declared Python project dependencies without scanning unrelated CI runner/toolchain packages |
|
||||
| GitHub CodeQL analysis | `.github/workflows/codeql.yml` | Run repository-native static security analysis for Python, JavaScript/TypeScript, and GitHub Actions on push, pull request, and weekly schedule |
|
||||
| Coverage governance | `python scripts/verify_quality_governance.py` | Fail closed on coverage-policy, mutation-threshold, SOP-guidance, and survivor-allowlist drift |
|
||||
@@ -40,7 +40,8 @@ If a change intentionally modifies contract behavior:
|
||||
|
||||
- Coverage governance is part of the standard gate, not an optional reporting step.
|
||||
- Dependency-audit governance is part of CI parity:
|
||||
- Node audit should continue to target production dependencies only.
|
||||
- Node audit must cover production and development dependencies because build and test tooling is part of the acceptance trust boundary.
|
||||
- A separate production-only audit may be retained as a runtime-boundary readback, but it is not a substitute for the full blocking audit.
|
||||
- Python audit must stay scoped to `requirements.txt`; env-wide bare `pip-audit` is out of contract because it can fail on tool-only transient packages that are not part of the repo dependency surface.
|
||||
- GitHub Actions workflow files are part of the security boundary:
|
||||
- workflows using `GITHUB_TOKEN` must declare explicit least-privilege `permissions:` instead of relying on repository defaults
|
||||
|
||||
Generated
+10
-10
@@ -1207,9 +1207,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.12",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz",
|
||||
"integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==",
|
||||
"version": "3.3.16",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz",
|
||||
"integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -1314,9 +1314,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.5.15",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz",
|
||||
"integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==",
|
||||
"version": "8.5.25",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.25.tgz",
|
||||
"integrity": "sha512-DTPx3RWSSnWyzLxQnlH0rJP+EW5ekl16ZU4/psbIhA0e53kJfdgaN5vKM+xP7yJtXVu+nfdVFmlgFDEKAe4Pyw==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -1334,7 +1334,7 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.12",
|
||||
"nanoid": "^3.3.16",
|
||||
"picocolors": "^1.1.1",
|
||||
"source-map-js": "^1.2.1"
|
||||
},
|
||||
@@ -1803,9 +1803,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "8.19.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",
|
||||
"integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==",
|
||||
"version": "8.21.1",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.21.1.tgz",
|
||||
"integrity": "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
|
||||
@@ -359,6 +359,10 @@ fi
|
||||
echo "[pre-push] Node version: $(node -v)"
|
||||
echo "[pre-push] 0/10 supply-chain hardening check"
|
||||
"$VENV_PY" scripts/check_supply_chain_hardening.py
|
||||
echo "[pre-push] 0.25/10 frontend dependency install and audit"
|
||||
# IMPORTANT: never accept a warmed or manually changed node_modules tree.
|
||||
npm ci
|
||||
npm audit --audit-level=high
|
||||
echo "[pre-push] 0/7 R120 dependency preflight"
|
||||
"$VENV_PY" scripts/preflight_check.py --strict
|
||||
echo "[pre-push] 1/7 detect-secrets"
|
||||
|
||||
@@ -87,10 +87,9 @@ require_cmd node
|
||||
require_cmd npm
|
||||
|
||||
ensure_npm_deps() {
|
||||
if [ -f "$ROOT_DIR/node_modules/@playwright/test/package.json" ]; then
|
||||
return 0
|
||||
fi
|
||||
echo "[tests] Installing frontend dependencies via npm ci ..."
|
||||
# IMPORTANT: acceptance must reconcile the complete lockfile; file-presence
|
||||
# shortcuts can silently reuse an invalid or stale development dependency tree.
|
||||
echo "[tests] Reconciling frontend dependencies via npm ci ..."
|
||||
npm ci
|
||||
}
|
||||
|
||||
@@ -200,6 +199,9 @@ echo "[tests] 0/11 supply-chain hardening check"
|
||||
|
||||
ensure_npm_deps
|
||||
|
||||
echo "[tests] 0.25/11 frontend dependency audit"
|
||||
npm audit --audit-level=high
|
||||
|
||||
echo "[tests] 0.5/11 static analysis policy"
|
||||
"$VENV_PY" scripts/verify_static_analysis_policy.py
|
||||
|
||||
|
||||
@@ -58,12 +58,9 @@ Require-Cmd node
|
||||
Require-Cmd npm
|
||||
|
||||
function Ensure-NpmDeps {
|
||||
$playwrightPkg = Join-Path $root "node_modules\@playwright\test\package.json"
|
||||
if (Test-Path $playwrightPkg) {
|
||||
return
|
||||
}
|
||||
|
||||
Write-Host "[tests] Installing frontend dependencies via npm ci ..."
|
||||
# IMPORTANT: acceptance must reconcile the complete lockfile; file-presence
|
||||
# shortcuts can silently reuse an invalid or stale development dependency tree.
|
||||
Write-Host "[tests] Reconciling frontend dependencies via npm ci ..."
|
||||
Invoke-Checked "npm ci" { npm ci }
|
||||
}
|
||||
|
||||
@@ -276,6 +273,8 @@ Invoke-Checked "supply-chain hardening check" {
|
||||
& $venvPython scripts\check_supply_chain_hardening.py
|
||||
}
|
||||
Ensure-NpmDeps
|
||||
Write-Host "[tests] 0.25/11 frontend dependency audit"
|
||||
Invoke-Checked "npm audit" { npm audit --audit-level=high }
|
||||
|
||||
Write-Host "[tests] 0.5/11 static analysis policy"
|
||||
Invoke-Checked "static analysis policy" {
|
||||
|
||||
+8
-1
@@ -249,7 +249,10 @@ Then every `git push` will run:
|
||||
bash scripts/pre_push_checks.sh
|
||||
```
|
||||
|
||||
`scripts/pre_push_checks.sh` is the CI-parity guard and must include all 9 stages:
|
||||
`scripts/pre_push_checks.sh` is the CI-parity guard and must reconcile the exact
|
||||
frontend lockfile with `npm ci`, then block high-severity findings across production
|
||||
and development dependencies with `npm audit --audit-level=high`, before the 9
|
||||
behavioral and governance stages:
|
||||
|
||||
1) `detect-secrets`
|
||||
2) all `pre-commit` hooks
|
||||
@@ -393,6 +396,10 @@ node -v
|
||||
# node -v
|
||||
#
|
||||
# IMPORTANT: run `npm ci` with the same Node version you use for `npm test`.
|
||||
npm ci
|
||||
|
||||
# Development tooling is part of the acceptance trust boundary.
|
||||
npm audit --audit-level=high
|
||||
|
||||
# One-time browser install (recommended)
|
||||
npx playwright install chromium
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
import json
|
||||
import re
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
PACKAGE_JSON = ROOT / "package.json"
|
||||
PACKAGE_LOCK = ROOT / "package-lock.json"
|
||||
WINDOWS_GATE = ROOT / "scripts" / "run_full_tests_windows.ps1"
|
||||
LINUX_GATE = ROOT / "scripts" / "run_full_tests_linux.sh"
|
||||
PRE_PUSH_GATE = ROOT / "scripts" / "pre_push_checks.sh"
|
||||
CI_WORKFLOW = ROOT / ".github" / "workflows" / "ci.yml"
|
||||
TEST_SOP = ROOT / "tests" / "TEST_SOP.md"
|
||||
CI_POLICY = ROOT / "docs" / "release" / "ci_regression_policy.md"
|
||||
|
||||
|
||||
def _version_tuple(value: str) -> tuple[int, int, int]:
|
||||
match = re.fullmatch(r"(\d+)\.(\d+)\.(\d+)", value)
|
||||
if match is None:
|
||||
raise AssertionError(f"expected a stable semantic version, got {value!r}")
|
||||
return tuple(int(part) for part in match.groups())
|
||||
|
||||
|
||||
def _assert_ordered(test: unittest.TestCase, content: str, *needles: str) -> None:
|
||||
positions = [content.index(needle) for needle in needles]
|
||||
test.assertEqual(
|
||||
positions,
|
||||
sorted(positions),
|
||||
f"expected ordered commands: {' -> '.join(needles)}",
|
||||
)
|
||||
|
||||
|
||||
class TestFrontendDependencyHardening(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls) -> None:
|
||||
cls.package = json.loads(PACKAGE_JSON.read_text(encoding="utf-8"))
|
||||
cls.lock = json.loads(PACKAGE_LOCK.read_text(encoding="utf-8"))
|
||||
|
||||
def test_lock_resolves_patched_transitive_dependencies(self):
|
||||
packages = self.lock["packages"]
|
||||
self.assertGreaterEqual(
|
||||
_version_tuple(packages["node_modules/ws"]["version"]),
|
||||
(8, 21, 0),
|
||||
)
|
||||
self.assertGreaterEqual(
|
||||
_version_tuple(packages["node_modules/postcss"]["version"]),
|
||||
(8, 5, 18),
|
||||
)
|
||||
|
||||
def test_root_manifest_does_not_claim_transitive_packages(self):
|
||||
root_dependencies = {
|
||||
name
|
||||
for section in ("dependencies", "devDependencies", "optionalDependencies")
|
||||
for name in self.package.get(section, {})
|
||||
}
|
||||
self.assertTrue({"ws", "postcss", "nanoid"}.isdisjoint(root_dependencies))
|
||||
self.assertEqual(self.lock["lockfileVersion"], 3)
|
||||
|
||||
def test_windows_full_gate_reconciles_then_audits_frontend_tree(self):
|
||||
content = WINDOWS_GATE.read_text(encoding="utf-8")
|
||||
self.assertNotIn("$playwrightPkg", content)
|
||||
self.assertNotIn("Test-Path $playwrightPkg", content)
|
||||
self.assertEqual(content.count('Invoke-Checked "npm ci" { npm ci }'), 1)
|
||||
_assert_ordered(
|
||||
self,
|
||||
content,
|
||||
'Invoke-Checked "npm ci" { npm ci }',
|
||||
'Invoke-Checked "npm audit" { npm audit --audit-level=high }',
|
||||
'Invoke-Checked "frontend E2E" { npm test }',
|
||||
)
|
||||
|
||||
def test_linux_full_gate_reconciles_then_audits_frontend_tree(self):
|
||||
content = LINUX_GATE.read_text(encoding="utf-8")
|
||||
self.assertNotIn("node_modules/@playwright/test/package.json", content)
|
||||
self.assertEqual(content.splitlines().count(" npm ci"), 1)
|
||||
_assert_ordered(
|
||||
self,
|
||||
content,
|
||||
"npm ci",
|
||||
"npm audit --audit-level=high",
|
||||
"npm test",
|
||||
)
|
||||
|
||||
def test_pre_push_reconciles_then_audits_frontend_tree(self):
|
||||
content = PRE_PUSH_GATE.read_text(encoding="utf-8")
|
||||
self.assertEqual(content.count("npm ci"), 1)
|
||||
_assert_ordered(
|
||||
self,
|
||||
content,
|
||||
"npm ci",
|
||||
"npm audit --audit-level=high",
|
||||
"npm test",
|
||||
)
|
||||
|
||||
def test_ci_security_job_blocks_full_frontend_tree_findings(self):
|
||||
content = CI_WORKFLOW.read_text(encoding="utf-8")
|
||||
self.assertIn("npm ci", content)
|
||||
self.assertIn("npm audit --audit-level=high", content)
|
||||
self.assertNotIn("npm audit --production", content)
|
||||
_assert_ordered(
|
||||
self,
|
||||
content,
|
||||
"npm ci",
|
||||
"npm audit --audit-level=high",
|
||||
)
|
||||
|
||||
def test_public_acceptance_docs_require_full_frontend_audit(self):
|
||||
required_command = "npm audit --audit-level=high"
|
||||
self.assertIn(required_command, TEST_SOP.read_text(encoding="utf-8"))
|
||||
policy = CI_POLICY.read_text(encoding="utf-8")
|
||||
self.assertIn(required_command, policy)
|
||||
self.assertIn("development dependencies", policy.lower())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user