scan-compromised 1.0.17 → 1.0.18

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/scripts/index.js +66 -30
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scan-compromised",
3
- "version": "1.0.17",
3
+ "version": "1.0.18",
4
4
  "description": "A simple npm CLI tool (starter template)",
5
5
  "main": "scripts/index.js",
6
6
  "type": "module",
package/scripts/index.js CHANGED
@@ -2,45 +2,55 @@
2
2
  import { existsSync, readFileSync } from "fs";
3
3
  import { join, isAbsolute, dirname } from "path";
4
4
  import { fileURLToPath } from "url";
5
+ import util from "util";
5
6
  import CONFIG from "../config/config.js";
6
7
 
8
+ // Create a debug logger for the "threats" namespace
9
+ const debug = util.debuglog("threats");
10
+
7
11
  const __filename = fileURLToPath(import.meta.url);
8
12
  const __dirname = dirname(__filename);
9
13
 
10
14
  const { threatsFile } = CONFIG;
11
15
  if (!threatsFile) throw new Error("Set threatsFile in config");
12
16
 
13
- // Try resolving from project root first
14
- const projectPath = isAbsolute(threatsFile)
15
- ? threatsFile
16
- : join(process.cwd(), threatsFile);
17
+ debug("CONFIG.threatsFile = %s", threatsFile);
18
+
19
+ let resolvedThreatsPath;
20
+
21
+ if (isAbsolute(threatsFile)) {
22
+ debug("Path is absolute");
23
+ resolvedThreatsPath = threatsFile;
24
+ } else {
25
+ debug("Path is relative");
26
+
27
+ const localPath = join(process.cwd(), threatsFile);
28
+ debug("Checking local path: %s -> %s", localPath, existsSync(localPath));
17
29
 
18
- // Fallback to CLI-relative path
19
- const fallbackPath = isAbsolute(threatsFile)
20
- ? threatsFile
21
- : join(__dirname, threatsFile);
30
+ const cliPath = join(__dirname, "..", threatsFile);
31
+ debug("Checking CLI path: %s -> %s", cliPath, existsSync(cliPath));
22
32
 
23
- // Final path
24
- const resolvedThreatsPath = existsSync(projectPath)
25
- ? projectPath
26
- : existsSync(fallbackPath)
27
- ? fallbackPath
28
- : null;
33
+ if (existsSync(localPath)) {
34
+ resolvedThreatsPath = localPath;
35
+ } else if (existsSync(cliPath)) {
36
+ resolvedThreatsPath = cliPath;
37
+ }
38
+ }
29
39
 
30
- if (!resolvedThreatsPath) {
40
+ if (!resolvedThreatsPath || !existsSync(resolvedThreatsPath)) {
31
41
  console.error(`❌ ${threatsFile} not found in project or CLI directory.`);
32
42
  process.exit(1);
33
43
  }
34
44
 
45
+ debug("Using threats file: %s", resolvedThreatsPath);
35
46
 
36
47
  function loadThreats() {
37
- if (!existsSync(resolvedThreatsPath)) {
38
- console.error(`❌ ${resolvedThreatsPath} not found.`);
39
- process.exit(1);
40
- }
48
+ debug("Loading threats from %s", resolvedThreatsPath);
41
49
  try {
42
50
  const raw = readFileSync(resolvedThreatsPath, "utf8");
43
- return JSON.parse(raw);
51
+ const parsed = JSON.parse(raw);
52
+ debug("Loaded %d packages from threats file", Object.keys(parsed).length);
53
+ return parsed;
44
54
  } catch (err) {
45
55
  console.error(`❌ Failed to parse ${resolvedThreatsPath}:`, err.message);
46
56
  process.exit(1);
@@ -61,21 +71,24 @@ function escRe(s) {
61
71
  }
62
72
 
63
73
  function getCompromisedMap() {
74
+ debug("Building compromised map");
64
75
  const map = new Map();
65
76
  for (const [pkg, versions] of Object.entries(compromised)) {
66
77
  map.set(pkg, new Set(versions));
67
78
  }
79
+ debug("Compromised map contains %d packages", map.size);
68
80
  return map;
69
81
  }
70
82
 
71
83
  function recordFinding(findings, type, file, pkg, version, where) {
84
+ debug("Recording finding: %s %s@%s in %s (%s)", type, pkg, version, file, where);
72
85
  findings.push({ type, file, pkg, version, where });
73
86
  }
74
87
 
75
88
  // -------- Scanners --------
76
89
 
77
- // package.json
78
90
  function scanPackageJson(content, bad) {
91
+ debug("Scanning package.json");
79
92
  const findings = [];
80
93
  try {
81
94
  const json = JSON.parse(content);
@@ -85,6 +98,7 @@ function scanPackageJson(content, bad) {
85
98
  ...(json.peerDependencies || {}),
86
99
  ...(json.optionalDependencies || {})
87
100
  };
101
+ debug("Found %d dependencies in package.json", Object.keys(allDeps).length);
88
102
  for (const [pkg, range] of Object.entries(allDeps)) {
89
103
  if (!bad.has(pkg)) continue;
90
104
  let type = "warn";
@@ -96,16 +110,18 @@ function scanPackageJson(content, bad) {
96
110
  }
97
111
  recordFinding(findings, type, "package.json", pkg, range, "declared dependency");
98
112
  }
99
- } catch {}
113
+ } catch (err) {
114
+ debug("Error parsing package.json: %s", err.message);
115
+ }
116
+ debug("package.json scan complete: %d findings", findings.length);
100
117
  return findings;
101
118
  }
102
119
 
103
- // package-lock.json
104
120
  function scanPackageLock(content, bad) {
121
+ debug("Scanning package-lock.json");
105
122
  const findings = [];
106
123
  try {
107
124
  const lock = JSON.parse(content);
108
- const stack = [];
109
125
 
110
126
  function visitDeps(obj, pathArr = []) {
111
127
  if (!obj) return;
@@ -121,6 +137,7 @@ function scanPackageLock(content, bad) {
121
137
  }
122
138
 
123
139
  if (lock.packages && typeof lock.packages === "object") {
140
+ debug("Detected npm v7+ lockfile format");
124
141
  for (const [pkgPath, meta] of Object.entries(lock.packages)) {
125
142
  if (!meta || !meta.version) continue;
126
143
  const name = meta.name || (pkgPath.includes("node_modules/") ? pkgPath.split("node_modules/").pop() : null);
@@ -131,14 +148,18 @@ function scanPackageLock(content, bad) {
131
148
  }
132
149
  }
133
150
  } else {
151
+ debug("Detected npm v6 lockfile format");
134
152
  visitDeps(lock, []);
135
153
  }
136
- } catch {}
154
+ } catch (err) {
155
+ debug("Error parsing package-lock.json: %s", err.message);
156
+ }
157
+ debug("package-lock.json scan complete: %d findings", findings.length);
137
158
  return findings;
138
159
  }
139
160
 
140
- // yarn.lock v1
141
161
  function scanYarnLockV1(content, bad) {
162
+ debug("Scanning yarn.lock v1");
142
163
  const findings = [];
143
164
  const entries = content.split(/\n{2,}/g);
144
165
  for (const entry of entries) {
@@ -166,11 +187,12 @@ function scanYarnLockV1(content, bad) {
166
187
  }
167
188
  }
168
189
  }
190
+ debug("yarn.lock v1 scan complete: %d findings", findings.length);
169
191
  return findings;
170
192
  }
171
193
 
172
- // Yarn Berry (v2+)
173
194
  function scanYarnBerryLock(content, bad) {
195
+ debug("Scanning Yarn Berry lockfile");
174
196
  const findings = [];
175
197
  const blockRe = /^("?([^"\n]+)"?):\n((?: {2}.+\n)+)/gm;
176
198
  let m;
@@ -192,11 +214,12 @@ function scanYarnBerryLock(content, bad) {
192
214
  recordFinding(findings, type, "yarn.lock", name, version, key);
193
215
  }
194
216
  }
