supply-chain-guard 5.20.2 → 5.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -131,7 +131,7 @@ Run the scanner as a [pre-commit](https://pre-commit.com) hook (Python-ecosystem
131
131
  ```yaml
132
132
  repos:
133
133
  - repo: https://github.com/homeofe/supply-chain-guard
134
- rev: v5.20.2
134
+ rev: v5.21.0
135
135
  hooks:
136
136
  - id: supply-chain-guard
137
137
  ```
package/dist/cli.js CHANGED
@@ -22,7 +22,7 @@ const program = new commander_1.Command();
22
22
  program
23
23
  .name("supply-chain-guard")
24
24
  .description("Open-source supply-chain security scanner. Detects GlassWorm and similar malware campaigns in npm packages, PyPI packages, code repos, VS Code extensions, and project dependencies.")
25
- .version("5.20.2");
25
+ .version("5.21.0");
26
26
  // ── scan command ────────────────────────────────────────────────────
27
27
  program
28
28
  .command("scan")
@@ -5,6 +5,22 @@
5
5
  * Checks package names against popular packages to detect mimicry.
6
6
  */
7
7
  import type { Finding } from "./types.js";
8
+ /**
9
+ * Resolve an npm alias spec to the package that actually gets installed.
10
+ *
11
+ * `"utils": "npm:chalk-tempalte@1.0.0"` installs chalk-tempalte and calls it
12
+ * `utils` in the tree. The KEY is a local label chosen by whoever wrote the
13
+ * manifest; the TARGET is the real registry package. Checking the key is
14
+ * therefore checking attacker-controlled text, which is how a known-malicious
15
+ * package used to scan completely clean: every consumer here read the key.
16
+ *
17
+ * Returns null for anything that is not an `npm:` alias, so callers can fall
18
+ * back to the literal name.
19
+ */
20
+ export declare function resolveNpmAlias(spec: string | undefined): {
21
+ name: string;
22
+ version?: string;
23
+ } | null;
8
24
  /**
9
25
  * Calculate Levenshtein distance between two strings.
10
26
  */
@@ -1 +1 @@
1
- {"version":3,"file":"dependency-risk-analyzer.d.ts","sourceRoot":"","sources":["../src/dependency-risk-analyzer.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAsC1C;;GAEG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAsBxD;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EACpC,YAAY,EAAE,MAAM,GACnB,OAAO,EAAE,CA0EX"}
1
+ {"version":3,"file":"dependency-risk-analyzer.d.ts","sourceRoot":"","sources":["../src/dependency-risk-analyzer.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AA8D1C;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,MAAM,GAAG,SAAS,GACvB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAsB3C;AAeD;;GAEG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAsBxD;AAmDD;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EACpC,YAAY,EAAE,MAAM,GACnB,OAAO,EAAE,CA8JX"}
@@ -6,6 +6,7 @@
6
6
  * Checks package names against popular packages to detect mimicry.
7
7
  */
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.resolveNpmAlias = resolveNpmAlias;
9
10
  exports.levenshtein = levenshtein;
10
11
  exports.analyzeDependencyRisks = analyzeDependencyRisks;
11
12
  /** Top popular npm packages (targets for typosquatting) */
@@ -29,6 +30,76 @@ const POPULAR_PACKAGES = [
29
30
  ];
30
31
  /** Packages that are known-safe even when close in name to popular packages */
31
32
  const POPULAR_PACKAGES_SET = new Set(POPULAR_PACKAGES);
33
+ /**
34
+ * Legitimate, widely-used packages that sit one edit from an entry in
35
+ * POPULAR_PACKAGES and must never be reported as typosquats.
36
+ *
37
+ * This is an ADDITIONAL exemption, not a replacement for POPULAR_PACKAGES_SET:
38
+ * entries here are NOT typosquat targets. The two lists are separate because a
39
+ * name can need exempting without being worth defending: putting a name in
40
+ * POPULAR_PACKAGES also makes every name one edit from it a candidate squat, so
41
+ * the target list has to stay a deliberate choice rather than a dumping ground
42
+ * for whatever tripped the rule this week.
43
+ *
44
+ * Scope note: this set gates exactly one rule (TYPOSQUAT_LEVENSHTEIN, at the
45
+ * single call site below). It is NOT a malware bypass - feed IOC matches, the
46
+ * known-bad-version blocklist and every scanner pattern run on separate paths
47
+ * that never consult it. Do not move this check earlier in install-guard's
48
+ * checkSpec, where it would start gating real malware verdicts.
49
+ *
50
+ * Every entry was verified against the npm registry on 2026-07-29 as a real,
51
+ * maintained package with an identifiable owner - the same evidentiary bar
52
+ * src/patterns.ts applies before blocking any bare name.
53
+ */
54
+ const TYPOSQUAT_ALLOWLIST = new Set([
55
+ "pathe", // unjs, universal path utils (also a transitive dep of this repo)
56
+ "color", // Qix-/color; NOT a squat of the sabotaged "colors"
57
+ "colord", // omgovich, color manipulation
58
+ "mysql", // the original mysqljs driver; "mysql2" is the successor, both real
59
+ "nuxt", // the Nuxt framework
60
+ "ulid", // ULID id generator; near "uuid" by coincidence
61
+ "preact", // 3kb React-compatible virtual DOM
62
+ "gaxios", // googleapis HTTP client
63
+ "enquirer", // interactive CLI prompts
64
+ "ttypescript", // TypeScript wrapper supporting custom transformers
65
+ ]);
66
+ /** Registry name shape, used to validate an alias target. */
67
+ const ALIAS_NAME_RE = /^(@[a-z0-9][a-z0-9-._~]*\/)?[a-z0-9-._~][a-z0-9-._~]*$/i;
68
+ /**
69
+ * Resolve an npm alias spec to the package that actually gets installed.
70
+ *
71
+ * `"utils": "npm:chalk-tempalte@1.0.0"` installs chalk-tempalte and calls it
72
+ * `utils` in the tree. The KEY is a local label chosen by whoever wrote the
73
+ * manifest; the TARGET is the real registry package. Checking the key is
74
+ * therefore checking attacker-controlled text, which is how a known-malicious
75
+ * package used to scan completely clean: every consumer here read the key.
76
+ *
77
+ * Returns null for anything that is not an `npm:` alias, so callers can fall
78
+ * back to the literal name.
79
+ */
80
+ function resolveNpmAlias(spec) {
81
+ if (typeof spec !== "string")
82
+ return null;
83
+ const trimmed = spec.trim();
84
+ if (!trimmed.startsWith("npm:"))
85
+ return null;
86
+ const target = trimmed.slice(4);
87
+ if (target.length === 0)
88
+ return null;
89
+ // Same split rule as a plain spec: the LAST "@" separates the version, and
90
+ // index 0 is a scope marker rather than a separator.
91
+ const at = target.lastIndexOf("@");
92
+ const name = at > 0 ? target.substring(0, at) : target;
93
+ const version = at > 0 ? target.substring(at + 1) : undefined;
94
+ if (!ALIAS_NAME_RE.test(name))
95
+ return null;
96
+ // A nested protocol ("npm:foo@git:...") is not a plain registry pin. Keep the
97
+ // resolved NAME, which is still checkable, and drop only the version.
98
+ if (version !== undefined && (version.length === 0 || version.includes(":"))) {
99
+ return { name };
100
+ }
101
+ return version === undefined ? { name } : { name, version };
102
+ }
32
103
  /** Patterns that suggest internal/private package names */
33
104
  const INTERNAL_PATTERNS = [
34
105
  /^@[^/]+\/internal-/,
@@ -60,13 +131,63 @@ function levenshtein(a, b) {
60
131
  }
61
132
  return dp[m][n];
62
133
  }
134
+ /**
135
+ * Optimal String Alignment distance (Damerau-Levenshtein restricted to adjacent
136
+ * transpositions), used ONLY by the typosquat heuristic below.
137
+ *
138
+ * Deliberately separate from the exported `levenshtein`, which is public API
139
+ * (re-exported from src/index.ts) and pinned to exact values by its own tests.
140
+ *
141
+ * Why the typosquat rule needs transposition awareness: the realistic squat is a
142
+ * swapped pair of adjacent characters, which plain Levenshtein scores as 2 edits
143
+ * (two substitutions) rather than 1. The curated squats this rule catches that
144
+ * are transpositions - "rimarf" -> rimraf, "yarsg" -> yargs, "lodahs" -> lodash,
145
+ * "axois" -> axios, "raect" -> react - would all be lost at a plain 1-edit
146
+ * ceiling. Scoring them as 1 is what allows the ceiling to drop from 2 to 1
147
+ * without losing them.
148
+ *
149
+ * Note this rule only ever sees squats of names in POPULAR_PACKAGES. Curated
150
+ * malicious names whose target is NOT in that list (cyrpto, hardhta, naniod,
151
+ * suport-color) are matched by exact name in patterns.ts and the feed, never
152
+ * here - so this heuristic's job is unpublished future squats, not the corpus
153
+ * already curated.
154
+ */
155
+ function osaDistance(a, b) {
156
+ const m = a.length;
157
+ const n = b.length;
158
+ const dp = Array.from({ length: m + 1 }, () => Array.from({ length: n + 1 }, () => 0));
159
+ for (let i = 0; i <= m; i++)
160
+ dp[i][0] = i;
161
+ for (let j = 0; j <= n; j++)
162
+ dp[0][j] = j;
163
+ for (let i = 1; i <= m; i++) {
164
+ for (let j = 1; j <= n; j++) {
165
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
166
+ dp[i][j] = Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + cost);
167
+ // Adjacent transposition counts as a single edit.
168
+ if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1]) {
169
+ dp[i][j] = Math.min(dp[i][j], dp[i - 2][j - 2] + cost);
170
+ }
171
+ }
172
+ }
173
+ return dp[m][n];
174
+ }
63
175
  /**
64
176
  * Analyze dependencies for typosquatting and confusion risks.
65
177
  */
