feat: add verify-lifecycle-scripts gate for install-hook defence#5
Merged
TheCryptoDonkey merged 1 commit intomainfrom Apr 23, 2026
Merged
Conversation
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).
3 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Blocks the April 2026
@bitwarden/cli@2026.4.0/ TeamPCP–Checkmarx attack shape, where a compromised publish shipped apreinstallhook that ran a credential stealer on every consumernpm install.New gate
verify-lifecycle-scripts.shreadsscriptsfrom the consumer'spackage.jsonand comparespreinstall/install/postinstallagainst 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-livedNPM_TOKENto 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-policy—warn(default) |strict|offallowed-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
warnso adopting anvil does not break releases of packages that already ship a legitimatepostinstall.THREAT-MODEL.md
NPM_TOKEN, run scanners in a separate workflow,strict-action-pins: true).requireside 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 betweenverify-secretsandrecord-tarballTHREAT-MODEL.md,docs/pre-publish-gates.md— documentationTest plan
shellcheck -x steps/*.shcleanaction.ymlandrelease.ymlparse as valid YAMLGenerated by Claude Code