-
Notifications
You must be signed in to change notification settings - Fork 4.7k
fix(arborist): skip extraneous orphans in strict-allow-scripts gate #9683
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: latest
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -57,6 +57,11 @@ const collectUnreviewedScripts = async ({ | |
| // must not be flagged (npm/cli#9562). | ||
| continue | ||
| } | ||
| if (node.extraneous) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This also skips extraneous packages when |
||
| // Extraneous nodes are orphans pruned before reify runs any install script, so their scripts never execute. | ||
| // buildIdealTree drops top-level orphans but can retain one nested in a workspace's node_modules, so this gate must skip them (npm/cli#9680). | ||
| continue | ||
| } | ||
|
|
||
| const verdict = isScriptAllowed(node, resolvedPolicy) | ||
| if (verdict === true || verdict === false) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -29,6 +29,7 @@ const node = ({ | |
| isLink = false, | ||
| inBundle = false, | ||
| inert = false, | ||
| extraneous = false, | ||
| resolved, | ||
| } = {}) => ({ | ||
| name, | ||
|
|
@@ -41,6 +42,7 @@ const node = ({ | |
| isLink, | ||
| inBundle, | ||
| inert, | ||
| extraneous, | ||
| isRegistryDependency: true, | ||
| package: { name, version, scripts }, | ||
| }) | ||
|
|
@@ -92,6 +94,30 @@ t.test('collectUnreviewedScripts', async t => { | |
| t.strictSame(result, []) | ||
| }) | ||
|
|
||
| t.test('skips extraneous (orphan) registry nodes', async t => { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could we cover this at the command level? We need one test for the lockfile-only orphan from #9680, plus a regression test confirming that an extraneous package on disk is still listed and still fails strict
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree with the distinction. The unconditional collector skip is too broad: it fixes the lockfile-only ideal-tree orphan, but it also hides an extraneous package from the actual-tree I pushed 5e1c6da with a RED/GREEN preflight regression for the orphan, but that test does not cover the actual-tree behavior, so it does not close your full request. The fix needs to carry the prune/reify context into the strict install/ci preflight and only skip extraneous nodes there; |
||
| // An extraneous orphan is pruned before reify runs any install script, so it must not gate the install even when the policy neither allows nor denies it (npm/cli#9680). | ||
| const result = await collectUnreviewedScripts({ | ||
| tree: tree([ | ||
| node({ name: 'orphan', scripts: { install: 'x' }, extraneous: true }), | ||
| ]), | ||
| policy: null, | ||
| }) | ||
| t.strictSame(result, []) | ||
| }) | ||
|
|
||
| t.test('skips extraneous nodes even when policy denies by name', async t => { | ||
| // Same orphan, but with a name-only deny entry. | ||
| // An extraneous registry node usually has no resolved URL for the matcher to verify, so the deny misses and it falls through to "unreviewed". | ||
| // It still must not gate the install (npm/cli#9680). | ||
| const orphan = node({ name: 'esbuild', scripts: { install: 'x' }, extraneous: true }) | ||
| orphan.isRegistryDependency = false | ||
| const result = await collectUnreviewedScripts({ | ||
| tree: tree([orphan]), | ||
| policy: { esbuild: false }, | ||
| }) | ||
| t.strictSame(result, []) | ||
| }) | ||
|
|
||
| t.test('skips nodes with no install-relevant scripts', async t => { | ||
| const result = await collectUnreviewedScripts({ | ||
| tree: tree([node({ scripts: { test: 'jest' } })]), | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This fixes the reported install case, but this collector also walks the actual tree for
npm install-scripts lsand strictnpm rebuild.I tested an extraneous package already present in
node_modules. With this change,install-scripts lsreports nothing and strict rebuild succeeds instead of throwingESTRICTALLOWSCRIPTS. The lower-level gate still blocks the script, but strict mode silently passes.Could we limit this skip to reify preflights where pruning is enabled and the node will not be installed?