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