fix(arborist): match allowScripts keys for local paths (#9914)
## Summary
Fixes `allowScripts` matching for local file and tarball dependencies,
particularly on Windows.
Arborist stores a resolved Windows file source in this form:
```text
file:C:\project\vendor\pkg.tgz
```
However, `npm-package-arg` parses an equivalent policy key into:
`saveSpec: file:C:/project/vendor/pkg.tgz`
`fetchSpec: C:\project\vendor\pkg.tgz`
The existing matcher compared `node.resolved` only with `saveSpec` and
`fetchSpec`. Neither comparison could match because one uses different
separators and the other lacks the `file:` prefix.
## Changes
• Match local file and directory sources against
`file:${parsed.fetchSpec}`, which is the same representation produced by
Arborist's `consistentResolve()`.
• Resolve relative `allowScripts` file keys from the Arborist project
root rather than the process working directory.
• Reuse the Arborist file/directory matcher in `allow-scripts-writer`,
keeping runtime enforcement and policy updates consistent.
• Preserve the existing exact matching behavior for remote URLs.
• Preserve deny-wins behavior when an existing local-file policy entry
is `false`.
The implementation intentionally does not globally normalize path
separators. On POSIX systems, a backslash can be a literal filename
character, so replacing every `\` with `/` could cause two different
sources to share an approval.
The change also does not modify dependency resolution, lockfile
generation, fetching, linking, or `consistentResolve()`.
## Security considerations
Local file and tarball dependencies continue to require an exact source
identity.
This change does not:
• Match local packages by their self-reported name or version.
• Case-fold paths.
• Match by basename.
• Resolve paths through `realpath()`.
• Treat remote URLs as local file sources.
• Change registry or Git identity matching.
This preserves the manifest-confusion protections in `allowScripts`
while recognizing the exact Windows representation npm already
generates.
## Testing
Added regression coverage for:
• Backslash and forward-slash absolute Windows keys matching the same
local tarball.
• Relative keys resolving from the project root.
• `versionedKeyFor()` producing a key accepted by `isScriptAllowed()`.
• Different local paths remaining unmatched.
• Existing `false` file entries continuing to block approval.
• Windows UNC paths matching through the same `file:${fetchSpec}`
representation.
• POSIX filenames containing literal backslashes remaining distinct.
• Remote URL policies not matching `file: sources`.
• `npm install-scripts prune` retaining valid local-file entries.
The focused Arborist matcher, policy-writer, and prune tests pass
locally. Native Windows path and UNC behavior is covered by
platform-specific tests and will run in the repository's Windows CI
matrix.
Fixes #9900
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> M
martin committed
d6c612258c571c71a00f496c1f8980ed13b8a4d9
Parent: 6e40f73
Committed by GitHub <noreply@github.com>
on 8/25/2026, 8:24:11 PM