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