217
+ debug("Yarn Berry scan complete: %d findings", findings.length);
195
218
  return findings;
196
219
  }
197
220
 
198
- // pnpm-lock.yaml
199
221
  function scanPnpmLock(content, bad) {
222
+ debug("Scanning pnpm-lock.yaml");
200
223
  const findings = [];
201
224
  const re = /^\s*\/?(@?[^@\s/][^@:\s/]*\/?[^@:\s/]*)@([0-9][^:\s]+):/gm;
202
225
  let m;
@@ -208,18 +231,25 @@ function scanPnpmLock(content, bad) {
208
231
  recordFinding(findings, type, "pnpm-lock.yaml", name, version, `${name}@${version}`);
209
232
  }
210
233
  }
234
+ debug("pnpm-lock.yaml scan complete: %d findings", findings.length);
211
235
  return findings;
212
236
  }
213
237
 
214
238
  // -------- Runner --------
215
239
  (function main() {
240
+ debug("Starting main scan");
216
241
  const bad = getCompromisedMap();
217
242
  const allFindings = [];
218
243
 
219
244
  for (const file of filesToCheck) {
245
+ debug("Checking for %s", file);
220
246
  const filePath = join(process.cwd(), file);
221
- if (!existsSync(filePath)) continue;
247
+ if (!existsSync(filePath)) {
248
+ debug("Skipping %s (not found)", file);
249
+ continue;
250
+ }
222
251
  const content = readFileSync(filePath, "utf8");
252
+ debug("Found %s, size %d bytes", file, content.length);
223
253
 
224
254
  if (file === "package.json") {
225
255
  allFindings.push(...scanPackageJson(content, bad));
@@ -234,9 +264,13 @@ function scanPnpmLock(content, bad) {
234
264
  }
235
265
  }
236
266
 
267
+ debug("Total findings collected: %d", allFindings.length);
268
+
237
269
  const badFindings = allFindings.filter(f => f.type === "bad");
238
270
  const warnFindings = allFindings.filter(f => f.type === "warn");
239
271
 
272
+ debug("Bad findings: %d, Warn findings: %d", badFindings.length, warnFindings.length);
273
+
240
274
  warnFindings.forEach(f =>
241
275
  console.log(`⚠️ WARNING: ${f.pkg}@${f.version} in ${f.file} (${f.where}) — package was targeted in past attack, but version is not flagged as malicious`)
242
276
  );
@@ -248,6 +282,8 @@ function scanPnpmLock(content, bad) {
248
282
  if (badFindings.length === 0) {
249
283
  console.log("✅ No known malicious versions detected.");
250
284
  } else {
285
+ debug("Exiting with code 1 due to bad findings");
251
286
  process.exit(1);
252
287
  }
253
- })();
288
+ debug("Scan complete");
289
+ })();