Skip to content

chore: harden .npmrc for supply-chain audit (AXE-3444)#236

Open
chikara1608 wants to merge 3 commits into
mainfrom
AXE-3444-npmrc-supply-chain-hardening
Open

chore: harden .npmrc for supply-chain audit (AXE-3444)#236
chikara1608 wants to merge 3 commits into
mainfrom
AXE-3444-npmrc-supply-chain-hardening

Conversation

@chikara1608

@chikara1608 chikara1608 commented Jun 3, 2026

Copy link
Copy Markdown
Collaborator

What & why

The weekly Enigma supply-chain audit (SC-12282) flagged browserstack/a11y-engine-axe-core with status misconfigured (AXE-3444): the committed root .npmrc existed but was missing every required hardening directive.

This adds the six flagged directives so the next weekly audit passes.

Changes

.npmrc — the audit fix

Added the six required directives:

Directive Purpose
ignore-scripts=true blocks malicious dependency lifecycle scripts
strict-ssl=true enforces TLS to the registry
save-exact=true pins exact versions on install
audit-level=high fail on high+ advisories
engine-strict=true refuse incompatible Node engines
legacy-peer-deps=false preserve npm 7+ peer-dep resolution

access=restricted is intentionally omitted — it applies to private repos only, and this repository is public. (The audit's missing-directives list did not include access.)

No tokens or secrets are added: this repo installs from the public npm registry and has no scoped/authenticated registry in .npmrc.

.circleci/config.yml — keep the build green under ignore-scripts

ignore-scripts=true suppresses dependency lifecycle scripts during npm ci, including esbuild's postinstall that downloads its native bundler binary. Without it, build_unixnpm run build (grunt → esbuild) would fail.

dependencies_unix is the only job that runs npm ci and it caches node_modules for every downstream job, so a single explicit rebuild there fixes the whole pipeline:

- run: npm rebuild esbuild --ignore-scripts=false --foreground-scripts

Two things deliberately not changed:

  • chromedriver — its npm install script is also skipped, but CI fetches the driver via browser-driver-manager (and the browser-tools orb), so it's unaffected.
  • patch-packagebuild_unix runs npm run prepare explicitly, and ignore-scripts only suppresses pre/post hooks around an explicit npm run, not the invoked script itself. So husky && npm run patch still applies patches/colorjs.io+0.4.3.patch.

doc/developer-guide.md — local-dev note

Documents the post-install steps now required locally (npm run prepare + npm rebuild esbuild --ignore-scripts=false) before building.

Reviewer note

engine-strict=true now makes npm ci refuse packages whose declared engines don't match the runner's Node. CI uses Node 22 (cimg/node:22.16); if a transitive dependency ever declares an incompatible range, install will fail loudly — that's the intended behavior, and any such dep is a separate fix.

Test plan

  • CI dependencies_unixbuild_unix is green (esbuild binary present after rebuild)
  • Browser test jobs green (chromedriver via browser-driver-manager)
  • Next weekly Enigma audit reports the repo as compliant

🤖 Generated with Claude Code

The weekly Enigma supply-chain audit (SC-12282) flagged this repo: the
committed .npmrc was missing the required hardening directives. Add
ignore-scripts, strict-ssl, save-exact, audit-level, engine-strict and
legacy-peer-deps. access=restricted is omitted intentionally — this is
a public repository, so the restricted-access directive does not apply.

ignore-scripts=true blocks dependency lifecycle scripts on install,
including esbuild's postinstall that downloads its native bundler
binary. Rebuild esbuild explicitly in the dependencies_unix job (the
only CI job that runs `npm ci`) so the cached node_modules carries the
binary to build_unix. chromedriver is unaffected — CI fetches it via
browser-driver-manager, not the npm install script. patch-package still
applies because build_unix runs `npm run prepare` explicitly, and
ignore-scripts only suppresses pre/post hooks around an explicit
`npm run`, not the invoked script itself.

Document the local-dev post-install steps in the developer guide.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@chikara1608

Copy link
Copy Markdown
Collaborator Author

Claude Code PR Review

PR: #236Head: e54b78bReviewers: stack:code-reviewer

Summary

Hardens .npmrc with six supply-chain directives (ignore-scripts=true, strict-ssl, save-exact, audit-level=high, engine-strict=true, legacy-peer-deps=false), adds an explicit npm rebuild esbuild step to the CircleCI dependencies_unix job to compensate for the skipped esbuild postinstall, documents the new install flow, and bumps package-lock.json (all resolved URLs remain on registry.npmjs.org). The intent is sound and the registry/lockfile changes are clean, but making ignore-scripts global breaks at least one CI path (the release pipeline) that this PR does not patch.

Review Table

Priority Category Check Status Notes
High Security No hardcoded secrets or credentials Pass .npmrc explicitly forbids tokens; no secrets added.
High Security Authentication/authorization checks present N/A No auth surface touched.
High Security Input validation and sanitization N/A No user input handling.
High Security No IDOR — resource ownership validated N/A Not applicable.
High Security No SQL injection (parameterized queries) N/A No DB access.
High Correctness Logic is correct, handles edge cases Fail Global ignore-scripts=true breaks the release pipeline: postbump runs npm ci (skips esbuild postinstall) then grunt build which needs esbuild. esbuild rebuild only added to dependencies_unix, not to release/nightly paths.
High Correctness Error handling is explicit, no swallowed exceptions Pass N/A to config change; failures surface loudly in CI.
High Correctness No race conditions or concurrency issues Pass None introduced.
Medium Testing New code has corresponding tests N/A Config/doc change; covered by CI jobs themselves.
Medium Testing Error paths and edge cases tested Fail Release (next_release/release) and nightly install paths not exercised by this PR's CI run; the broken paths only fire on tagged releases / nightly cron.
Medium Testing Existing tests still pass (no regressions) Pass dependencies_unix/build_unix path handled by the new rebuild step.
Medium Performance No N+1 queries or unbounded data fetching N/A Not applicable.
Medium Performance Long-running tasks use background jobs N/A Not applicable.
Medium Quality Follows existing codebase patterns Pass Directives are conventional npm config.
Medium Quality Changes are focused (single concern) Fail Diff also carries unrelated rule-JSON changes (aria-prohibited-attr.json, link-in-text-block.json) from prior merged PRs (#231/#225/#224), adding noise to a security-sensitive review.
Low Quality Meaningful names, no dead code Pass Directives are meaningful; no dead config (note: save-exact is genuinely new, not a duplicate).
Low Quality Comments explain why, not what Fail .npmrc comment leaks internal tool/ticket refs ("Enigma", "SC-12282") in a publicly-visible file.
Low Quality No unnecessary dependencies added Pass No new deps; lockfile is version bumps only.

Findings

  • File: package.json:68 (postbump) + .npmrc (ignore-scripts=true)

  • Severity: High

  • Reviewer: stack:code-reviewer

  • Issue: The next_release/release CI jobs run npm run next-release → standard-version, whose postbump hook is npm ci && npm run sri-update. The inner npm ci now runs under the global ignore-scripts=true, so esbuild's postinstall (which fetches its native binary) is skipped and the rebuilt binary from dependencies_unix is wiped. sri-update then runs grunt build, whose esbuild task (build/tasks/esbuild.js) requires that binary → the release pipeline fails. The PR only adds npm rebuild esbuild to dependencies_unix, not to this path.

  • Suggestion: Change postbump to npm ci && npm rebuild esbuild --ignore-scripts=false && npm run sri-update && git add doc/rule-descriptions.md (or add the rebuild inside sri-update).

  • File: .circleci/config.yml:210 and .circleci/config.yml:226 (nightly jobs)

  • Severity: High

  • Reviewer: stack:code-reviewer

  • Issue: test_nightly_act / test_nightly_aria_practices run npm install w3c/wcag-act-rules#main and npm install w3c/aria-practices#main. With ignore-scripts=true now global, any lifecycle scripts for these (or their re-resolved dep tree, including esbuild) are silently skipped, which can break nightly runs without a clear error. These cron-only jobs aren't exercised by the PR's CI.

  • Suggestion: Append --ignore-scripts=false to both nightly npm install calls, or add an esbuild rebuild step after them.

  • File: .npmrc (engine-strict=true) + package.json:6 ("engines": { "node": ">=4" })

  • Severity: Medium

  • Reviewer: stack:code-reviewer

  • Issue: engine-strict=true turns any dependency whose engines field doesn't satisfy the runtime into a hard npm ci failure instead of a warning. The main install path is tested by the lockfile bump, but this adds latent fragility on future Node bumps or new transitive deps with tighter ranges. The repo's own engines.node: ">=4" is satisfied by Node 22, so this directive provides little local benefit.

  • Suggestion: Confirm all transitive deps' engines are compatible with the supported Node range, or drop engine-strict=true.

  • File: .npmrc (comment line)

  • Severity: Low

  • Reviewer: stack:code-reviewer

  • Issue: The comment # ... (AXE-3444 / SC-12282) — linted weekly by Enigma. exposes internal tooling ("Enigma") and an internal ticket ("SC-12282") in a committed file on a fork of a public repo.

  • Suggestion: Replace with a generic note, e.g. # Supply-chain hardening directives (AXE-3444) — reviewed periodically.

  • File: doc/developer-guide.md:37

  • Severity: Low

  • Reviewer: stack:code-reviewer

  • Issue: The new step documents npm run prepare + npm rebuild esbuild, but doesn't call out that with ignore-scripts=true, husky's git hooks aren't auto-installed on a fresh npm install. Contributors won't get .husky/pre-commit without running npm run prepare explicitly — worth stating plainly.

  • Suggestion: Note that npm run prepare is required to install husky hooks because lifecycle scripts are disabled.

Note (not a blocker): The reviewer's "duplicate save-exact=true" finding was incorrect — the base .npmrc on main contained only the registry line, so save-exact is genuinely new. The "esbuild binary not portable to build_unix" and "patches never re-saved to downstream cache" concerns are either currently-safe (same CI base image) or pre-existing sequencing issues not introduced by this PR; flagged for awareness but not counted against the verdict.


Verdict: FAIL — sound hardening intent, but global ignore-scripts=true breaks the release pipeline (postbumpgrunt build needs esbuild) and risks silent nightly-job breakage; the esbuild rebuild is only applied to one of the affected CI paths.

The .npmrc supply-chain hardening sets ignore-scripts=true, which skips
esbuild's postinstall that fetches its native bundler binary. Every path
that installs deps and then builds (grunt -> esbuild) therefore broke:

- GitHub Actions `build` job (test.yml) failed with
  "esbuild: Failed to install correctly" at the esbuild:core grunt task.
- The standard-version `postbump` hook re-runs `npm ci` (wiping the binary)
  before `sri-update` -> `grunt build`, breaking the release / next_release
  pipelines (CircleCI release jobs and GitHub release.yml).
- The nightly `npm install w3c/...` steps re-resolve the whole dependency
  tree under ignore-scripts, skipping native postinstalls.

Fixes, keeping all six audit-required directives intact (access=restricted
is omitted because this is a public repo):

- test.yml: `npm rebuild esbuild --ignore-scripts=false` between ci and build
- package.json postbump: rebuild esbuild after its inner `npm ci`
- nightly jobs: `--ignore-scripts=false` on the trusted w3c installs
- .npmrc: drop internal tool/ticket reference from the comment

Verified: under ignore-scripts=true esbuild installs broken, and
`npm rebuild esbuild --ignore-scripts=false` runs install.js and restores
the working native binary.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@chikara1608

Copy link
Copy Markdown
Collaborator Author

Claude Code PR Review

PR: #236Head: 4d32488Reviewers: stack:code-reviewer

Summary

Hardens .npmrc with the six supply-chain directives mandated by the AXE-3444 / SC-12282 audit (ignore-scripts=true, strict-ssl, save-exact, audit-level=high, engine-strict, legacy-peer-deps=false; access=restricted correctly omitted for a public repo), and patches every install→build path so the now-global ignore-scripts=true no longer breaks esbuild. Re-review of HEAD 4d32488, which adds the follow-up fix commit on top of the original .npmrc change.

Review Table

Priority Category Check Status Notes
High Security No hardcoded secrets or credentials Pass .npmrc explicitly forbids tokens; none added.
High Security Authentication/authorization checks present N/A No auth surface.
High Security Input validation and sanitization N/A No user input.
High Security No IDOR — resource ownership validated N/A Not applicable.
High Security No SQL injection (parameterized queries) N/A No DB access.
High Correctness Logic is correct, handles edge cases Pass All install→build paths now rebuild esbuild: GH Actions test.yml build, CircleCI dependencies_unixbuild_unix (cache-baked binary), postbump hook (CircleCI release/next_release + GH release.yml), and both nightly jobs.
High Correctness Error handling is explicit, no swallowed exceptions Pass --foreground-scripts surfaces rebuild output; failures break CI loudly.
High Correctness No race conditions or concurrency issues Pass None introduced.
Medium Testing New code has corresponding tests N/A CI-config change; the CI jobs themselves are the test.
Medium Testing Error paths and edge cases tested Pass Previously-red GH build path now has the rebuild step; mechanism verified locally (broken esbuild under ignore-scripts=truenpm rebuild esbuild --ignore-scripts=false restores the binary).
Medium Testing Existing tests still pass (no regressions) Pass Build/release/nightly paths restored; directives kept intact.
Medium Performance No N+1 queries or unbounded data fetching N/A Not applicable.
Medium Performance Long-running tasks use background jobs N/A Not applicable.
Medium Quality Follows existing codebase patterns Pass Fix mirrors the PR author's existing npm rebuild esbuild step and comment style.
Medium Quality Changes are focused (single concern) Pass Supply-chain hardening + its required CI fixes. (Unrelated rule-JSON ride-alongs come from already-merged #231/#225/#224.)
Low Quality Meaningful names, no dead code Pass No dead config.
Low Quality Comments explain why, not what Pass Comments explain the ignore-scripts/esbuild rationale; prior internal tool/ticket leak removed.
Low Quality No unnecessary dependencies added Pass No new deps; lockfile is version bumps only, all on registry.npmjs.org.

Findings

No blocking issues. Non-blocking observations:

  • File: package.json:7 (engines.node)

  • Severity: Low

  • Reviewer: stack:code-reviewer

  • Issue: engine-strict=true (audit-mandated) also enforces every installed package's engines. The root floor "node": ">=4" is vacuously wide and doesn't document the real Node 22 floor; a future transitive dep that excludes Node 22 would hard-fail npm ci. Not a current breakage (CI npm ci passes today).

  • Suggestion: Optionally tighten root engines.node to ">=22" to match .nvmrc/CI image and make the declaration meaningful.

  • File: .circleci/config.yml nightly jobs (unix_nightly_boxcimg/node:lts-browsers)

  • Severity: Low

  • Reviewer: stack:code-reviewer

  • Issue: Floating lts Node tag combined with engine-strict=true means a future LTS bump could trip a transitive engines mismatch in the w3c installs. Pre-existing architectural risk, only amplified — not introduced by this PR.

  • Suggestion: Consider pinning the nightly Node image; no action required for this PR.

  • File: doc/developer-guide.md (new step)

  • Severity: Low (cosmetic)

  • Reviewer: stack:code-reviewer

  • Issue: Doc lists npm run prepare before npm rebuild esbuild. Harmless today (prepare is only husky + patch-package), purely cosmetic ordering.

  • Suggestion: None required.


Verdict: PASS — all install→build paths correctly patched; directives kept intact per the audit mandate; only non-blocking low-severity notes remain.

Comment thread .circleci/config.yml Outdated
Nightly jobs used `npm install w3c/...#main --ignore-scripts=false`, which
re-enabled lifecycle scripts for the whole re-resolved dependency tree and
the untrusted w3c/*#main installs — defeating the ignore-scripts=true
hardening. Those jobs only consume the prebuilt artifact (test:act/test:apg
are mocha), so esbuild isn't needed there; drop the flag.

Also remove the verbose AXE-3444 comment blocks from the CI configs per
review feedback. The targeted `npm rebuild esbuild --ignore-scripts=false`
steps (single trusted package) stay, matching the tech spec's sanctioned
approach.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants