Skip to content

Commit 1e6df77

Browse files
Sahilb315abhisek
andauthored
fix(lfp): don't flag npm aliased deps as lockfile poisoning (#713)
* fix(lfp): don't flag npm aliased deps as lockfile poisoning npm aliased dependencies ("foo": "npm:bar@1.2.3") install `bar` under node_modules/foo and record the real package name in the lockfile's `name` field. The analyzer was comparing the resolved URL against the path-derived name, so legitimate aliased entries — e.g. @openai/codex installed as @openai/codex-darwin-arm64 — were reported as poisoning. Parse `name` and use it for the URL convention check when present; fall back to the path-derived name otherwise. * docs(lfp): add lockfile poisoning doc with npm alias limitation Revert the insecure Name field fix and document npm aliased dependencies as a known limitation with a workaround using --lockfile-poisoning-trusted-urls. * update flag * fix: Disable on-demand malicious package scanning e2e tests --------- Co-authored-by: Abhisek Datta <abhisek.datta@gmail.com>
1 parent 787faa6 commit 1e6df77

2 files changed

Lines changed: 84 additions & 0 deletions

File tree

docs/lockfile-poisoning.md

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# Lockfile Poisoning Detection
2+
3+
## What is Lockfile Poisoning?
4+
5+
Lockfile poisoning is a supply chain attack where an attacker modifies a package manager's lockfile (e.g., `package-lock.json`) to redirect dependency resolution to malicious packages. Since lockfiles are auto-generated and often rubber-stamped in code review, subtle changes to resolved URLs or integrity hashes can go unnoticed.
6+
7+
For example, an attacker might change:
8+
9+
```json
10+
"node_modules/lodash": {
11+
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz"
12+
}
13+
```
14+
15+
to:
16+
17+
```json
18+
"node_modules/lodash": {
19+
"resolved": "https://registry.npmjs.org/evil-pkg/-/evil-pkg-1.0.0.tgz"
20+
}
21+
```
22+
23+
When `npm install` runs, `evil-pkg` is downloaded and placed at `node_modules/lodash/`. Application code importing `lodash` now executes attacker-controlled code.
24+
25+
## How vet Detects It
26+
27+
vet applies two checks to every package in a `package-lock.json` (lockfile version 2+):
28+
29+
### 1. Trusted Source Check
30+
31+
Verifies that the `resolved` URL points to a known, trusted registry host. By default, only `https://registry.npmjs.org` is trusted. Additional trusted registries (e.g., private registries) can be configured.
32+
33+
This catches attacks where the URL points to an attacker-controlled server.
34+
35+
### 2. Path Convention Check
36+
37+
Verifies that the package name derived from the `node_modules/...` key matches the package name in the resolved URL path. For example, `node_modules/lodash` should resolve to a URL containing `lodash/-/`.
38+
39+
This catches attacks where the URL points to a different package on the same trusted registry.
40+
41+
## Usage
42+
43+
```bash
44+
# Scan a project for lockfile poisoning
45+
vet scan -D /path/to/project
46+
47+
# With a private registry trusted
48+
vet scan -D /path/to/project --trusted-registry="https://registry.internal.example.com"
49+
```
50+
51+
## Known Limitations
52+
53+
### npm Aliased Dependencies
54+
55+
npm supports [dependency aliases](https://docs.npmjs.com/cli/v10/configuring-npm/package-json#dependencies) using the `npm:` prefix syntax:
56+
57+
```json
58+
{
59+
"dependencies": {
60+
"codex": "npm:@openai/codex@^0.77.0"
61+
}
62+
}
63+
```
64+
65+
This installs `@openai/codex` under `node_modules/codex`. The resulting lockfile has a path/URL mismatch that is indistinguishable from a poisoning attack, causing a false positive.
66+
67+
**Workaround:** Add the aliased package's registry URL to the trusted URLs list:
68+
69+
```bash
70+
vet scan -D /path/to/project --trusted-registry="https://registry.npmjs.org/@openai/codex"
71+
```
72+
73+
### Lockfile Version Support
74+
75+
Only npm lockfile version 2+ is supported. Older lockfile formats (version 1) use a different structure and are not analyzed.

test/scenarios/scenario-9-malware-analysis.sh

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,15 @@ if [ "$E2E_VET_INSIGHTS_V2" != "true" ]; then
1010
exit 0
1111
fi
1212

13+
# We have a separate E2E for on-demand malicious package scanning which requires
14+
# a SafeDep Pro subscription, hence we skip those tests here unless explicitly
15+
# enabled via VET_E2E_ON_DEMAND_MALICIOUS_PACKAGE_SCANNING.
16+
if [ -z "$VET_E2E_ON_DEMAND_MALICIOUS_PACKAGE_SCANNING" ]; then
17+
echo "Skipping scenario-9-malware-analysis.sh as VET_E2E_ON_DEMAND_MALICIOUS_PACKAGE_SCANNING is not set"
18+
19+
exit 0
20+
fi
21+
1322
# Not a malicious package
1423
$E2E_VET_SCAN_CMD scan --purl pkg:/npm/@clerk/nextjs@6.9.6 \
1524
--malware --malware-analysis-timeout 60s --fail-fast

0 commit comments

Comments
 (0)