66
178
  function analyzeDependencyRisks(dependencies, relativePath) {
67
179
  const findings = [];
68
- const depNames = Object.keys(dependencies);
69
- for (const name of depNames) {
180
+ // Every rule below judges the package that is actually INSTALLED. For an npm
181
+ // alias ("utils": "npm:lodahs@1.0.0") that is the target, not the manifest
182
+ // key, which is arbitrary text chosen by whoever wrote the file.
183
+ const entries = Object.keys(dependencies).map((key) => {
184
+ const alias = resolveNpmAlias(dependencies[key]);
185
+ return { key, name: alias ? alias.name : key, aliased: alias !== null };
186
+ });
187
+ /** Sorted "a b" keys, so one pair yields one finding rather than one per side. */
188
+ const reportedPairs = new Set();
189
+ for (const { key, name, aliased } of entries) {
190
+ const via = aliased ? ` (installed via the npm alias "${key}")` : "";
70
191
  // Skip scoped packages for Levenshtein (handled separately)
71
192
  if (name.startsWith("@")) {
72
193
  // Check internal name patterns on public registry
@@ -74,7 +195,7 @@ function analyzeDependencyRisks(dependencies, relativePath) {
74
195
  if (pattern.test(name)) {
75
196
  findings.push({
76
197
  rule: "DEP_INTERNAL_NAME_PUBLIC",
77
- description: `Dependency "${name}" looks like an internal package name. If this is on a public registry, it may be a dependency confusion attack.`,
198
+ description: `Dependency "${name}" looks like an internal package name${via}. If this is on a public registry, it may be a dependency confusion attack.`,
78
199
  severity: "critical",
79
200
  file: relativePath,
80
201
  confidence: 0.7,
@@ -86,53 +207,122 @@ function analyzeDependencyRisks(dependencies, relativePath) {
86
207
  }
87
208
  continue;
88
209
  }
89
- // Levenshtein check against popular packages.
90
- // Skip if the name is itself a known popular/safe package — prevents false positives
91
- // where two legitimate popular packages happen to be close in name (e.g. next/jest).
92
- if (!POPULAR_PACKAGES_SET.has(name) && name.length >= 4) {
210
+ // Typosquat check against popular packages.
211
+ //
212
+ // The ceiling is ONE transposition-aware edit. It used to be two plain
213
+ // Levenshtein edits, which flagged 321 measured real packages (acorn, preact,
214
+ // cypress, redux, viem, knex, globby, jose...) against 2 true positives that
215
+ // patterns.ts and the feed already catch by exact name - a ~1:160 signal ratio,
216
+ // and it got WORSE the more popular the package was (2.37% of packages above
217
+ // 10M weekly downloads). Two edits against a 4-character name is simply not
218
+ // evidence of anything. Transposition awareness (see osaDistance) is what makes
219
+ // the tightening free: every real squat in this repo's threat data is an
220
+ // adjacent swap, so all of them still score 1 and are still caught.
221
+ //
222
+ // No first-character predicate is used. It would cut a further ~24 false
223
+ // positives, but this repo curates "1odash" and "l0dash" in patterns.ts as
224
+ // canonical squats, and both change character zero - a leading-homoglyph rule
225
+ // would make this heuristic structurally unable to generalize them. It is also
226
+ // published source, so it would be a one-character documented bypass.
227
+ //
228
+ // Skip if the name is itself a known popular/safe package - prevents false
229
+ // positives where two legitimate popular packages are close in name (next/jest).
230
+ if (!POPULAR_PACKAGES_SET.has(name) &&
231
+ !TYPOSQUAT_ALLOWLIST.has(name) &&
232
+ name.length >= 4) {
233
+ // At a ceiling of 1 every hit is the same distance, so there is no closer
234
+ // match to search for and the first hit is reported. (Under the old 2-edit
235
+ // ceiling this loop could report a distance-2 target while a distance-1 one
236
+ // sat later in the array.) If the ceiling is ever raised, this must go back
237
+ // to tracking the minimum before reporting.
238
+ let best;
93
239
  for (const popular of POPULAR_PACKAGES) {
94
240
  if (name === popular)
95
241
  continue; // Exact match = legitimate
96
- // Skip very short popular packages (ws, pg, nx…) too many false positives
97
- if (popular.length < 4)
242
+ // Skip short popular targets. The floor is 5, not 4: measured over 29,687
243
+ // real published npm names, a floor of 4 produced 57 false positives and a
244
+ // floor of 5 produces 29, with the curated true-positive set unchanged. The
245
+ // four-letter targets are the damaging ones because almost any short name is
246
+ // one edit from them - "path" alone accounted for 11 (upath, mpath, xpath,
247
+ // paths, ...) and "jest" for 5 (test, nest, rest, ...). Floor 6 is too far:
248
+ // it loses yarsg, axois, raect and chlak.
249
+ if (popular.length < 5)
98
250
  continue;
99
- if (Math.abs(name.length - popular.length) > 2)
251
+ if (Math.abs(name.length - popular.length) > 1)
100
252
  continue; // Quick skip
101
- const dist = levenshtein(name, popular);
102
- if (dist > 0 && dist <= 2) {
103
- findings.push({
104
- rule: "TYPOSQUAT_LEVENSHTEIN",
105
- description: `Dependency "${name}" is ${dist} edit(s) away from popular package "${popular}". Likely a typosquat.`,
106
- severity: "high",
107
- file: relativePath,
108
- confidence: dist === 1 ? 0.85 : 0.65,
109
- category: "supply-chain",
110
- recommendation: `Did you mean "${popular}"? Typosquatting replaces popular packages with malicious copies.`,
111
- });
112
- break; // One match per dep is enough
253
+ const dist = osaDistance(name, popular);
254
+ if (dist > 0 && dist <= 1 && (!best || dist < best.dist)) {
255
+ best = { popular, dist };
256
+ break; // Distance 1 is the tightest possible hit; nothing can beat it.
113
257
  }
114
258
  }
115
- }
116
- // Check if name is similar to another direct dependency
117
- for (const otherName of depNames) {
118
- if (name === otherName)
119
- continue;
120
- if (name.startsWith("@") || otherName.startsWith("@"))
121
- continue;
122
- if (Math.abs(name.length - otherName.length) > 2)
123
- continue;
124
- const dist = levenshtein(name, otherName);
125
- if (dist > 0 && dist <= 1) {
259
+ if (best) {
126
260
  findings.push({
127
- rule: "TYPOSQUAT_SIMILAR_TO_DEP",
128
- description: `Dependencies "${name}" and "${otherName}" differ by only ${dist} character(s). One may be a typosquat of the other.`,
261
+ rule: "TYPOSQUAT_LEVENSHTEIN",
262
+ // The dependency name MUST stay the first quoted token: policy-engine.ts
263
+ // extracts it with /"([^"]+)"/ to honour user `allowlist.packages` rules.
264
+ description: `Dependency "${name}"${via} is ${best.dist} edit(s) away from popular package "${best.popular}". Likely a typosquat.`,
129
265
  severity: "high",
130
266
  file: relativePath,
131
- confidence: 0.7,
267
+ confidence: 0.85,
132
268
  category: "supply-chain",
133
- recommendation: `Review both "${name}" and "${otherName}". Only one should be in your dependencies.`,
269
+ recommendation: `Did you mean "${best.popular}"? Typosquatting replaces popular packages with malicious copies.`,
134
270
  });
135
- break;
271
+ }
272
+ }
273
+ // Check if name is similar to another direct dependency.
274
+ //
275
+ // Measured over 939 real manifests, this rule fired on 5.0% of them (8.1% of
276
+ // real repository roots) and every colliding pair was a legitimate co-install:
277
+ // preact+react, vue+vuex, path+pathe, color+colors, mysql+mysql2, uuid+ulid,
278
+ // eslint+tslint. Four guards below cut that to under 1% while INCREASING
279
+ // recall, because switching to osaDistance starts catching the transposition
280
+ // squats (lodahs+lodash, axois+axios, raect+react, yarsg+yargs) that the rule
281
+ // was silently missing next to their own target.
282
+ //
283
+ // The known-good test is ONE-SIDED on purpose. The outer loop visits both
284
+ // members of a pair, so testing only the reported side keeps the case this
285
+ // rule exists for: {expres, express} still reports, because the express-side
286
+ // pass is dropped while the expres-side pass survives. Testing otherName too
287
+ // would suppress exactly the squat-next-to-its-target shape (measured: it
288
+ // drops 11 of 14 true positives).
289
+ //
290
+ // Guards are scoped to this block rather than the outer loop so the two
291
+ // typosquat rules stay independently editable.
292
+ const skipPairRule = name.length < 4 || POPULAR_PACKAGES_SET.has(name) || TYPOSQUAT_ALLOWLIST.has(name);
293
+ if (!skipPairRule) {
294
+ for (const other of entries) {
295
+ const otherName = other.name;
296
+ if (name === otherName)
297
+ continue;
298
+ if (name.startsWith("@") || otherName.startsWith("@"))
299
+ continue;
300
+ // Same length floor as the sibling rule: below 4 characters almost any
301
+ // name is one edit from another (ws/wss, np/nx, fs/fse, qs/q).
302
+ if (otherName.length < 4)
303
+ continue;
304
+ if (Math.abs(name.length - otherName.length) > 1)
305
+ continue;
306
+ const dist = osaDistance(name, otherName);
307
+ if (dist > 0 && dist <= 1) {
308
+ const pairKey = [name, otherName].sort().join(" ");
309
+ if (reportedPairs.has(pairKey))
310
+ break;
311
+ reportedPairs.add(pairKey);
312
+ const otherVia = other.aliased ? ` (installed via the npm alias "${other.key}")` : "";
313
+ findings.push({
314
+ rule: "TYPOSQUAT_SIMILAR_TO_DEP",
315
+ // "name" leads and is guaranteed to be the non-allowlisted side, so
316
+ // the suspicious package is named first.
317
+ description: `Dependencies "${name}"${via} and "${otherName}"${otherVia} differ by only ${dist} character(s). One may be a typosquat of the other.`,
318
+ severity: "high",
319
+ file: relativePath,
320
+ confidence: 0.7,
321
+ category: "supply-chain",
322
+ recommendation: `Review both "${name}" and "${otherName}". Only one should be in your dependencies.`,
323
+ });
324
+ break;
325
+ }
136
326
  }
137
327
  }
138
328
  }
@@ -1 +1 @@
1
- {"version":3,"file":"dependency-risk-analyzer.js","sourceRoot":"","sources":["../src/dependency-risk-analyzer.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"dependency-risk-analyzer.js","sourceRoot":"","sources":["../src/dependency-risk-analyzer.ts"],"names":[],"mappings":"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"}
@@ -11,7 +11,9 @@
11
11
  * No network calls are made by this module.
12
12
  */
13
13
  import { type FeedIOC } from "./threat-intel.js";
14
+ import { resolveNpmAlias } from "./dependency-risk-analyzer.js";
14
15
  import type { Finding } from "./types.js";
16
+ export { resolveNpmAlias };
15
17
  export declare const SUPPORTED_MANAGERS: readonly ["npm", "pnpm", "yarn", "bun"];
16
18
  export type PackageManager = (typeof SUPPORTED_MANAGERS)[number];
17
19
  /**
@@ -50,7 +52,12 @@ export declare function extractInstallSpecs(manager: PackageManager, args: strin
50
52
  };
51
53
  export interface InstallGuardVerdict {
52
54
  spec: InstallPackageSpec;
55
+ /** Every finding, blocking or not. Nothing is dropped. */
53
56
  findings: Finding[];
57
+ /** Findings that carry blocking authority */
58
+ blocking: Finding[];
59
+ /** Heuristic findings that are reported but do not block */
60
+ warnings: Finding[];
54
61
  }
55
62
  export interface InstallCommandAnalysis {
56
63
  manager: PackageManager;
@@ -61,8 +68,10 @@ export interface InstallCommandAnalysis {
61
68
  specs: InstallPackageSpec[];
62
69
  /** One verdict per spec; findings empty = clean */
63
70
  verdicts: InstallGuardVerdict[];
64
- /** True when at least one spec has findings */
71
+ /** True when at least one spec has a BLOCKING finding */
65
72
  blocked: boolean;
73
+ /** True when at least one spec has a warn-only finding */
74
+ warned: boolean;
66
75
  }
67
76
  /**
68
77
  * npm package IOCs in the feed carry no ecosystem prefix (only ruby:/composer:/
@@ -1 +1 @@
1
- {"version":3,"file":"install-guard.d.ts","sourceRoot":"","sources":["../src/install-guard.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"install-guard.d.ts","sourceRoot":"","sources":["../src/install-guard.ts"],"names":[],"mappings":"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"}
@@ -12,7 +12,7 @@
12
12
  * No network calls are made by this module.
13
13
  */
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
- exports.SUPPORTED_MANAGERS = void 0;
15
+ exports.SUPPORTED_MANAGERS = exports.resolveNpmAlias = void 0;
16
16
  exports.resolveManagerBinary = resolveManagerBinary;
17
17
  exports.parseSpecToken = parseSpecToken;
18
18
  exports.extractInstallSpecs = extractInstallSpecs;
@@ -25,6 +25,7 @@ const node_child_process_1 = require("node:child_process");
25
25
  const threat_intel_js_1 = require("./threat-intel.js");
26
26
  const ioc_blocklist_js_1 = require("./ioc-blocklist.js");
27
27
  const dependency_risk_analyzer_js_1 = require("./dependency-risk-analyzer.js");
28
+ Object.defineProperty(exports, "resolveNpmAlias", { enumerable: true, get: function () { return dependency_risk_analyzer_js_1.resolveNpmAlias; } });
28
29
  // ---------------------------------------------------------------------------
29
30
  // Managers and install verbs
30
31
  // ---------------------------------------------------------------------------
@@ -79,6 +80,24 @@ const REGISTRY_NAME_RE = /^(@[a-z0-9][a-z0-9-._~]*\/)?[a-z0-9-._~][a-z0-9-._~]*$
79
80
  function parseSpecToken(token) {
80
81
  if (token.length === 0 || token.startsWith("-"))
81
82
  return null;
83
+ // Alias form FIRST: "utils@npm:chalk-tempalte@1.0.0". This must be detected
84
+ // before the version split, because lastIndexOf("@") would land on the alias
85
+ // target's own version and leave an unparseable name. Previously that made the
86
+ // whole spec fail REGISTRY_NAME_RE and get dropped, so `npm install
87
+ // x@npm:<malware>` was never checked at all - a complete bypass of the guard.
88
+ // The installed package is the alias TARGET, so resolve and check that.
89
+ const aliasAt = token.indexOf("@npm:");
90
+ if (aliasAt > 0) {
91
+ const label = token.substring(0, aliasAt);
92
+ if (!REGISTRY_NAME_RE.test(label))
93
+ return null;
94
+ const alias = (0, dependency_risk_analyzer_js_1.resolveNpmAlias)(token.substring(aliasAt + 1));
95
+ if (!alias)
96
+ return null;
97
+ return alias.version === undefined
98
+ ? { raw: token, name: alias.name }
99
+ : { raw: token, name: alias.name, version: alias.version };
100
+ }
82
101
  // Split "name@version" / "@scope/name@version" at the LAST "@";
83
102
  // index 0 is the scope marker, not a version separator.
84
103
  const at = token.lastIndexOf("@");
@@ -86,8 +105,8 @@ function parseSpecToken(token) {
86
105
  const version = at > 0 ? token.substring(at + 1) : undefined;
87
106
  if (!REGISTRY_NAME_RE.test(name))
88
107
  return null;
89
- // Protocol versions ("foo@npm:bar@1.0.0" aliases, "foo@git:...") are not
90
- // plain registry pins; skip rather than mis-attribute a version.
108
+ // Other protocol versions ("foo@git:...") are not plain registry pins; skip
109
+ // rather than mis-attribute a version.
91
110
  if (version !== undefined && (version.length === 0 || version.includes(":")))
92
111
  return null;
93
112
  return version === undefined ? { raw: token, name } : { raw: token, name, version };
@@ -148,6 +167,31 @@ function extractInstallSpecs(manager, args) {
148
167
  }
149
168
  return { verb, installVerb: true, specs };
150
169
  }
170
+ // ---------------------------------------------------------------------------
171
+ // Offline analysis
172
+ // ---------------------------------------------------------------------------
173
+ /**
174
+ * Rules that are REPORTED but never block an install.
175
+ *
176
+ * A deny-list, not an allow-list, on purpose: a new exact-match rule added to
177
+ * checkSpec keeps blocking authority automatically, and only a rule someone
178
+ * deliberately lists here loses it. Given that a false negative silently lowers
179
+ * the bar everywhere this runs, the safe default has to be "blocks".
180
+ *
181
+ * Only DEP_INTERNAL_NAME_PUBLIC is demoted. It fires on the NAME SHAPE of a
182
+ * scoped package (any @scope/*-utils, -api, -core, -service, -shared, -common,
183
+ * -lib), which matched 1.7% of all real scoped packages in a registry sweep -
184
+ * including @babel/helper-plugin-utils, @vue/compiler-core and
185
+ * @tanstack/query-core - and blocked every one of them at critical.
186
+ *
187
+ * TYPOSQUAT_LEVENSHTEIN is deliberately NOT demoted, even though it is also a
188
+ * heuristic. checkSpec never consults src/patterns.ts, so for curated squats
189
+ * whose target is popular (lodahs, crossenv, cros-env, 1odash, l0dash, expresss,
190
+ * nodemai1er, reqeust, axois, raect) this rule is the guard's ONLY view of them:
191
+ * demoting it was measured to lose 10 real blocks. Wiring the curated names into
192
+ * checkSpec is the prerequisite for ever revisiting that.
193
+ */
194
+ const WARN_ONLY_RULES = new Set(["DEP_INTERNAL_NAME_PUBLIC"]);
151
195
  /**
152
196
  * npm package IOCs in the feed carry no ecosystem prefix (only ruby:/composer:/
153
197
  * nuget:/go:/jenkins: entries do - see matchPackageIOC). Same companion
@@ -214,12 +258,21 @@ function analyzeInstallCommand(manager, args, feed) {
214
258
  }
215
259
  const { verb, installVerb, specs } = extractInstallSpecs(manager, args);
216
260
  if (!installVerb || specs.length === 0) {
217
- return { manager, verb, installVerb, specs, verdicts: [], blocked: false };
261
+ return { manager, verb, installVerb, specs, verdicts: [], blocked: false, warned: false };
218
262
  }
219
263
  const entries = feed ?? (0, threat_intel_js_1.loadThreatIntel)();
220
- const verdicts = specs.map((spec) => ({ spec, findings: checkSpec(spec, entries) }));
221
- const blocked = verdicts.some((v) => v.findings.length > 0);
222
- return { manager, verb, installVerb, specs, verdicts, blocked };
264
+ const verdicts = specs.map((spec) => {
265
+ const findings = checkSpec(spec, entries);
266
+ return {
267
+ spec,
268
+ findings,
269
+ blocking: findings.filter((f) => !WARN_ONLY_RULES.has(f.rule)),
270
+ warnings: findings.filter((f) => WARN_ONLY_RULES.has(f.rule)),
271
+ };
272
+ });
273
+ const blocked = verdicts.some((v) => v.blocking.length > 0);
274
+ const warned = verdicts.some((v) => v.warnings.length > 0);
275
+ return { manager, verb, installVerb, specs, verdicts, blocked, warned };
223
276
  }
224
277
  // cmd.exe metacharacters that must be ^-escaped (cross-spawn's meta set).
225
278
  const CMD_META_RE = /([()\][%!^"`<>&|;, *?])/g;
@@ -266,15 +319,20 @@ const defaultSpawn = (command, args, options) => {
266
319
  const result = (0, node_child_process_1.spawnSync)(command, args, options);
267
320
  return { status: result.status, error: result.error };
268
321
  };
269
- function printVerdicts(analysis, log) {
270
- const hits = analysis.verdicts.filter((v) => v.findings.length > 0);
271
- const total = hits.reduce((n, v) => n + v.findings.length, 0);
322
+ function printVerdicts(analysis, log, kind = "blocking") {
323
+ const hits = analysis.verdicts.filter((v) => v[kind].length > 0);
324
+ if (hits.length === 0)
325
+ return;
326
+ const total = hits.reduce((n, v) => n + v[kind].length, 0);
327
+ const label = kind === "blocking"
328
+ ? `${total} finding(s)`
329
+ : `${total} warning(s) (reported, not blocking)`;
272
330
  log("");
273
- log(` Install guard: ${total} finding(s) in "${analysis.manager} ${analysis.verb}" command:`);
331
+ log(` Install guard: ${label} in "${analysis.manager} ${analysis.verb}" command:`);
274
332
  for (const verdict of hits) {
275
333
  log("");
276
334
  log(` Package: ${verdict.spec.raw}`);
277
- for (const finding of verdict.findings) {
335
+ for (const finding of verdict[kind]) {
278
336
  log(` [${finding.severity.toUpperCase()}] ${finding.rule}: ${finding.description}`);
279
337
  log(` ${finding.recommendation}`);
280
338
  }
@@ -296,7 +354,9 @@ function runInstallGuard(manager, managerArgs, options = {}) {
296
354
  const log = options.log ?? ((line) => console.log(line));
297
355
  const analysis = analyzeInstallCommand(manager, managerArgs, options.feed);
298
356
  if (analysis.blocked) {
299
- printVerdicts(analysis, log);
357
+ printVerdicts(analysis, log, "blocking");
358
+ // Warn-only findings on a blocked command are still worth showing.
359
+ printVerdicts(analysis, log, "warnings");
300
360
  if (options.dryRun) {
301
361
  log(" Dry run: package manager not invoked.");
302
362
  return 2;
@@ -307,6 +367,16 @@ function runInstallGuard(manager, managerArgs, options = {}) {
307
367
  }
308
368
  log(" WARNING: --force is set - proceeding DESPITE the findings above. The packages listed are known-bad or suspicious.");
309
369
  }
370
+ else if (analysis.warned) {
371
+ // Heuristic-only findings: report in full, then let the install proceed.
372
+ // These used to exit 2, which made a name-shape guess as authoritative as a
373
+ // threat-feed malware match.
374
+ printVerdicts(analysis, log, "warnings");
375
+ if (options.dryRun) {
376
+ log(" Dry run: nothing known-bad, warnings above are advisory only.");
377
+ return 0;
378
+ }
379
+ }
310
380
  else if (options.dryRun) {
311
381
  log(` Install guard: no known-bad packages in "${[analysis.manager, ...managerArgs].join(" ")}" (dry run, nothing executed).`);
312
382
  return 0;