Keep publication checks independent of selector and fixture formatting

Accept a stable exact Node pin only within the governed major instead of
comparing its entire string with the major selector. Keep all other lines,
ranges and unresolved selectors rejected. This repairs the deterministic
required-check failure on the published-RC browser workflow without removing
its exact Node pin.

Give fake npm audit tests their own default timing controls and exercise
poisoned caller environments while preserving explicit watchdog tests.
No production audit verdict, dependency graph, benchmark threshold or release
gate changes.

Change-source: pulse-maintainer
This commit is contained in:
pulse-triage[bot]
2026-10-04 06:40:42 +01:00
parent de08129671
commit 9213a6d75a
4 changed files with 86 additions and 3 deletions
@@ -3992,7 +3992,12 @@ Every `actions/setup-node` release, governance, security, integration, and
native-agent job, the release-preflight worker, the developer container, and
the release frontend stages must stay on Node.js `24`; local integration setup
must reject a different major instead of treating newer non-LTS majors as
equivalent. Shipped and test Dockerfiles must pin every external base to a full
equivalent. Workflow selectors may use the governed major or an exact stable
`major.minor.patch` pin within it; ranges, previews, other majors and unresolved
selectors are not parity evidence. `TestNodeToolchainParity` checks the actual
workflows, and `TestNodeToolchainSelectors` checks both allowed forms and those
rejections without removing an exact runtime pin from a qualification job.
Shipped and test Dockerfiles must pin every external base to a full
manifest-list digest, and checked-in integration Compose images must do the
same. Node.js `24` is normally supported through `2028-04-30` and Alpine
`3.24` through `2028-06-01`; a weekly repository check must fail once either
@@ -5003,7 +5008,13 @@ advisory fails whenever the graph moves or the mode value is not exactly
`false`, that an inherited advisory warns and is still named, that an
unparseable or unrecognised report is never read as
clean, and that neither a hung attempt nor an exhausted budget can outlive
its bound. `frontend-modern/src/security/__tests__/dependencySecurity.test.ts`
its bound. Both fake-provider suites own their default timing controls rather
than inheriting a caller's shortened or exhausted budget. Python's hostile-env
controls exercise both exact single-request advisory verdicts and the real
shell fixture, including its explicit hung-attempt and total-deadline cases.
This fixture isolation does not change the production runner, retry budget or
audit verdict, and cannot establish the cause of an earlier extra invocation.
`frontend-modern/src/security/__tests__/dependencySecurity.test.ts`
pins the known safe floors for advisories remediated by commit `6ba85a185`,
including DOMPurify `GHSA-55q2-fjhq-7xh7`, brace-expansion
`GHSA-mh99-v99m-4gvg` and `GHSA-rgw5-rvv9-x895`, and nanoid
@@ -84,7 +84,7 @@ func TestNodeToolchainParity(t *testing.T) {
setupCount += strings.Count(content, "actions/setup-node@")
for _, match := range versionPattern.FindAllStringSubmatch(content, -1) {
versionCount++
if match[1] != nodeToolchainLine {
if !usesNodeToolchainLine(match[1], nodeToolchainLine) {
t.Fatalf("%s selects Node.js %s; all workflows must use governed line %s", path, match[1], nodeToolchainLine)
}
}
@@ -111,3 +111,27 @@ func TestNodeToolchainParity(t *testing.T) {
t.Fatalf("devcontainer must use Node.js %s from a digest-pinned feature", nodeToolchainLine)
}
}
// A reproducible job may pin an exact stable version within the governed
// major. Reject ranges, previews and unresolved selectors rather than treating
// a literal same-major pin as a different toolchain line.
func usesNodeToolchainLine(selector, line string) bool {
return regexp.MustCompile(`^` + regexp.QuoteMeta(line) + `(\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*))?$`).MatchString(selector)
}
func TestNodeToolchainSelectors(t *testing.T) {
for _, selector := range []string{"24", "24.0.0", "24.20.0", "24.123.456"} {
if !usesNodeToolchainLine(selector, "24") {
t.Errorf("governed stable selector %q rejected", selector)
}
}
for _, selector := range []string{
"23", "25", "23.20.0", "25.20.0", "240.20.0", "24.20", "24.x", "24.*",
"^24.20.0", ">=24", "latest", "node", "lts/*", "${{ matrix.node }}",
"24.20.0-rc.1", "24.20.0+build", "24.020.0", "24.20.00", "24.20.0 ", "",
} {
if usesNodeToolchainLine(selector, "24") {
t.Errorf("ungoverned or unresolved selector %q accepted", selector)
}
}
}
+7
View File
@@ -9,6 +9,13 @@ set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
SCRIPT="${ROOT_DIR}/scripts/npm-audit-retry.sh"
# Own the fake-provider baseline. Individual deadline controls below still
# override these values at the function call, but a CI caller's audit budget
# must not silently alter all the other canned-verdict tests.
NPM_AUDIT_ATTEMPTS=3
NPM_AUDIT_ATTEMPT_TIMEOUT=60
NPM_AUDIT_MAX_SECONDS=240
WORK_DIR="$(mktemp -d)"
trap 'rm -rf "${WORK_DIR}"' EXIT
+41
View File
@@ -9,6 +9,7 @@ import subprocess
import tempfile
import textwrap
import unittest
from unittest.mock import patch
import yaml
@@ -99,6 +100,8 @@ class NpmAuditRetryTest(unittest.TestCase):
"FAKE_NPM_MODE": mode,
"FAKE_NPM_REPORT": str(report_path),
"NPM_AUDIT_ATTEMPTS": "3",
"NPM_AUDIT_ATTEMPT_TIMEOUT": "60",
"NPM_AUDIT_MAX_SECONDS": "240",
"NPM_AUDIT_CMD": str(fake_npm),
"NPM_AUDIT_REQUIRE_RESULT": require,
"NPM_AUDIT_RETRY_DELAY": "0",
@@ -119,6 +122,44 @@ class NpmAuditRetryTest(unittest.TestCase):
)
return result, recorded_calls
def test_fake_audit_budget_is_not_inherited_from_the_caller(self) -> None:
# These are fixture controls, not a real advisory query. A caller's
# exhausted/short budget must not prevent the canned verdict from
# being read or change the exact one-request assertion.
with patch.dict(os.environ, {
"NPM_AUDIT_ATTEMPTS": "0",
"NPM_AUDIT_ATTEMPT_TIMEOUT": "0",
"NPM_AUDIT_MAX_SECONDS": "0",
"NPM_AUDIT_RETRY_DELAY": "10000",
"NPM_AUDIT_CMD": "/must-not-be-executed",
"NPM_AUDIT_REQUIRE_RESULT": "false",
}):
for require, status, annotation in [
("true", 1, "::error::"), ("false", 0, "::warning::")
]:
with self.subTest(require=require):
result, calls = self.run_check("vulnerability", "all", require=require)
self.assertEqual(result.returncode, status, result.stdout)
self.assertEqual(calls, ["audit --json"])
self.assertIn(annotation, result.stdout)
self.assertIn("vulnerabilities present", result.stdout)
self.assertNotIn("retrying", result.stdout)
def test_shell_fixture_owns_its_default_and_explicit_timing_controls(self) -> None:
env = os.environ.copy()
env.update(NPM_AUDIT_ATTEMPTS="0", NPM_AUDIT_ATTEMPT_TIMEOUT="0",
NPM_AUDIT_MAX_SECONDS="0", NPM_AUDIT_RETRY_DELAY="10000",
NPM_AUDIT_CMD="/must-not-be-executed")
result = subprocess.run(
["bash", str(ROOT / "scripts/tests/test-npm-audit-retry.sh")],
cwd=ROOT, env=env, text=True, capture_output=True, check=False,
timeout=90,
)
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
self.assertIn("all npm-audit-retry tests passed", result.stdout)
self.assertIn("a hung audit is stopped at the per-attempt limit", result.stdout)
self.assertIn("the wall-clock budget ends the retry sequence", result.stdout)
def test_passes_a_clean_production_audit_and_forwards_arguments(self) -> None:
result, calls = self.run_check(
"success", "production", "--package-lock-only"