@mr-aftab-ahmad-khan/depguard-cli 0.1.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/CHANGELOG.md +10 -0
- package/LICENSE +15 -0
- package/README.md +337 -0
- package/dist/chunk-BH7GFJ3G.js +592 -0
- package/dist/chunk-BH7GFJ3G.js.map +1 -0
- package/dist/cli.cjs +679 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +88 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.cjs +639 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +63 -0
- package/dist/index.d.ts +63 -0
- package/dist/index.js +29 -0
- package/dist/index.js.map +1 -0
- package/package.json +67 -0
|
@@ -0,0 +1,592 @@
|
|
|
1
|
+
// src/scoring.ts
|
|
2
|
+
var SEVERITY_ORDER = {
|
|
3
|
+
info: 0,
|
|
4
|
+
low: 1,
|
|
5
|
+
medium: 2,
|
|
6
|
+
high: 3,
|
|
7
|
+
critical: 4
|
|
8
|
+
};
|
|
9
|
+
function maxSeverity(findings) {
|
|
10
|
+
let worst = "info";
|
|
11
|
+
for (const f of findings) {
|
|
12
|
+
if (SEVERITY_ORDER[f.severity] > SEVERITY_ORDER[worst]) worst = f.severity;
|
|
13
|
+
}
|
|
14
|
+
return worst;
|
|
15
|
+
}
|
|
16
|
+
function severityAtLeast(a, b) {
|
|
17
|
+
return SEVERITY_ORDER[a] >= SEVERITY_ORDER[b];
|
|
18
|
+
}
|
|
19
|
+
function scoreInstallScript(script) {
|
|
20
|
+
const reasons = [];
|
|
21
|
+
let score = 0;
|
|
22
|
+
const lower = script.toLowerCase();
|
|
23
|
+
if (/(curl|wget)\s+[^|]+\|\s*(sh|bash|zsh|node)/i.test(script)) {
|
|
24
|
+
score += 5;
|
|
25
|
+
reasons.push("curl/wget piped to a shell or node");
|
|
26
|
+
}
|
|
27
|
+
if (/\bbase64\b/.test(lower) && /\b(eval|exec)\b/.test(lower)) {
|
|
28
|
+
score += 5;
|
|
29
|
+
reasons.push("base64 combined with eval/exec");
|
|
30
|
+
}
|
|
31
|
+
if (/\bnew\s+Function\s*\(/.test(script)) {
|
|
32
|
+
score += 3;
|
|
33
|
+
reasons.push("dynamic `new Function()` invocation");
|
|
34
|
+
}
|
|
35
|
+
if (/process\.env(\.|\[)/.test(script)) {
|
|
36
|
+
score += 2;
|
|
37
|
+
reasons.push("reads process.env at install time");
|
|
38
|
+
}
|
|
39
|
+
if (/https?:\/\/(?!registry\.npmjs\.org|nodejs\.org|github\.com)[^\s'"`]+/i.test(script)) {
|
|
40
|
+
score += 3;
|
|
41
|
+
reasons.push("network call to a non-trusted domain");
|
|
42
|
+
}
|
|
43
|
+
if (/[A-Za-z0-9+/]{200,}={0,2}/.test(script)) {
|
|
44
|
+
score += 3;
|
|
45
|
+
reasons.push("very long opaque token (possible obfuscation)");
|
|
46
|
+
}
|
|
47
|
+
if (script.length > 300 && !script.includes("\n")) {
|
|
48
|
+
score += 2;
|
|
49
|
+
reasons.push("obfuscated one-liner > 300 chars");
|
|
50
|
+
}
|
|
51
|
+
return { score: Math.min(score, 10), reasons };
|
|
52
|
+
}
|
|
53
|
+
function levenshtein(a, b) {
|
|
54
|
+
if (a === b) return 0;
|
|
55
|
+
if (a.length === 0) return b.length;
|
|
56
|
+
if (b.length === 0) return a.length;
|
|
57
|
+
const prev = new Array(b.length + 1);
|
|
58
|
+
const curr = new Array(b.length + 1);
|
|
59
|
+
for (let j = 0; j <= b.length; j++) prev[j] = j;
|
|
60
|
+
for (let i = 1; i <= a.length; i++) {
|
|
61
|
+
curr[0] = i;
|
|
62
|
+
for (let j = 1; j <= b.length; j++) {
|
|
63
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
64
|
+
curr[j] = Math.min(curr[j - 1] + 1, prev[j] + 1, prev[j - 1] + cost);
|
|
65
|
+
}
|
|
66
|
+
for (let j = 0; j <= b.length; j++) prev[j] = curr[j];
|
|
67
|
+
}
|
|
68
|
+
return prev[b.length];
|
|
69
|
+
}
|
|
70
|
+
function findTyposquatCandidate(name, topPackages) {
|
|
71
|
+
if (topPackages.includes(name)) return void 0;
|
|
72
|
+
for (const top of topPackages) {
|
|
73
|
+
if (Math.abs(top.length - name.length) > 2) continue;
|
|
74
|
+
const dist = levenshtein(name, top);
|
|
75
|
+
if (dist > 0 && dist <= 2) return top;
|
|
76
|
+
}
|
|
77
|
+
return void 0;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// src/top-packages.ts
|
|
81
|
+
var TOP_PACKAGES = [
|
|
82
|
+
"react",
|
|
83
|
+
"react-dom",
|
|
84
|
+
"next",
|
|
85
|
+
"vue",
|
|
86
|
+
"svelte",
|
|
87
|
+
"angular",
|
|
88
|
+
"lodash",
|
|
89
|
+
"axios",
|
|
90
|
+
"express",
|
|
91
|
+
"fastify",
|
|
92
|
+
"koa",
|
|
93
|
+
"hono",
|
|
94
|
+
"moment",
|
|
95
|
+
"date-fns",
|
|
96
|
+
"dayjs",
|
|
97
|
+
"luxon",
|
|
98
|
+
"typescript",
|
|
99
|
+
"ts-node",
|
|
100
|
+
"tsx",
|
|
101
|
+
"tsup",
|
|
102
|
+
"esbuild",
|
|
103
|
+
"webpack",
|
|
104
|
+
"vite",
|
|
105
|
+
"rollup",
|
|
106
|
+
"parcel",
|
|
107
|
+
"babel",
|
|
108
|
+
"eslint",
|
|
109
|
+
"prettier",
|
|
110
|
+
"jest",
|
|
111
|
+
"vitest",
|
|
112
|
+
"mocha",
|
|
113
|
+
"chai",
|
|
114
|
+
"sinon",
|
|
115
|
+
"ava",
|
|
116
|
+
"cypress",
|
|
117
|
+
"playwright",
|
|
118
|
+
"puppeteer",
|
|
119
|
+
"redux",
|
|
120
|
+
"zustand",
|
|
121
|
+
"jotai",
|
|
122
|
+
"mobx",
|
|
123
|
+
"recoil",
|
|
124
|
+
"rxjs",
|
|
125
|
+
"graphql",
|
|
126
|
+
"apollo-client",
|
|
127
|
+
"apollo-server",
|
|
128
|
+
"prisma",
|
|
129
|
+
"drizzle-orm",
|
|
130
|
+
"kysely",
|
|
131
|
+
"knex",
|
|
132
|
+
"typeorm",
|
|
133
|
+
"sequelize",
|
|
134
|
+
"mongoose",
|
|
135
|
+
"pg",
|
|
136
|
+
"mysql2",
|
|
137
|
+
"sqlite3",
|
|
138
|
+
"better-sqlite3",
|
|
139
|
+
"redis",
|
|
140
|
+
"ioredis",
|
|
141
|
+
"bullmq",
|
|
142
|
+
"kafkajs",
|
|
143
|
+
"amqplib",
|
|
144
|
+
"socket.io",
|
|
145
|
+
"ws",
|
|
146
|
+
"uws",
|
|
147
|
+
"polka",
|
|
148
|
+
"fastify",
|
|
149
|
+
"h3",
|
|
150
|
+
"commander",
|
|
151
|
+
"yargs",
|
|
152
|
+
"minimist",
|
|
153
|
+
"inquirer",
|
|
154
|
+
"prompts",
|
|
155
|
+
"ora",
|
|
156
|
+
"chalk",
|
|
157
|
+
"picocolors",
|
|
158
|
+
"kleur",
|
|
159
|
+
"boxen",
|
|
160
|
+
"cli-table3",
|
|
161
|
+
"figlet",
|
|
162
|
+
"fs-extra",
|
|
163
|
+
"fast-glob",
|
|
164
|
+
"glob",
|
|
165
|
+
"globby",
|
|
166
|
+
"rimraf",
|
|
167
|
+
"del",
|
|
168
|
+
"execa",
|
|
169
|
+
"shelljs",
|
|
170
|
+
"cross-env",
|
|
171
|
+
"dotenv",
|
|
172
|
+
"joi",
|
|
173
|
+
"yup",
|
|
174
|
+
"zod",
|
|
175
|
+
"ajv",
|
|
176
|
+
"valibot",
|
|
177
|
+
"superstruct",
|
|
178
|
+
"io-ts",
|
|
179
|
+
"class-validator",
|
|
180
|
+
"passport",
|
|
181
|
+
"jsonwebtoken",
|
|
182
|
+
"jose",
|
|
183
|
+
"bcrypt",
|
|
184
|
+
"bcryptjs",
|
|
185
|
+
"argon2",
|
|
186
|
+
"uuid",
|
|
187
|
+
"nanoid",
|
|
188
|
+
"ulid",
|
|
189
|
+
"cuid",
|
|
190
|
+
"shortid",
|
|
191
|
+
"ms",
|
|
192
|
+
"humanize-duration",
|
|
193
|
+
"pluralize",
|
|
194
|
+
"marked",
|
|
195
|
+
"remark",
|
|
196
|
+
"rehype",
|
|
197
|
+
"showdown",
|
|
198
|
+
"turndown",
|
|
199
|
+
"cheerio",
|
|
200
|
+
"jsdom",
|
|
201
|
+
"playwright-core",
|
|
202
|
+
"node-fetch",
|
|
203
|
+
"got",
|
|
204
|
+
"ky",
|
|
205
|
+
"openai",
|
|
206
|
+
"anthropic",
|
|
207
|
+
"ai",
|
|
208
|
+
"@modelcontextprotocol/sdk",
|
|
209
|
+
"langchain",
|
|
210
|
+
"llamaindex",
|
|
211
|
+
"tiktoken",
|
|
212
|
+
"gpt-3-encoder",
|
|
213
|
+
"tailwindcss",
|
|
214
|
+
"postcss",
|
|
215
|
+
"autoprefixer",
|
|
216
|
+
"sass",
|
|
217
|
+
"stylus",
|
|
218
|
+
"less",
|
|
219
|
+
"styled-components",
|
|
220
|
+
"@emotion/react",
|
|
221
|
+
"@emotion/styled",
|
|
222
|
+
"framer-motion",
|
|
223
|
+
"react-router-dom",
|
|
224
|
+
"react-query",
|
|
225
|
+
"@tanstack/react-query",
|
|
226
|
+
"swr",
|
|
227
|
+
"trpc",
|
|
228
|
+
"@trpc/server",
|
|
229
|
+
"@trpc/client",
|
|
230
|
+
"winston",
|
|
231
|
+
"pino",
|
|
232
|
+
"bunyan",
|
|
233
|
+
"morgan",
|
|
234
|
+
"debug",
|
|
235
|
+
"sharp",
|
|
236
|
+
"jimp",
|
|
237
|
+
"canvas",
|
|
238
|
+
"puppeteer-core",
|
|
239
|
+
"playwright-chromium",
|
|
240
|
+
"exceljs",
|
|
241
|
+
"xlsx",
|
|
242
|
+
"pdfkit",
|
|
243
|
+
"pdf-lib",
|
|
244
|
+
"pdfmake",
|
|
245
|
+
"node-cron",
|
|
246
|
+
"agenda",
|
|
247
|
+
"bull",
|
|
248
|
+
"node-schedule",
|
|
249
|
+
"@sentry/node",
|
|
250
|
+
"@sentry/browser",
|
|
251
|
+
"@sentry/react",
|
|
252
|
+
"stripe",
|
|
253
|
+
"@stripe/stripe-js",
|
|
254
|
+
"twilio",
|
|
255
|
+
"nodemailer",
|
|
256
|
+
"resend",
|
|
257
|
+
"aws-sdk",
|
|
258
|
+
"@aws-sdk/client-s3",
|
|
259
|
+
"@aws-sdk/client-dynamodb",
|
|
260
|
+
"googleapis",
|
|
261
|
+
"firebase",
|
|
262
|
+
"firebase-admin",
|
|
263
|
+
"supabase",
|
|
264
|
+
"@supabase/supabase-js",
|
|
265
|
+
"mongodb",
|
|
266
|
+
"mongoose",
|
|
267
|
+
"elasticsearch",
|
|
268
|
+
"@elastic/elasticsearch",
|
|
269
|
+
"puppeteer-extra",
|
|
270
|
+
"playwright-extra",
|
|
271
|
+
"discord.js",
|
|
272
|
+
"telegraf",
|
|
273
|
+
"node-telegram-bot-api",
|
|
274
|
+
"slack-sdk",
|
|
275
|
+
"@slack/web-api",
|
|
276
|
+
"fast-xml-parser",
|
|
277
|
+
"xml2js",
|
|
278
|
+
"yaml",
|
|
279
|
+
"toml",
|
|
280
|
+
"ini",
|
|
281
|
+
"msgpack-lite",
|
|
282
|
+
"protobufjs",
|
|
283
|
+
"grpc",
|
|
284
|
+
"@grpc/grpc-js"
|
|
285
|
+
];
|
|
286
|
+
|
|
287
|
+
// src/scanner.ts
|
|
288
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2, readdirSync, statSync } from "fs";
|
|
289
|
+
import { join as join2 } from "path";
|
|
290
|
+
import { homedir as homedir2 } from "os";
|
|
291
|
+
|
|
292
|
+
// src/registry.ts
|
|
293
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
294
|
+
import { homedir } from "os";
|
|
295
|
+
import { join } from "path";
|
|
296
|
+
var CACHE_DIR = join(homedir(), ".depguard", "cache");
|
|
297
|
+
function cacheFile(name) {
|
|
298
|
+
const safe = name.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
299
|
+
return join(CACHE_DIR, `${safe}.json`);
|
|
300
|
+
}
|
|
301
|
+
function readCache(name) {
|
|
302
|
+
const file = cacheFile(name);
|
|
303
|
+
if (!existsSync(file)) return void 0;
|
|
304
|
+
try {
|
|
305
|
+
const entry = JSON.parse(readFileSync(file, "utf8"));
|
|
306
|
+
if (entry.expiresAt > Date.now()) return entry.data;
|
|
307
|
+
} catch {
|
|
308
|
+
return void 0;
|
|
309
|
+
}
|
|
310
|
+
return void 0;
|
|
311
|
+
}
|
|
312
|
+
function writeCache(name, data, ttlMs) {
|
|
313
|
+
if (!existsSync(CACHE_DIR)) mkdirSync(CACHE_DIR, { recursive: true });
|
|
314
|
+
const entry = { expiresAt: Date.now() + ttlMs, data };
|
|
315
|
+
writeFileSync(cacheFile(name), JSON.stringify(entry), "utf8");
|
|
316
|
+
}
|
|
317
|
+
async function fetchPackument(name, opts = {}) {
|
|
318
|
+
const ttl = opts.ttlMs ?? 60 * 60 * 1e3;
|
|
319
|
+
const cached = readCache(name);
|
|
320
|
+
if (cached) return cached;
|
|
321
|
+
const fetcher = opts.fetcher ?? globalThis.fetch;
|
|
322
|
+
if (!fetcher) return void 0;
|
|
323
|
+
try {
|
|
324
|
+
const res = await fetcher(`https://registry.npmjs.org/${encodeURIComponent(name).replace("%40", "@")}`);
|
|
325
|
+
if (!res.ok) return void 0;
|
|
326
|
+
const data = await res.json();
|
|
327
|
+
writeCache(name, data, ttl);
|
|
328
|
+
return data;
|
|
329
|
+
} catch {
|
|
330
|
+
return void 0;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// src/scanner.ts
|
|
335
|
+
function readJsonSafe(file) {
|
|
336
|
+
try {
|
|
337
|
+
return JSON.parse(readFileSync2(file, "utf8"));
|
|
338
|
+
} catch {
|
|
339
|
+
return void 0;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
function findInstalled(root, depth) {
|
|
343
|
+
const nm = join2(root, "node_modules");
|
|
344
|
+
if (!existsSync2(nm)) return [];
|
|
345
|
+
const out = [];
|
|
346
|
+
const visited = /* @__PURE__ */ new Set();
|
|
347
|
+
const walk = (dir, level) => {
|
|
348
|
+
if (visited.has(dir)) return;
|
|
349
|
+
visited.add(dir);
|
|
350
|
+
if (!existsSync2(dir)) return;
|
|
351
|
+
for (const entry of readdirSync(dir)) {
|
|
352
|
+
if (entry.startsWith(".")) continue;
|
|
353
|
+
const pkgDir = join2(dir, entry);
|
|
354
|
+
try {
|
|
355
|
+
const stat = statSync(pkgDir);
|
|
356
|
+
if (!stat.isDirectory()) continue;
|
|
357
|
+
} catch {
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
if (entry.startsWith("@")) {
|
|
361
|
+
for (const sub of readdirSync(pkgDir)) {
|
|
362
|
+
handle(join2(pkgDir, sub), `${entry}/${sub}`, level);
|
|
363
|
+
}
|
|
364
|
+
} else {
|
|
365
|
+
handle(pkgDir, entry, level);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
const handle = (pkgDir, name, level) => {
|
|
370
|
+
const pj = readJsonSafe(join2(pkgDir, "package.json"));
|
|
371
|
+
if (!pj) return;
|
|
372
|
+
out.push({
|
|
373
|
+
name: pj.name ?? name,
|
|
374
|
+
version: pj.version ?? "0.0.0",
|
|
375
|
+
dir: pkgDir,
|
|
376
|
+
pkgJson: pj
|
|
377
|
+
});
|
|
378
|
+
if (depth === "all" && existsSync2(join2(pkgDir, "node_modules"))) {
|
|
379
|
+
walk(join2(pkgDir, "node_modules"), level + 1);
|
|
380
|
+
}
|
|
381
|
+
};
|
|
382
|
+
walk(nm, 0);
|
|
383
|
+
return out;
|
|
384
|
+
}
|
|
385
|
+
function loadBaseline() {
|
|
386
|
+
const file = join2(homedir2(), ".depguard", "baseline.json");
|
|
387
|
+
if (!existsSync2(file)) return {};
|
|
388
|
+
try {
|
|
389
|
+
return JSON.parse(readFileSync2(file, "utf8"));
|
|
390
|
+
} catch {
|
|
391
|
+
return {};
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
var INSTALL_SCRIPT_FIELDS = ["preinstall", "install", "postinstall", "preuninstall", "preprepare", "prepare"];
|
|
395
|
+
function scriptFindings(pkg) {
|
|
396
|
+
const findings = [];
|
|
397
|
+
const scripts = pkg.pkgJson.scripts ?? {};
|
|
398
|
+
for (const field of INSTALL_SCRIPT_FIELDS) {
|
|
399
|
+
const src = scripts[field];
|
|
400
|
+
if (!src) continue;
|
|
401
|
+
const { score, reasons } = scoreInstallScript(src);
|
|
402
|
+
if (score === 0) continue;
|
|
403
|
+
const severity = score >= 8 ? "critical" : score >= 5 ? "high" : score >= 3 ? "medium" : "low";
|
|
404
|
+
findings.push({
|
|
405
|
+
rule: `install-script:${field}`,
|
|
406
|
+
severity,
|
|
407
|
+
message: `${field} script is suspicious (${reasons.join("; ")})`,
|
|
408
|
+
score
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
return findings;
|
|
412
|
+
}
|
|
413
|
+
function maintainerFindings(pkg, packument, baseline) {
|
|
414
|
+
if (!packument?.maintainers) return [];
|
|
415
|
+
const now = packument.maintainers.map((m) => m.name).sort();
|
|
416
|
+
const known = baseline[pkg.name]?.maintainers;
|
|
417
|
+
if (!known) return [];
|
|
418
|
+
const added = now.filter((m) => !known.includes(m));
|
|
419
|
+
if (added.length === 0) return [];
|
|
420
|
+
return [{
|
|
421
|
+
rule: "maintainer-change",
|
|
422
|
+
severity: "high",
|
|
423
|
+
message: `New maintainer(s) added since baseline: ${added.join(", ")}`,
|
|
424
|
+
score: 6
|
|
425
|
+
}];
|
|
426
|
+
}
|
|
427
|
+
function versionAnomalyFindings(pkg, packument) {
|
|
428
|
+
if (!packument?.time) return [];
|
|
429
|
+
const out = [];
|
|
430
|
+
const time = packument.time;
|
|
431
|
+
const versions = Object.keys(time).filter((k) => k !== "created" && k !== "modified");
|
|
432
|
+
const sevenDays = 7 * 24 * 60 * 60 * 1e3;
|
|
433
|
+
const major = /* @__PURE__ */ new Map();
|
|
434
|
+
for (const v of versions) {
|
|
435
|
+
const m = /^(\d+)\./.exec(v);
|
|
436
|
+
if (!m) continue;
|
|
437
|
+
const key = Number(m[1]);
|
|
438
|
+
const arr = major.get(key) ?? [];
|
|
439
|
+
arr.push(v);
|
|
440
|
+
major.set(key, arr);
|
|
441
|
+
}
|
|
442
|
+
const keys = [...major.keys()].sort((a, b) => a - b);
|
|
443
|
+
for (let i = 1; i < keys.length; i++) {
|
|
444
|
+
const prev = keys[i - 1];
|
|
445
|
+
const cur = keys[i];
|
|
446
|
+
const prevReleases = major.get(prev).map((v) => Date.parse(time[v] ?? "")).filter(Number.isFinite);
|
|
447
|
+
const curReleases = major.get(cur).map((v) => Date.parse(time[v] ?? "")).filter(Number.isFinite);
|
|
448
|
+
if (prevReleases.length === 0 || curReleases.length === 0) continue;
|
|
449
|
+
const lastPrev = Math.max(...prevReleases);
|
|
450
|
+
const firstCur = Math.min(...curReleases);
|
|
451
|
+
if (firstCur - lastPrev < sevenDays) {
|
|
452
|
+
out.push({
|
|
453
|
+
rule: "version-anomaly",
|
|
454
|
+
severity: "medium",
|
|
455
|
+
message: `Major bump v${prev} \u2192 v${cur} within 7 days (potential takeover)`,
|
|
456
|
+
score: 4
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
const created = Date.parse(time.created ?? "");
|
|
461
|
+
if (Number.isFinite(created) && Date.now() - created < 30 * 24 * 60 * 60 * 1e3) {
|
|
462
|
+
out.push({
|
|
463
|
+
rule: "young-package",
|
|
464
|
+
severity: "low",
|
|
465
|
+
message: "Package was first published less than 30 days ago",
|
|
466
|
+
score: 2
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
return out;
|
|
470
|
+
}
|
|
471
|
+
function typosquatFindings(name) {
|
|
472
|
+
const sug = findTyposquatCandidate(name, TOP_PACKAGES);
|
|
473
|
+
if (!sug) return [];
|
|
474
|
+
return [{
|
|
475
|
+
rule: "typosquat",
|
|
476
|
+
severity: "critical",
|
|
477
|
+
message: `Name closely resembles "${sug}" \u2014 likely typosquat`,
|
|
478
|
+
score: 10
|
|
479
|
+
}];
|
|
480
|
+
}
|
|
481
|
+
async function scan(options = {}) {
|
|
482
|
+
const cwd = options.cwd ?? process.cwd();
|
|
483
|
+
const depth = options.scanDepth ?? "all";
|
|
484
|
+
const ignore = new Set(options.ignore ?? []);
|
|
485
|
+
const baseline = loadBaseline();
|
|
486
|
+
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
487
|
+
const packages = [];
|
|
488
|
+
const installed = findInstalled(cwd, depth).filter((p) => !ignore.has(p.name));
|
|
489
|
+
for (const pkg of installed) {
|
|
490
|
+
const findings = [];
|
|
491
|
+
findings.push(...scriptFindings(pkg));
|
|
492
|
+
findings.push(...typosquatFindings(pkg.name));
|
|
493
|
+
if (options.network !== false) {
|
|
494
|
+
const packument = await fetchPackument(pkg.name, { ttlMs: options.cacheTTL ?? 60 * 60 * 1e3 });
|
|
495
|
+
findings.push(...maintainerFindings(pkg, packument, baseline));
|
|
496
|
+
findings.push(...versionAnomalyFindings(pkg, packument));
|
|
497
|
+
}
|
|
498
|
+
if (findings.length === 0) continue;
|
|
499
|
+
packages.push({
|
|
500
|
+
name: pkg.name,
|
|
501
|
+
version: pkg.version,
|
|
502
|
+
worstSeverity: maxSeverity(findings),
|
|
503
|
+
totalScore: findings.reduce((s, f) => s + f.score, 0),
|
|
504
|
+
findings
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
packages.sort((a, b) => b.totalScore - a.totalScore);
|
|
508
|
+
return {
|
|
509
|
+
packages,
|
|
510
|
+
scannedAt: startedAt,
|
|
511
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
512
|
+
totalPackages: installed.length
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
async function audit(name) {
|
|
516
|
+
const packument = await fetchPackument(name);
|
|
517
|
+
if (!packument) return void 0;
|
|
518
|
+
const findings = [];
|
|
519
|
+
findings.push(...typosquatFindings(name));
|
|
520
|
+
return {
|
|
521
|
+
name,
|
|
522
|
+
version: packument["dist-tags"]?.latest ?? "0.0.0",
|
|
523
|
+
worstSeverity: maxSeverity(findings),
|
|
524
|
+
totalScore: findings.reduce((s, f) => s + f.score, 0),
|
|
525
|
+
findings
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// src/format.ts
|
|
530
|
+
import pc from "picocolors";
|
|
531
|
+
var sevColor = {
|
|
532
|
+
info: pc.gray,
|
|
533
|
+
low: pc.blue,
|
|
534
|
+
medium: pc.yellow,
|
|
535
|
+
high: pc.magenta,
|
|
536
|
+
critical: pc.red
|
|
537
|
+
};
|
|
538
|
+
function formatReport(result) {
|
|
539
|
+
if (result.packages.length === 0) {
|
|
540
|
+
return pc.green(`\u2713 No risk findings across ${result.totalPackages} packages`);
|
|
541
|
+
}
|
|
542
|
+
const lines = [];
|
|
543
|
+
lines.push(pc.bold(`depguard report \u2014 ${result.packages.length} package(s) with findings (of ${result.totalPackages} scanned)`));
|
|
544
|
+
for (const p of result.packages) {
|
|
545
|
+
lines.push("");
|
|
546
|
+
lines.push(` ${sevColor[p.worstSeverity](p.worstSeverity.toUpperCase())} ${pc.bold(p.name)}@${p.version} score=${p.totalScore}`);
|
|
547
|
+
for (const f of p.findings) {
|
|
548
|
+
lines.push(` - ${sevColor[f.severity](f.severity)} ${f.rule}: ${f.message}`);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
return lines.join("\n");
|
|
552
|
+
}
|
|
553
|
+
function formatJson(result) {
|
|
554
|
+
return JSON.stringify(result, null, 2);
|
|
555
|
+
}
|
|
556
|
+
function formatPackage(p) {
|
|
557
|
+
const lines = [];
|
|
558
|
+
lines.push(`${sevColor[p.worstSeverity](p.worstSeverity.toUpperCase())} ${pc.bold(p.name)}@${p.version} score=${p.totalScore}`);
|
|
559
|
+
for (const f of p.findings) {
|
|
560
|
+
lines.push(` - ${sevColor[f.severity](f.severity)} ${f.rule}: ${f.message}`);
|
|
561
|
+
}
|
|
562
|
+
return lines.join("\n");
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
// src/config.ts
|
|
566
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
567
|
+
import { join as join3 } from "path";
|
|
568
|
+
function loadConfig(cwd) {
|
|
569
|
+
const file = join3(cwd, ".depguardrc.json");
|
|
570
|
+
if (!existsSync3(file)) return {};
|
|
571
|
+
try {
|
|
572
|
+
return JSON.parse(readFileSync3(file, "utf8"));
|
|
573
|
+
} catch {
|
|
574
|
+
return {};
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
export {
|
|
579
|
+
maxSeverity,
|
|
580
|
+
severityAtLeast,
|
|
581
|
+
scoreInstallScript,
|
|
582
|
+
levenshtein,
|
|
583
|
+
findTyposquatCandidate,
|
|
584
|
+
TOP_PACKAGES,
|
|
585
|
+
scan,
|
|
586
|
+
audit,
|
|
587
|
+
formatReport,
|
|
588
|
+
formatJson,
|
|
589
|
+
formatPackage,
|
|
590
|
+
loadConfig
|
|
591
|
+
};
|
|
592
|
+
//# sourceMappingURL=chunk-BH7GFJ3G.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/scoring.ts","../src/top-packages.ts","../src/scanner.ts","../src/registry.ts","../src/format.ts","../src/config.ts"],"sourcesContent":["import type { Finding, RiskLevel } from \"./types.js\";\n\nconst SEVERITY_ORDER: Record<RiskLevel, number> = {\n info: 0,\n low: 1,\n medium: 2,\n high: 3,\n critical: 4,\n};\n\nexport function maxSeverity(findings: Finding[]): RiskLevel {\n let worst: RiskLevel = \"info\";\n for (const f of findings) {\n if (SEVERITY_ORDER[f.severity] > SEVERITY_ORDER[worst]) worst = f.severity;\n }\n return worst;\n}\n\nexport function severityAtLeast(a: RiskLevel, b: RiskLevel): boolean {\n return SEVERITY_ORDER[a] >= SEVERITY_ORDER[b];\n}\n\nexport interface ScriptScore {\n score: number;\n reasons: string[];\n}\n\nexport function scoreInstallScript(script: string): ScriptScore {\n const reasons: string[] = [];\n let score = 0;\n const lower = script.toLowerCase();\n\n if (/(curl|wget)\\s+[^|]+\\|\\s*(sh|bash|zsh|node)/i.test(script)) {\n score += 5;\n reasons.push(\"curl/wget piped to a shell or node\");\n }\n if (/\\bbase64\\b/.test(lower) && /\\b(eval|exec)\\b/.test(lower)) {\n score += 5;\n reasons.push(\"base64 combined with eval/exec\");\n }\n if (/\\bnew\\s+Function\\s*\\(/.test(script)) {\n score += 3;\n reasons.push(\"dynamic `new Function()` invocation\");\n }\n if (/process\\.env(\\.|\\[)/.test(script)) {\n score += 2;\n reasons.push(\"reads process.env at install time\");\n }\n if (/https?:\\/\\/(?!registry\\.npmjs\\.org|nodejs\\.org|github\\.com)[^\\s'\"`]+/i.test(script)) {\n score += 3;\n reasons.push(\"network call to a non-trusted domain\");\n }\n if (/[A-Za-z0-9+/]{200,}={0,2}/.test(script)) {\n score += 3;\n reasons.push(\"very long opaque token (possible obfuscation)\");\n }\n if (script.length > 300 && !script.includes(\"\\n\")) {\n score += 2;\n reasons.push(\"obfuscated one-liner > 300 chars\");\n }\n\n return { score: Math.min(score, 10), reasons };\n}\n\nexport function levenshtein(a: string, b: string): number {\n if (a === b) return 0;\n if (a.length === 0) return b.length;\n if (b.length === 0) return a.length;\n const prev = new Array<number>(b.length + 1);\n const curr = new Array<number>(b.length + 1);\n for (let j = 0; j <= b.length; j++) prev[j] = j;\n for (let i = 1; i <= a.length; i++) {\n curr[0] = i;\n for (let j = 1; j <= b.length; j++) {\n const cost = a[i - 1] === b[j - 1] ? 0 : 1;\n curr[j] = Math.min(curr[j - 1]! + 1, prev[j]! + 1, prev[j - 1]! + cost);\n }\n for (let j = 0; j <= b.length; j++) prev[j] = curr[j]!;\n }\n return prev[b.length]!;\n}\n\nexport function findTyposquatCandidate(\n name: string,\n topPackages: readonly string[],\n): string | undefined {\n if (topPackages.includes(name)) return undefined;\n for (const top of topPackages) {\n if (Math.abs(top.length - name.length) > 2) continue;\n const dist = levenshtein(name, top);\n if (dist > 0 && dist <= 2) return top;\n }\n return undefined;\n}\n","/**\n * Hardcoded top npm packages used for typosquat detection.\n * Updated periodically; sourced from npm download counts.\n */\nexport const TOP_PACKAGES: readonly string[] = [\n \"react\", \"react-dom\", \"next\", \"vue\", \"svelte\", \"angular\", \"lodash\", \"axios\",\n \"express\", \"fastify\", \"koa\", \"hono\", \"moment\", \"date-fns\", \"dayjs\", \"luxon\",\n \"typescript\", \"ts-node\", \"tsx\", \"tsup\", \"esbuild\", \"webpack\", \"vite\", \"rollup\",\n \"parcel\", \"babel\", \"eslint\", \"prettier\", \"jest\", \"vitest\", \"mocha\", \"chai\",\n \"sinon\", \"ava\", \"cypress\", \"playwright\", \"puppeteer\", \"redux\", \"zustand\",\n \"jotai\", \"mobx\", \"recoil\", \"rxjs\", \"graphql\", \"apollo-client\", \"apollo-server\",\n \"prisma\", \"drizzle-orm\", \"kysely\", \"knex\", \"typeorm\", \"sequelize\", \"mongoose\",\n \"pg\", \"mysql2\", \"sqlite3\", \"better-sqlite3\", \"redis\", \"ioredis\", \"bullmq\",\n \"kafkajs\", \"amqplib\", \"socket.io\", \"ws\", \"uws\", \"polka\", \"fastify\", \"h3\",\n \"commander\", \"yargs\", \"minimist\", \"inquirer\", \"prompts\", \"ora\", \"chalk\",\n \"picocolors\", \"kleur\", \"boxen\", \"cli-table3\", \"figlet\", \"fs-extra\", \"fast-glob\",\n \"glob\", \"globby\", \"rimraf\", \"del\", \"execa\", \"shelljs\", \"cross-env\", \"dotenv\",\n \"joi\", \"yup\", \"zod\", \"ajv\", \"valibot\", \"superstruct\", \"io-ts\", \"class-validator\",\n \"passport\", \"jsonwebtoken\", \"jose\", \"bcrypt\", \"bcryptjs\", \"argon2\", \"uuid\",\n \"nanoid\", \"ulid\", \"cuid\", \"shortid\", \"ms\", \"humanize-duration\", \"pluralize\",\n \"marked\", \"remark\", \"rehype\", \"showdown\", \"turndown\", \"cheerio\", \"jsdom\",\n \"playwright-core\", \"node-fetch\", \"got\", \"ky\", \"openai\", \"anthropic\", \"ai\",\n \"@modelcontextprotocol/sdk\", \"langchain\", \"llamaindex\", \"tiktoken\", \"gpt-3-encoder\",\n \"tailwindcss\", \"postcss\", \"autoprefixer\", \"sass\", \"stylus\", \"less\", \"styled-components\",\n \"@emotion/react\", \"@emotion/styled\", \"framer-motion\", \"react-router-dom\",\n \"react-query\", \"@tanstack/react-query\", \"swr\", \"trpc\", \"@trpc/server\",\n \"@trpc/client\", \"winston\", \"pino\", \"bunyan\", \"morgan\", \"debug\", \"sharp\", \"jimp\",\n \"canvas\", \"puppeteer-core\", \"playwright-chromium\", \"exceljs\", \"xlsx\", \"pdfkit\",\n \"pdf-lib\", \"pdfmake\", \"node-cron\", \"agenda\", \"bull\", \"node-schedule\",\n \"@sentry/node\", \"@sentry/browser\", \"@sentry/react\", \"stripe\", \"@stripe/stripe-js\",\n \"twilio\", \"nodemailer\", \"resend\", \"aws-sdk\", \"@aws-sdk/client-s3\", \"@aws-sdk/client-dynamodb\",\n \"googleapis\", \"firebase\", \"firebase-admin\", \"supabase\", \"@supabase/supabase-js\",\n \"mongodb\", \"mongoose\", \"elasticsearch\", \"@elastic/elasticsearch\", \"puppeteer-extra\",\n \"playwright-extra\", \"discord.js\", \"telegraf\", \"node-telegram-bot-api\",\n \"slack-sdk\", \"@slack/web-api\", \"fast-xml-parser\", \"xml2js\", \"yaml\", \"toml\",\n \"ini\", \"msgpack-lite\", \"protobufjs\", \"grpc\", \"@grpc/grpc-js\",\n];\n","import { existsSync, readFileSync, readdirSync, statSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport {\n findTyposquatCandidate,\n maxSeverity,\n scoreInstallScript,\n} from \"./scoring.js\";\nimport { fetchPackument, type RegistryPackument } from \"./registry.js\";\nimport { TOP_PACKAGES } from \"./top-packages.js\";\nimport type {\n Finding,\n PackageRisk,\n RiskLevel,\n ScanOptions,\n ScanResult,\n} from \"./types.js\";\n\ninterface InstalledPackage {\n name: string;\n version: string;\n dir: string;\n pkgJson: {\n name?: string;\n version?: string;\n scripts?: Record<string, string>;\n maintainers?: Array<{ name: string; email?: string }>;\n };\n}\n\nfunction readJsonSafe(file: string): Record<string, unknown> | undefined {\n try {\n return JSON.parse(readFileSync(file, \"utf8\")) as Record<string, unknown>;\n } catch {\n return undefined;\n }\n}\n\nfunction findInstalled(root: string, depth: \"direct\" | \"all\"): InstalledPackage[] {\n const nm = join(root, \"node_modules\");\n if (!existsSync(nm)) return [];\n const out: InstalledPackage[] = [];\n const visited = new Set<string>();\n\n const walk = (dir: string, level: number): void => {\n if (visited.has(dir)) return;\n visited.add(dir);\n if (!existsSync(dir)) return;\n for (const entry of readdirSync(dir)) {\n if (entry.startsWith(\".\")) continue;\n const pkgDir = join(dir, entry);\n try {\n const stat = statSync(pkgDir);\n if (!stat.isDirectory()) continue;\n } catch {\n continue;\n }\n if (entry.startsWith(\"@\")) {\n for (const sub of readdirSync(pkgDir)) {\n handle(join(pkgDir, sub), `${entry}/${sub}`, level);\n }\n } else {\n handle(pkgDir, entry, level);\n }\n }\n };\n\n const handle = (pkgDir: string, name: string, level: number): void => {\n const pj = readJsonSafe(join(pkgDir, \"package.json\"));\n if (!pj) return;\n out.push({\n name: (pj.name as string) ?? name,\n version: (pj.version as string) ?? \"0.0.0\",\n dir: pkgDir,\n pkgJson: pj as InstalledPackage[\"pkgJson\"],\n });\n if (depth === \"all\" && existsSync(join(pkgDir, \"node_modules\"))) {\n walk(join(pkgDir, \"node_modules\"), level + 1);\n }\n };\n\n walk(nm, 0);\n return out;\n}\n\nfunction loadBaseline(): Record<string, { maintainers: string[] }> {\n const file = join(homedir(), \".depguard\", \"baseline.json\");\n if (!existsSync(file)) return {};\n try {\n return JSON.parse(readFileSync(file, \"utf8\")) as Record<string, { maintainers: string[] }>;\n } catch {\n return {};\n }\n}\n\nconst INSTALL_SCRIPT_FIELDS = [\"preinstall\", \"install\", \"postinstall\", \"preuninstall\", \"preprepare\", \"prepare\"] as const;\n\nfunction scriptFindings(pkg: InstalledPackage): Finding[] {\n const findings: Finding[] = [];\n const scripts = pkg.pkgJson.scripts ?? {};\n for (const field of INSTALL_SCRIPT_FIELDS) {\n const src = scripts[field];\n if (!src) continue;\n const { score, reasons } = scoreInstallScript(src);\n if (score === 0) continue;\n const severity: RiskLevel =\n score >= 8 ? \"critical\" : score >= 5 ? \"high\" : score >= 3 ? \"medium\" : \"low\";\n findings.push({\n rule: `install-script:${field}`,\n severity,\n message: `${field} script is suspicious (${reasons.join(\"; \")})`,\n score,\n });\n }\n return findings;\n}\n\nfunction maintainerFindings(\n pkg: InstalledPackage,\n packument: RegistryPackument | undefined,\n baseline: Record<string, { maintainers: string[] }>,\n): Finding[] {\n if (!packument?.maintainers) return [];\n const now = packument.maintainers.map((m) => m.name).sort();\n const known = baseline[pkg.name]?.maintainers;\n if (!known) return [];\n const added = now.filter((m) => !known.includes(m));\n if (added.length === 0) return [];\n return [{\n rule: \"maintainer-change\",\n severity: \"high\",\n message: `New maintainer(s) added since baseline: ${added.join(\", \")}`,\n score: 6,\n }];\n}\n\nfunction versionAnomalyFindings(\n pkg: InstalledPackage,\n packument: RegistryPackument | undefined,\n): Finding[] {\n if (!packument?.time) return [];\n const out: Finding[] = [];\n const time = packument.time;\n const versions = Object.keys(time).filter((k) => k !== \"created\" && k !== \"modified\");\n\n const sevenDays = 7 * 24 * 60 * 60 * 1000;\n const major = new Map<number, string[]>();\n for (const v of versions) {\n const m = /^(\\d+)\\./.exec(v);\n if (!m) continue;\n const key = Number(m[1]);\n const arr = major.get(key) ?? [];\n arr.push(v);\n major.set(key, arr);\n }\n const keys = [...major.keys()].sort((a, b) => a - b);\n for (let i = 1; i < keys.length; i++) {\n const prev = keys[i - 1]!;\n const cur = keys[i]!;\n const prevReleases = major.get(prev)!.map((v) => Date.parse(time[v] ?? \"\")).filter(Number.isFinite);\n const curReleases = major.get(cur)!.map((v) => Date.parse(time[v] ?? \"\")).filter(Number.isFinite);\n if (prevReleases.length === 0 || curReleases.length === 0) continue;\n const lastPrev = Math.max(...prevReleases);\n const firstCur = Math.min(...curReleases);\n if (firstCur - lastPrev < sevenDays) {\n out.push({\n rule: \"version-anomaly\",\n severity: \"medium\",\n message: `Major bump v${prev} → v${cur} within 7 days (potential takeover)`,\n score: 4,\n });\n }\n }\n\n const created = Date.parse(time.created ?? \"\");\n if (Number.isFinite(created) && Date.now() - created < 30 * 24 * 60 * 60 * 1000) {\n out.push({\n rule: \"young-package\",\n severity: \"low\",\n message: \"Package was first published less than 30 days ago\",\n score: 2,\n });\n }\n return out;\n}\n\nfunction typosquatFindings(name: string): Finding[] {\n const sug = findTyposquatCandidate(name, TOP_PACKAGES);\n if (!sug) return [];\n return [{\n rule: \"typosquat\",\n severity: \"critical\",\n message: `Name closely resembles \"${sug}\" — likely typosquat`,\n score: 10,\n }];\n}\n\nexport async function scan(options: ScanOptions = {}): Promise<ScanResult> {\n const cwd = options.cwd ?? process.cwd();\n const depth = options.scanDepth ?? \"all\";\n const ignore = new Set(options.ignore ?? []);\n const baseline = loadBaseline();\n const startedAt = new Date().toISOString();\n const packages: PackageRisk[] = [];\n\n const installed = findInstalled(cwd, depth).filter((p) => !ignore.has(p.name));\n for (const pkg of installed) {\n const findings: Finding[] = [];\n findings.push(...scriptFindings(pkg));\n findings.push(...typosquatFindings(pkg.name));\n\n if (options.network !== false) {\n const packument = await fetchPackument(pkg.name, { ttlMs: options.cacheTTL ?? 60 * 60 * 1000 });\n findings.push(...maintainerFindings(pkg, packument, baseline));\n findings.push(...versionAnomalyFindings(pkg, packument));\n }\n\n if (findings.length === 0) continue;\n packages.push({\n name: pkg.name,\n version: pkg.version,\n worstSeverity: maxSeverity(findings),\n totalScore: findings.reduce((s, f) => s + f.score, 0),\n findings,\n });\n }\n\n packages.sort((a, b) => b.totalScore - a.totalScore);\n\n return {\n packages,\n scannedAt: startedAt,\n generatedAt: new Date().toISOString(),\n totalPackages: installed.length,\n };\n}\n\nexport async function audit(name: string): Promise<PackageRisk | undefined> {\n const packument = await fetchPackument(name);\n if (!packument) return undefined;\n const findings: Finding[] = [];\n findings.push(...typosquatFindings(name));\n return {\n name,\n version: packument[\"dist-tags\"]?.latest ?? \"0.0.0\",\n worstSeverity: maxSeverity(findings),\n totalScore: findings.reduce((s, f) => s + f.score, 0),\n findings,\n };\n}\n","import { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\nexport interface RegistryPackument {\n name: string;\n maintainers?: Array<{ name: string; email?: string }>;\n time?: Record<string, string>;\n versions?: Record<string, unknown>;\n \"dist-tags\"?: Record<string, string>;\n}\n\ninterface CacheEntry {\n expiresAt: number;\n data: RegistryPackument;\n}\n\nconst CACHE_DIR = join(homedir(), \".depguard\", \"cache\");\n\nfunction cacheFile(name: string): string {\n const safe = name.replace(/[^a-zA-Z0-9._-]/g, \"_\");\n return join(CACHE_DIR, `${safe}.json`);\n}\n\nfunction readCache(name: string): RegistryPackument | undefined {\n const file = cacheFile(name);\n if (!existsSync(file)) return undefined;\n try {\n const entry = JSON.parse(readFileSync(file, \"utf8\")) as CacheEntry;\n if (entry.expiresAt > Date.now()) return entry.data;\n } catch {\n return undefined;\n }\n return undefined;\n}\n\nfunction writeCache(name: string, data: RegistryPackument, ttlMs: number): void {\n if (!existsSync(CACHE_DIR)) mkdirSync(CACHE_DIR, { recursive: true });\n const entry: CacheEntry = { expiresAt: Date.now() + ttlMs, data };\n writeFileSync(cacheFile(name), JSON.stringify(entry), \"utf8\");\n}\n\nexport interface FetchOptions {\n ttlMs?: number;\n fetcher?: (url: string) => Promise<{ ok: boolean; status: number; json(): Promise<unknown> }>;\n}\n\nexport async function fetchPackument(\n name: string,\n opts: FetchOptions = {},\n): Promise<RegistryPackument | undefined> {\n const ttl = opts.ttlMs ?? 60 * 60 * 1000;\n const cached = readCache(name);\n if (cached) return cached;\n const fetcher = opts.fetcher ?? (globalThis.fetch as typeof fetch);\n if (!fetcher) return undefined;\n try {\n const res = await fetcher(`https://registry.npmjs.org/${encodeURIComponent(name).replace(\"%40\", \"@\")}`);\n if (!res.ok) return undefined;\n const data = (await res.json()) as RegistryPackument;\n writeCache(name, data, ttl);\n return data;\n } catch {\n return undefined;\n }\n}\n","import pc from \"picocolors\";\nimport type { PackageRisk, RiskLevel, ScanResult } from \"./types.js\";\n\nconst sevColor: Record<RiskLevel, (s: string) => string> = {\n info: pc.gray,\n low: pc.blue,\n medium: pc.yellow,\n high: pc.magenta,\n critical: pc.red,\n};\n\nexport function formatReport(result: ScanResult): string {\n if (result.packages.length === 0) {\n return pc.green(`✓ No risk findings across ${result.totalPackages} packages`);\n }\n const lines: string[] = [];\n lines.push(pc.bold(`depguard report — ${result.packages.length} package(s) with findings (of ${result.totalPackages} scanned)`));\n for (const p of result.packages) {\n lines.push(\"\");\n lines.push(` ${sevColor[p.worstSeverity](p.worstSeverity.toUpperCase())} ${pc.bold(p.name)}@${p.version} score=${p.totalScore}`);\n for (const f of p.findings) {\n lines.push(` - ${sevColor[f.severity](f.severity)} ${f.rule}: ${f.message}`);\n }\n }\n return lines.join(\"\\n\");\n}\n\nexport function formatJson(result: ScanResult): string {\n return JSON.stringify(result, null, 2);\n}\n\nexport function formatPackage(p: PackageRisk): string {\n const lines: string[] = [];\n lines.push(`${sevColor[p.worstSeverity](p.worstSeverity.toUpperCase())} ${pc.bold(p.name)}@${p.version} score=${p.totalScore}`);\n for (const f of p.findings) {\n lines.push(` - ${sevColor[f.severity](f.severity)} ${f.rule}: ${f.message}`);\n }\n return lines.join(\"\\n\");\n}\n","import { existsSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { RiskLevel } from \"./types.js\";\n\nexport interface DepguardConfig {\n failOn?: RiskLevel;\n ignore?: string[];\n baselinePath?: string;\n cacheTTL?: number;\n scanDepth?: \"direct\" | \"all\";\n}\n\nexport function loadConfig(cwd: string): DepguardConfig {\n const file = join(cwd, \".depguardrc.json\");\n if (!existsSync(file)) return {};\n try {\n return JSON.parse(readFileSync(file, \"utf8\")) as DepguardConfig;\n } catch {\n return {};\n }\n}\n"],"mappings":"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","names":["existsSync","readFileSync","join","homedir","readFileSync","join","existsSync","homedir","existsSync","readFileSync","join"]}
|