Repository navigation
build(security): stop running dependency lifecycle scripts on install - #1317
Merged
Merged
Conversation
A bare `npm ci` runs preinstall/install/postinstall from every package in the tree -- 1490 installed, 1557 lockfile entries -- as arbitrary code on the runner. Eleven call sites across ten workflows do exactly that, and four of them sit in jobs holding a write-scoped GITHUB_TOKEN: architecture-submission (contents/pull-requests/issues: write), import-architectures (contents/ pull-requests: write), pr-queue-hygiene and create-milestones (issues/ pull-requests: write). Those scripts run before any validator, test or lint step, so a single compromised transitive dependency gets code execution next to a token that pushes branches and opens pull requests, and can rewrite the node_modules those later steps load. SHA-pinned actions, an integrity-complete lockfile and persist-credentials: false do not cover this: none of them stop a lifecycle script inside a package the lockfile resolves faithfully. A root .npmrc covers all eleven call sites, and local installs, in one file -- and needs no workflows permission to land. Nothing here depends on an install script: package.json declares no lifecycle hooks, and the only three dependencies that declare one are not load-bearing. @swc/core's postinstall only emits an advisory warning, with the native binding shipped prebuilt in @swc/core-linux-x64-gnu, which declares no install script; core-js prints a funding banner; fsevents is macOS-only and optional, and chokidar falls back without it. Verified at cc447d8: npm ci resolves all 1490 packages and npm run build reports "Generated static files". npm run test:unit gives an identical result with and without the file. Signed-off-by: sec-check <sec-check@hive.kubestellar.io>
Contributor
Author
|
Important Held for human review by the hive's ACMM level gate. This PR was opened by the "sec-check" agent while Hive policy required a human checkpoint for that agent. Non-outreach agents are held at ACMM L3–L5; the Hive will keep the |
This was referenced Oct 11, 2026
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.
Security Fix
Closes #1316
A bare
npm cirunspreinstall/install/postinstallfrom every package inthe tree — 1490 installed, 1557 lockfile entries — as arbitrary code on the
runner. Eleven call sites across ten workflows do exactly that, and four sit in
jobs holding a write-scoped
GITHUB_TOKEN:architecture-submission.ymlcontents: write,pull-requests: write,issues: writeimport-architectures.ymlcontents: write,pull-requests: writepr-queue-hygiene.ymlpull-requests: write,issues: writecreate-milestones.ymlissues: write,pull-requests: writeThose scripts run before any validator, test or lint step, so a single
compromised transitive dependency gets code execution next to a token that
pushes branches and opens pull requests, and can rewrite the
node_modulesthelater steps load. SHA-pinned actions, an integrity-complete lockfile and
persist-credentials: falsedo not cover this — none of them stop a lifecyclescript inside a package the lockfile resolves faithfully.
What this changes
.npmrc(new):ignore-scripts=true, with a comment recording why andwhat was checked. One file covers all eleven
npm cicall sites and localinstalls, and needs no
workflowspermission to land.CONTRIBUTING.md: one paragraph in Development setup so a contributorwho hits a dependency that wants an install script raises it in review rather
than quietly deleting the setting.
Scope claimed:
.npmrc, and the Development setup section ofCONTRIBUTING.md. No.github/workflows/**file is touched, so this isdisjoint from #1309 (
codeql.ymllanguage set) and #1310 (automation-workflowconcurrency:groups).Why nothing breaks
package.jsondeclares no lifecycle hooks, and only three dependencies declarean install script, none load-bearing:
@swc/core— postinstall emits an advisory warning only; the native bindingships prebuilt in
@swc/core-linux-x64-gnu, which declares no install script.core-js— postinstall prints a funding banner.fsevents— macOS-only andoptional; chokidar falls back without it.Verified at
cc447d8: with the file in place,npm ciresolves all 1490packages and
npm run buildreports[SUCCESS] Generated static files in "build".npm run test:unitgives a byte-identical pass/fail split with andwithout the file (72 files failing in the review sandbox for an unrelated
pre-existing reason — Node v26 against the declared
node: 22.x), so this movesno test either way.
Filed by sec-check agent (ACMM L4/L5 — hold-gated mode). Hold-gated: human review required.
— hive: agent=sec-check backend=copilot model=claude-opus-5 copilot=1.0.88