trawly 0.0.1 → 0.0.2
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/dist/cli.js +301 -19
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +12 -1
- package/dist/index.js +287 -9
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/scanner.ts
|
|
2
|
-
import { existsSync, statSync } from "fs";
|
|
3
|
-
import { resolve as
|
|
2
|
+
import { existsSync as existsSync2, statSync } from "fs";
|
|
3
|
+
import { basename, resolve as resolve4, join as join2 } from "path";
|
|
4
4
|
|
|
5
5
|
// src/extractors/npm-package-lock.ts
|
|
6
6
|
import { readFileSync } from "fs";
|
|
@@ -86,6 +86,262 @@ function isTopLevelInstance(path) {
|
|
|
86
86
|
return path.indexOf("node_modules/", first + 1) === -1;
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
// src/extractors/pnpm-lock.ts
|
|
90
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
91
|
+
import { resolve as resolve2 } from "path";
|
|
92
|
+
import yaml from "js-yaml";
|
|
93
|
+
var SUPPORTED_MAJOR_VERSIONS = /* @__PURE__ */ new Set([6, 9]);
|
|
94
|
+
function parsePnpmLock(filePath) {
|
|
95
|
+
const absolute = resolve2(filePath);
|
|
96
|
+
const raw = readFileSync2(absolute, "utf8");
|
|
97
|
+
let parsed;
|
|
98
|
+
try {
|
|
99
|
+
parsed = yaml.load(raw) ?? {};
|
|
100
|
+
} catch (err) {
|
|
101
|
+
throw new Error(
|
|
102
|
+
`Failed to parse ${absolute}: ${err.message}`
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
const versionRaw = parsed.lockfileVersion;
|
|
106
|
+
const major = parseLockfileMajor(versionRaw);
|
|
107
|
+
if (major === null || !SUPPORTED_MAJOR_VERSIONS.has(major)) {
|
|
108
|
+
throw new Error(
|
|
109
|
+
`Unsupported pnpm lockfileVersion ${String(versionRaw)} in ${absolute}. Supported: 6.x, 9.x.`
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
const packages = parsed.packages;
|
|
113
|
+
if (!packages || typeof packages !== "object") {
|
|
114
|
+
throw new Error(
|
|
115
|
+
`Lockfile ${absolute} has no "packages" map; cannot extract installed versions.`
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
const importers = parsed.importers ?? {
|
|
119
|
+
".": {
|
|
120
|
+
dependencies: parsed.dependencies,
|
|
121
|
+
devDependencies: parsed.devDependencies,
|
|
122
|
+
optionalDependencies: parsed.optionalDependencies
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
const direct = collectDirectFromImporters(importers);
|
|
126
|
+
const instances = [];
|
|
127
|
+
for (const [key, entry] of Object.entries(packages)) {
|
|
128
|
+
const parsed2 = parsePnpmPackageKey(key);
|
|
129
|
+
if (!parsed2) continue;
|
|
130
|
+
const name = entry.name ?? parsed2.name;
|
|
131
|
+
const version = entry.version ?? parsed2.version;
|
|
132
|
+
if (!name || !version) continue;
|
|
133
|
+
const isDirect = direct.prod.has(name) || direct.dev.has(name) || direct.optional.has(name);
|
|
134
|
+
const onlyDev = direct.dev.has(name) && !direct.prod.has(name);
|
|
135
|
+
const onlyOptional = direct.optional.has(name) && !direct.prod.has(name) && !direct.dev.has(name);
|
|
136
|
+
instances.push({
|
|
137
|
+
name,
|
|
138
|
+
version,
|
|
139
|
+
ecosystem: "npm",
|
|
140
|
+
path: key,
|
|
141
|
+
direct: isDirect,
|
|
142
|
+
dev: Boolean(entry.dev) || onlyDev,
|
|
143
|
+
optional: Boolean(entry.optional) || onlyOptional,
|
|
144
|
+
resolved: entry.resolution?.tarball,
|
|
145
|
+
integrity: entry.resolution?.integrity
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
return instances;
|
|
149
|
+
}
|
|
150
|
+
function parseLockfileMajor(value) {
|
|
151
|
+
if (typeof value === "number") return Math.trunc(value);
|
|
152
|
+
if (typeof value === "string") {
|
|
153
|
+
const num = parseInt(value, 10);
|
|
154
|
+
return Number.isNaN(num) ? null : num;
|
|
155
|
+
}
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
function collectDirectFromImporters(importers) {
|
|
159
|
+
const prod = /* @__PURE__ */ new Set();
|
|
160
|
+
const dev = /* @__PURE__ */ new Set();
|
|
161
|
+
const optional = /* @__PURE__ */ new Set();
|
|
162
|
+
for (const importer of Object.values(importers)) {
|
|
163
|
+
if (!importer) continue;
|
|
164
|
+
addDepNames(importer.dependencies, prod);
|
|
165
|
+
addDepNames(importer.devDependencies, dev);
|
|
166
|
+
addDepNames(importer.optionalDependencies, optional);
|
|
167
|
+
}
|
|
168
|
+
return { prod, dev, optional };
|
|
169
|
+
}
|
|
170
|
+
function addDepNames(block, into) {
|
|
171
|
+
if (!block) return;
|
|
172
|
+
for (const name of Object.keys(block)) into.add(name);
|
|
173
|
+
}
|
|
174
|
+
function parsePnpmPackageKey(key) {
|
|
175
|
+
let working = key.startsWith("/") ? key.slice(1) : key;
|
|
176
|
+
const parenIdx = working.indexOf("(");
|
|
177
|
+
if (parenIdx !== -1) working = working.slice(0, parenIdx);
|
|
178
|
+
const startSearch = working.startsWith("@") ? 1 : 0;
|
|
179
|
+
const atIdx = working.indexOf("@", startSearch);
|
|
180
|
+
if (atIdx <= 0) return null;
|
|
181
|
+
const name = working.slice(0, atIdx);
|
|
182
|
+
const version = working.slice(atIdx + 1);
|
|
183
|
+
if (!name || !version) return null;
|
|
184
|
+
return { name, version };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// src/extractors/yarn-lock.ts
|
|
188
|
+
import { existsSync, readFileSync as readFileSync3 } from "fs";
|
|
189
|
+
import { dirname, join, resolve as resolve3 } from "path";
|
|
190
|
+
import yaml2 from "js-yaml";
|
|
191
|
+
function parseYarnLock(filePath) {
|
|
192
|
+
const absolute = resolve3(filePath);
|
|
193
|
+
const content = readFileSync3(absolute, "utf8");
|
|
194
|
+
const projectDir = dirname(absolute);
|
|
195
|
+
const directs = readDirectDepsFromPackageJson(projectDir);
|
|
196
|
+
const isBerry = /^__metadata:/m.test(content);
|
|
197
|
+
return isBerry ? parseBerry(absolute, content, directs) : parseClassic(absolute, content, directs);
|
|
198
|
+
}
|
|
199
|
+
function readDirectDepsFromPackageJson(projectDir) {
|
|
200
|
+
const result = {
|
|
201
|
+
prod: /* @__PURE__ */ new Set(),
|
|
202
|
+
dev: /* @__PURE__ */ new Set(),
|
|
203
|
+
optional: /* @__PURE__ */ new Set(),
|
|
204
|
+
any: /* @__PURE__ */ new Set()
|
|
205
|
+
};
|
|
206
|
+
const pkgPath = join(projectDir, "package.json");
|
|
207
|
+
if (!existsSync(pkgPath)) return result;
|
|
208
|
+
try {
|
|
209
|
+
const pkg = JSON.parse(readFileSync3(pkgPath, "utf8"));
|
|
210
|
+
for (const name of Object.keys(pkg.dependencies ?? {})) result.prod.add(name);
|
|
211
|
+
for (const name of Object.keys(pkg.devDependencies ?? {})) result.dev.add(name);
|
|
212
|
+
for (const name of Object.keys(pkg.optionalDependencies ?? {}))
|
|
213
|
+
result.optional.add(name);
|
|
214
|
+
for (const set of [result.prod, result.dev, result.optional]) {
|
|
215
|
+
for (const n of set) result.any.add(n);
|
|
216
|
+
}
|
|
217
|
+
for (const name of Object.keys(pkg.peerDependencies ?? {})) result.any.add(name);
|
|
218
|
+
} catch {
|
|
219
|
+
}
|
|
220
|
+
return result;
|
|
221
|
+
}
|
|
222
|
+
function parseClassic(absolute, content, directs) {
|
|
223
|
+
const entries = parseClassicEntries(content);
|
|
224
|
+
const instances = [];
|
|
225
|
+
for (const entry of entries) {
|
|
226
|
+
const version = entry.fields.version;
|
|
227
|
+
if (!version) continue;
|
|
228
|
+
const names = uniq(entry.specs.map((s) => parseYarnSpec(s).name).filter(Boolean));
|
|
229
|
+
const name = names[0];
|
|
230
|
+
if (!name) continue;
|
|
231
|
+
const isDirect = names.some((n) => directs.any.has(n));
|
|
232
|
+
const inProd = names.some((n) => directs.prod.has(n));
|
|
233
|
+
const inDev = names.some((n) => directs.dev.has(n));
|
|
234
|
+
const inOpt = names.some((n) => directs.optional.has(n));
|
|
235
|
+
instances.push({
|
|
236
|
+
name,
|
|
237
|
+
version,
|
|
238
|
+
ecosystem: "npm",
|
|
239
|
+
path: `${name}@${version}`,
|
|
240
|
+
direct: isDirect,
|
|
241
|
+
dev: inDev && !inProd,
|
|
242
|
+
optional: inOpt && !inProd && !inDev,
|
|
243
|
+
resolved: entry.fields.resolved,
|
|
244
|
+
integrity: entry.fields.integrity
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
void absolute;
|
|
248
|
+
return instances;
|
|
249
|
+
}
|
|
250
|
+
function parseClassicEntries(content) {
|
|
251
|
+
const lines = content.split(/\r?\n/);
|
|
252
|
+
const entries = [];
|
|
253
|
+
let current = null;
|
|
254
|
+
for (const rawLine of lines) {
|
|
255
|
+
if (rawLine === "" || rawLine.trimStart().startsWith("#")) continue;
|
|
256
|
+
if (!/^\s/.test(rawLine)) {
|
|
257
|
+
if (current) entries.push(current);
|
|
258
|
+
const header = rawLine.replace(/:\s*$/, "");
|
|
259
|
+
current = { specs: splitClassicSpecs(header), fields: {} };
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
if (!current) continue;
|
|
263
|
+
const indent = rawLine.match(/^ +/)?.[0].length ?? 0;
|
|
264
|
+
if (indent !== 2) continue;
|
|
265
|
+
const trimmed = rawLine.trim();
|
|
266
|
+
const m = trimmed.match(/^([^\s"]+)\s+"((?:[^"\\]|\\.)*)"$/) ?? trimmed.match(/^([^\s"]+)\s+(\S+)$/);
|
|
267
|
+
if (m && m[1] !== void 0 && m[2] !== void 0) {
|
|
268
|
+
current.fields[m[1]] = m[2];
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
if (current) entries.push(current);
|
|
272
|
+
return entries;
|
|
273
|
+
}
|
|
274
|
+
function splitClassicSpecs(header) {
|
|
275
|
+
const out = [];
|
|
276
|
+
let cur = "";
|
|
277
|
+
let inQuote = false;
|
|
278
|
+
for (const ch of header) {
|
|
279
|
+
if (ch === '"') {
|
|
280
|
+
inQuote = !inQuote;
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
if (ch === "," && !inQuote) {
|
|
284
|
+
const spec = cur.trim();
|
|
285
|
+
if (spec) out.push(spec);
|
|
286
|
+
cur = "";
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
cur += ch;
|
|
290
|
+
}
|
|
291
|
+
const last = cur.trim();
|
|
292
|
+
if (last) out.push(last);
|
|
293
|
+
return out;
|
|
294
|
+
}
|
|
295
|
+
function parseBerry(absolute, content, directs) {
|
|
296
|
+
let parsed;
|
|
297
|
+
try {
|
|
298
|
+
parsed = yaml2.load(content) ?? {};
|
|
299
|
+
} catch (err) {
|
|
300
|
+
throw new Error(
|
|
301
|
+
`Failed to parse ${absolute}: ${err.message}`
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
const instances = [];
|
|
305
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
306
|
+
if (key === "__metadata") continue;
|
|
307
|
+
if (!value || typeof value !== "object") continue;
|
|
308
|
+
const entry = value;
|
|
309
|
+
if (!entry.version) continue;
|
|
310
|
+
const specs = splitClassicSpecs(key);
|
|
311
|
+
const names = uniq(specs.map((s) => parseYarnSpec(s).name).filter(Boolean));
|
|
312
|
+
const name = names[0];
|
|
313
|
+
if (!name) continue;
|
|
314
|
+
const isDirect = names.some((n) => directs.any.has(n));
|
|
315
|
+
const inProd = names.some((n) => directs.prod.has(n));
|
|
316
|
+
const inDev = names.some((n) => directs.dev.has(n));
|
|
317
|
+
const inOpt = names.some((n) => directs.optional.has(n));
|
|
318
|
+
instances.push({
|
|
319
|
+
name,
|
|
320
|
+
version: entry.version,
|
|
321
|
+
ecosystem: "npm",
|
|
322
|
+
path: `${name}@${entry.version}`,
|
|
323
|
+
direct: isDirect,
|
|
324
|
+
dev: inDev && !inProd,
|
|
325
|
+
optional: inOpt && !inProd && !inDev,
|
|
326
|
+
resolved: entry.resolution,
|
|
327
|
+
integrity: entry.checksum
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
return instances;
|
|
331
|
+
}
|
|
332
|
+
function parseYarnSpec(spec) {
|
|
333
|
+
const startSearch = spec.startsWith("@") ? 1 : 0;
|
|
334
|
+
const atIdx = spec.indexOf("@", startSearch);
|
|
335
|
+
if (atIdx <= 0) return { name: spec, selector: "" };
|
|
336
|
+
const name = spec.slice(0, atIdx);
|
|
337
|
+
let selector = spec.slice(atIdx + 1);
|
|
338
|
+
if (selector.startsWith("npm:")) selector = selector.slice(4);
|
|
339
|
+
return { name, selector };
|
|
340
|
+
}
|
|
341
|
+
function uniq(values) {
|
|
342
|
+
return Array.from(new Set(values));
|
|
343
|
+
}
|
|
344
|
+
|
|
89
345
|
// src/sources/osv.ts
|
|
90
346
|
var OSV_QUERYBATCH_URL = "https://api.osv.dev/v1/querybatch";
|
|
91
347
|
var OSV_VULN_URL = "https://api.osv.dev/v1/vulns";
|
|
@@ -292,11 +548,11 @@ var SEVERITY_RANK = {
|
|
|
292
548
|
|
|
293
549
|
// src/scanner.ts
|
|
294
550
|
async function scanProject(options = {}) {
|
|
295
|
-
const cwd =
|
|
296
|
-
const lockfilePath = options.lockfile ?
|
|
551
|
+
const cwd = resolve4(options.cwd ?? process.cwd());
|
|
552
|
+
const lockfilePath = options.lockfile ? resolve4(cwd, options.lockfile) : detectLockfile(cwd);
|
|
297
553
|
if (!lockfilePath) {
|
|
298
554
|
throw new ScanInputError(
|
|
299
|
-
`No
|
|
555
|
+
`No supported lockfile found in ${cwd}. Pass --lockfile or run in a directory with package-lock.json, pnpm-lock.yaml, or yarn.lock.`
|
|
300
556
|
);
|
|
301
557
|
}
|
|
302
558
|
return scanLockfile({
|
|
@@ -308,14 +564,14 @@ async function scanProject(options = {}) {
|
|
|
308
564
|
}
|
|
309
565
|
async function scanLockfile(options) {
|
|
310
566
|
const { lockfilePath } = options;
|
|
311
|
-
if (!
|
|
567
|
+
if (!existsSync2(lockfilePath)) {
|
|
312
568
|
throw new ScanInputError(`Lockfile does not exist: ${lockfilePath}`);
|
|
313
569
|
}
|
|
314
570
|
const stat = statSync(lockfilePath);
|
|
315
571
|
if (!stat.isFile()) {
|
|
316
572
|
throw new ScanInputError(`Lockfile path is not a file: ${lockfilePath}`);
|
|
317
573
|
}
|
|
318
|
-
const allInstances =
|
|
574
|
+
const allInstances = parseLockfile(lockfilePath);
|
|
319
575
|
const instances = filterInstances(allInstances, options);
|
|
320
576
|
const errors = [];
|
|
321
577
|
let findings = [];
|
|
@@ -342,9 +598,29 @@ var ScanInputError = class extends Error {
|
|
|
342
598
|
this.name = "ScanInputError";
|
|
343
599
|
}
|
|
344
600
|
};
|
|
601
|
+
var LOCKFILE_CANDIDATES = [
|
|
602
|
+
"pnpm-lock.yaml",
|
|
603
|
+
"yarn.lock",
|
|
604
|
+
"package-lock.json",
|
|
605
|
+
"npm-shrinkwrap.json"
|
|
606
|
+
];
|
|
345
607
|
function detectLockfile(cwd) {
|
|
346
|
-
const
|
|
347
|
-
|
|
608
|
+
for (const file of LOCKFILE_CANDIDATES) {
|
|
609
|
+
const candidate = join2(cwd, file);
|
|
610
|
+
if (existsSync2(candidate)) return candidate;
|
|
611
|
+
}
|
|
612
|
+
return void 0;
|
|
613
|
+
}
|
|
614
|
+
function parseLockfile(lockfilePath) {
|
|
615
|
+
const name = basename(lockfilePath);
|
|
616
|
+
if (name === "pnpm-lock.yaml") return parsePnpmLock(lockfilePath);
|
|
617
|
+
if (name === "yarn.lock") return parseYarnLock(lockfilePath);
|
|
618
|
+
if (name === "package-lock.json" || name === "npm-shrinkwrap.json") {
|
|
619
|
+
return parseNpmPackageLock(lockfilePath);
|
|
620
|
+
}
|
|
621
|
+
throw new ScanInputError(
|
|
622
|
+
`Unsupported lockfile name: ${name}. Supported: package-lock.json, npm-shrinkwrap.json, pnpm-lock.yaml, yarn.lock.`
|
|
623
|
+
);
|
|
348
624
|
}
|
|
349
625
|
function filterInstances(instances, options) {
|
|
350
626
|
const includeDev = options.prodOnly ? false : options.includeDev !== false;
|
|
@@ -385,6 +661,8 @@ export {
|
|
|
385
661
|
dedupeForQuery,
|
|
386
662
|
meetsThreshold,
|
|
387
663
|
parseNpmPackageLock,
|
|
664
|
+
parsePnpmLock,
|
|
665
|
+
parseYarnLock,
|
|
388
666
|
queryOsv,
|
|
389
667
|
scanLockfile,
|
|
390
668
|
scanProject,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/scanner.ts","../src/extractors/npm-package-lock.ts","../src/sources/osv.ts","../src/types.ts"],"sourcesContent":["import { existsSync, statSync } from \"node:fs\";\nimport { resolve, join } from \"node:path\";\nimport { parseNpmPackageLock } from \"./extractors/npm-package-lock.js\";\nimport { queryOsv } from \"./sources/osv.js\";\nimport { SEVERITY_RANK } from \"./types.js\";\nimport type {\n Finding,\n PackageInstance,\n ScanError,\n ScanLockfileOptions,\n ScanProjectOptions,\n ScanResult,\n Severity,\n} from \"./types.js\";\n\nexport async function scanProject(\n options: ScanProjectOptions = {},\n): Promise<ScanResult> {\n const cwd = resolve(options.cwd ?? process.cwd());\n const lockfilePath = options.lockfile\n ? resolve(cwd, options.lockfile)\n : detectLockfile(cwd);\n\n if (!lockfilePath) {\n throw new ScanInputError(\n `No npm lockfile found in ${cwd}. Pass --lockfile or run in a directory with package-lock.json.`,\n );\n }\n\n return scanLockfile({\n lockfilePath,\n includeDev: options.includeDev,\n prodOnly: options.prodOnly,\n fetchImpl: options.fetchImpl,\n });\n}\n\nexport async function scanLockfile(\n options: ScanLockfileOptions,\n): Promise<ScanResult> {\n const { lockfilePath } = options;\n if (!existsSync(lockfilePath)) {\n throw new ScanInputError(`Lockfile does not exist: ${lockfilePath}`);\n }\n const stat = statSync(lockfilePath);\n if (!stat.isFile()) {\n throw new ScanInputError(`Lockfile path is not a file: ${lockfilePath}`);\n }\n\n const allInstances = parseNpmPackageLock(lockfilePath);\n const instances = filterInstances(allInstances, options);\n const errors: ScanError[] = [];\n\n let findings: Finding[] = [];\n try {\n findings = await queryOsv(instances, { fetchImpl: options.fetchImpl });\n } catch (err) {\n errors.push({\n message: \"Failed to query OSV advisory database\",\n cause: (err as Error).message,\n });\n }\n\n findings.sort(compareFindings);\n\n return {\n scannedAt: new Date().toISOString(),\n packagesScanned: instances.length,\n findings,\n summary: summarize(findings),\n errors,\n };\n}\n\nexport class ScanInputError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"ScanInputError\";\n }\n}\n\nfunction detectLockfile(cwd: string): string | undefined {\n const candidate = join(cwd, \"package-lock.json\");\n return existsSync(candidate) ? candidate : undefined;\n}\n\nfunction filterInstances(\n instances: PackageInstance[],\n options: { includeDev?: boolean; prodOnly?: boolean },\n): PackageInstance[] {\n const includeDev = options.prodOnly ? false : options.includeDev !== false;\n if (includeDev) return instances;\n return instances.filter((p) => !p.dev);\n}\n\nexport function compareFindings(a: Finding, b: Finding): number {\n const sev = SEVERITY_RANK[b.severity] - SEVERITY_RANK[a.severity];\n if (sev !== 0) return sev;\n if (a.packageName !== b.packageName) {\n return a.packageName.localeCompare(b.packageName);\n }\n if (a.installedVersion !== b.installedVersion) {\n return a.installedVersion.localeCompare(b.installedVersion);\n }\n return a.id.localeCompare(b.id);\n}\n\nexport function summarize(findings: Finding[]): Record<Severity, number> {\n const summary: Record<Severity, number> = {\n critical: 0,\n high: 0,\n moderate: 0,\n low: 0,\n unknown: 0,\n };\n for (const f of findings) summary[f.severity]++;\n return summary;\n}\n\n/**\n * Returns true when any finding meets or exceeds the given severity threshold.\n * \"none\" means never fail.\n */\nexport function meetsThreshold(\n findings: Finding[],\n threshold: Severity | \"none\",\n): boolean {\n if (threshold === \"none\") return false;\n const min = SEVERITY_RANK[threshold];\n return findings.some((f) => SEVERITY_RANK[f.severity] >= min);\n}\n","import { readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport type { PackageInstance } from \"../types.js\";\n\ninterface NpmLockEntry {\n version?: string;\n resolved?: string;\n integrity?: string;\n dev?: boolean;\n devOptional?: boolean;\n optional?: boolean;\n peer?: boolean;\n link?: boolean;\n // Present on the root (\"\") entry only.\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n optionalDependencies?: Record<string, string>;\n peerDependencies?: Record<string, string>;\n}\n\ninterface NpmLockfile {\n name?: string;\n lockfileVersion?: number;\n packages?: Record<string, NpmLockEntry>;\n}\n\n/**\n * Parse an npm `package-lock.json` (v2 or v3) and return one\n * PackageInstance per node in the `packages` map.\n *\n * The empty-string key represents the root project and is skipped.\n */\nexport function parseNpmPackageLock(filePath: string): PackageInstance[] {\n const absolute = resolve(filePath);\n const raw = readFileSync(absolute, \"utf8\");\n let parsed: NpmLockfile;\n try {\n parsed = JSON.parse(raw) as NpmLockfile;\n } catch (err) {\n throw new Error(\n `Failed to parse ${absolute}: ${(err as Error).message}`,\n );\n }\n\n if (parsed.lockfileVersion !== 2 && parsed.lockfileVersion !== 3) {\n throw new Error(\n `Unsupported npm lockfileVersion ${String(\n parsed.lockfileVersion,\n )} in ${absolute}. Only v2 and v3 are supported.`,\n );\n }\n\n const packages = parsed.packages;\n if (!packages || typeof packages !== \"object\") {\n throw new Error(\n `Lockfile ${absolute} has no \"packages\" map; cannot extract installed versions.`,\n );\n }\n\n const directDeps = collectDirectDependencyNames(packages[\"\"] ?? {});\n const instances: PackageInstance[] = [];\n\n for (const [path, entry] of Object.entries(packages)) {\n if (path === \"\") continue;\n if (entry.link) continue; // workspace symlink, not a real install\n const name = packagePathToName(path);\n if (!name) continue;\n if (!entry.version) continue;\n\n instances.push({\n name,\n version: entry.version,\n ecosystem: \"npm\",\n path,\n direct: directDeps.has(name) && isTopLevelInstance(path),\n dev: Boolean(entry.dev || entry.devOptional),\n optional: Boolean(entry.optional || entry.devOptional),\n resolved: entry.resolved,\n integrity: entry.integrity,\n });\n }\n\n return instances;\n}\n\nfunction collectDirectDependencyNames(rootEntry: NpmLockEntry): Set<string> {\n const names = new Set<string>();\n for (const key of [\n \"dependencies\",\n \"devDependencies\",\n \"optionalDependencies\",\n \"peerDependencies\",\n ] as const) {\n const block = rootEntry[key];\n if (!block) continue;\n for (const name of Object.keys(block)) names.add(name);\n }\n return names;\n}\n\n/**\n * \"node_modules/foo\" -> \"foo\"\n * \"node_modules/@scope/bar\" -> \"@scope/bar\"\n * \"node_modules/foo/node_modules/bar\" -> \"bar\"\n */\nexport function packagePathToName(path: string): string | null {\n const marker = \"node_modules/\";\n const idx = path.lastIndexOf(marker);\n if (idx === -1) return null;\n const tail = path.slice(idx + marker.length);\n if (!tail) return null;\n if (tail.startsWith(\"@\")) {\n const firstSlash = tail.indexOf(\"/\");\n if (firstSlash === -1) return null;\n const secondSlash = tail.indexOf(\"/\", firstSlash + 1);\n return secondSlash === -1 ? tail : tail.slice(0, secondSlash);\n }\n const next = tail.indexOf(\"/\");\n return next === -1 ? tail : tail.slice(0, next);\n}\n\n/** A direct-install path has exactly one `node_modules/` segment. */\nfunction isTopLevelInstance(path: string): boolean {\n const first = path.indexOf(\"node_modules/\");\n if (first === -1) return false;\n return path.indexOf(\"node_modules/\", first + 1) === -1;\n}\n","import type { Finding, PackageInstance, Severity } from \"../types.js\";\n\nconst OSV_QUERYBATCH_URL = \"https://api.osv.dev/v1/querybatch\";\nconst OSV_VULN_URL = \"https://api.osv.dev/v1/vulns\";\nconst QUERY_CHUNK_SIZE = 500;\nconst REQUEST_TIMEOUT_MS = 15_000;\nconst MAX_RETRIES = 2;\n\ninterface OsvQueryBatchResponse {\n results: Array<{ vulns?: Array<{ id: string; modified?: string }> }>;\n}\n\ninterface OsvSeverity {\n type: string;\n score: string;\n}\n\ninterface OsvAffectedRange {\n type: string;\n events: Array<{ introduced?: string; fixed?: string; last_affected?: string }>;\n}\n\ninterface OsvAffectedPackage {\n package?: { ecosystem?: string; name?: string };\n ranges?: OsvAffectedRange[];\n versions?: string[];\n}\n\ninterface OsvVulnDetail {\n id: string;\n summary?: string;\n details?: string;\n references?: Array<{ type?: string; url?: string }>;\n severity?: OsvSeverity[];\n database_specific?: { severity?: string };\n affected?: OsvAffectedPackage[];\n}\n\nexport interface OsvQueryDeps {\n fetchImpl?: typeof fetch;\n}\n\ninterface UniquePackage {\n name: string;\n version: string;\n}\n\n/**\n * Build the deduplicated list of unique name@version pairs to query OSV with.\n */\nexport function dedupeForQuery(\n packages: PackageInstance[],\n): UniquePackage[] {\n const seen = new Set<string>();\n const out: UniquePackage[] = [];\n for (const pkg of packages) {\n const key = `${pkg.name}@${pkg.version}`;\n if (seen.has(key)) continue;\n seen.add(key);\n out.push({ name: pkg.name, version: pkg.version });\n }\n return out;\n}\n\n/**\n * Query OSV for the given installed packages and return one Finding per\n * (advisory, affected package instance) pair.\n */\nexport async function queryOsv(\n packages: PackageInstance[],\n deps: OsvQueryDeps = {},\n): Promise<Finding[]> {\n const fetchImpl = deps.fetchImpl ?? fetch;\n const unique = dedupeForQuery(packages);\n if (unique.length === 0) return [];\n\n const idsByPackage = new Map<string, Set<string>>();\n for (const chunk of chunked(unique, QUERY_CHUNK_SIZE)) {\n const body = {\n queries: chunk.map((q) => ({\n package: { ecosystem: \"npm\", name: q.name },\n version: q.version,\n })),\n };\n const res = await postJson<OsvQueryBatchResponse>(\n fetchImpl,\n OSV_QUERYBATCH_URL,\n body,\n );\n res.results.forEach((result, i) => {\n const q = chunk[i];\n if (!q) return;\n const key = `${q.name}@${q.version}`;\n if (!result.vulns || result.vulns.length === 0) return;\n const ids = idsByPackage.get(key) ?? new Set<string>();\n for (const v of result.vulns) ids.add(v.id);\n idsByPackage.set(key, ids);\n });\n }\n\n const allIds = new Set<string>();\n for (const ids of idsByPackage.values()) {\n for (const id of ids) allIds.add(id);\n }\n\n const detailsById = new Map<string, OsvVulnDetail>();\n for (const id of allIds) {\n try {\n const detail = await getJson<OsvVulnDetail>(\n fetchImpl,\n `${OSV_VULN_URL}/${encodeURIComponent(id)}`,\n );\n detailsById.set(id, detail);\n } catch {\n // Skip missing/broken records; we still have the id reported below.\n }\n }\n\n const findings: Finding[] = [];\n for (const pkg of packages) {\n const key = `${pkg.name}@${pkg.version}`;\n const ids = idsByPackage.get(key);\n if (!ids) continue;\n for (const id of ids) {\n const detail = detailsById.get(id);\n findings.push(buildFinding(pkg, id, detail));\n }\n }\n return findings;\n}\n\nfunction buildFinding(\n pkg: PackageInstance,\n id: string,\n detail: OsvVulnDetail | undefined,\n): Finding {\n const severity = detail ? parseSeverity(detail) : \"unknown\";\n const summary = detail?.summary ?? detail?.details ?? id;\n return {\n id,\n source: \"osv\",\n type: \"vulnerability\",\n severity,\n packageName: pkg.name,\n installedVersion: pkg.version,\n summary: truncate(summary, 240),\n url: pickAdvisoryUrl(detail) ?? `https://osv.dev/vulnerability/${id}`,\n fixedVersions: detail ? collectFixedVersions(detail, pkg.name) : [],\n affectedPaths: [pkg.path],\n };\n}\n\nexport function parseSeverity(detail: OsvVulnDetail): Severity {\n // GHSA records expose a normalized severity in database_specific.severity.\n const dbSpecific = detail.database_specific?.severity?.toLowerCase();\n if (\n dbSpecific === \"critical\" ||\n dbSpecific === \"high\" ||\n dbSpecific === \"moderate\" ||\n dbSpecific === \"low\"\n ) {\n return dbSpecific;\n }\n if (dbSpecific === \"medium\") return \"moderate\";\n\n const cvss = detail.severity?.find((s) => s.type?.startsWith(\"CVSS_\"));\n if (cvss) {\n const score = parseCvssScore(cvss.score);\n if (score === undefined) return \"unknown\";\n if (score >= 9.0) return \"critical\";\n if (score >= 7.0) return \"high\";\n if (score >= 4.0) return \"moderate\";\n if (score > 0) return \"low\";\n }\n return \"unknown\";\n}\n\nfunction parseCvssScore(vector: string): number | undefined {\n const direct = Number.parseFloat(vector);\n if (!Number.isNaN(direct) && vector.trim() !== \"\") return direct;\n // Some entries store the full CVSS vector string; we don't compute it here.\n return undefined;\n}\n\nfunction pickAdvisoryUrl(detail: OsvVulnDetail | undefined): string | undefined {\n if (!detail?.references) return undefined;\n const advisory = detail.references.find((r) => r.type === \"ADVISORY\");\n return advisory?.url ?? detail.references[0]?.url;\n}\n\nexport function collectFixedVersions(\n detail: OsvVulnDetail,\n packageName: string,\n): string[] {\n const out = new Set<string>();\n for (const aff of detail.affected ?? []) {\n if (aff.package?.name && aff.package.name !== packageName) continue;\n for (const range of aff.ranges ?? []) {\n for (const event of range.events ?? []) {\n if (event.fixed) out.add(event.fixed);\n }\n }\n }\n return [...out];\n}\n\nfunction* chunked<T>(items: T[], size: number): Generator<T[]> {\n for (let i = 0; i < items.length; i += size) {\n yield items.slice(i, i + size);\n }\n}\n\nasync function postJson<T>(\n fetchImpl: typeof fetch,\n url: string,\n body: unknown,\n): Promise<T> {\n return withRetry(async () => {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);\n try {\n const res = await fetchImpl(url, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n if (!res.ok) {\n throw new HttpError(`OSV ${res.status}: ${res.statusText}`, res.status);\n }\n return (await res.json()) as T;\n } finally {\n clearTimeout(timer);\n }\n });\n}\n\nasync function getJson<T>(fetchImpl: typeof fetch, url: string): Promise<T> {\n return withRetry(async () => {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);\n try {\n const res = await fetchImpl(url, { signal: controller.signal });\n if (!res.ok) {\n throw new HttpError(`OSV ${res.status}: ${res.statusText}`, res.status);\n }\n return (await res.json()) as T;\n } finally {\n clearTimeout(timer);\n }\n });\n}\n\nclass HttpError extends Error {\n constructor(message: string, public readonly status: number) {\n super(message);\n }\n}\n\nasync function withRetry<T>(fn: () => Promise<T>): Promise<T> {\n let lastErr: unknown;\n for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {\n try {\n return await fn();\n } catch (err) {\n lastErr = err;\n if (!isRetryable(err) || attempt === MAX_RETRIES) break;\n await new Promise((r) => setTimeout(r, 250 * 2 ** attempt));\n }\n }\n throw lastErr;\n}\n\nfunction isRetryable(err: unknown): boolean {\n if (err instanceof HttpError) return err.status >= 500;\n // AbortError (timeout) and network errors are retryable.\n return true;\n}\n\nfunction truncate(s: string, max: number): string {\n if (s.length <= max) return s;\n return `${s.slice(0, max - 1)}…`;\n}\n","export type Severity = \"critical\" | \"high\" | \"moderate\" | \"low\" | \"unknown\";\n\nexport type Ecosystem = \"npm\";\n\nexport type FindingType =\n | \"vulnerability\"\n | \"malware\"\n | \"risk-signal\"\n | \"integrity\";\n\nexport const SEVERITY_RANK: Record<Severity, number> = {\n critical: 4,\n high: 3,\n moderate: 2,\n low: 1,\n unknown: 0,\n};\n\nexport interface PackageInstance {\n name: string;\n version: string;\n ecosystem: Ecosystem;\n /** Path within the lockfile's `packages` map (e.g. \"node_modules/foo\"). */\n path: string;\n direct: boolean;\n dev: boolean;\n optional: boolean;\n resolved?: string;\n integrity?: string;\n}\n\nexport interface Finding {\n id: string;\n source: \"osv\";\n type: FindingType;\n severity: Severity;\n packageName: string;\n installedVersion: string;\n summary: string;\n url?: string;\n fixedVersions: string[];\n affectedPaths: string[];\n}\n\nexport interface ScanError {\n message: string;\n cause?: string;\n}\n\nexport interface ScanResult {\n scannedAt: string;\n packagesScanned: number;\n findings: Finding[];\n summary: Record<Severity, number>;\n errors: ScanError[];\n}\n\nexport interface ScanProjectOptions {\n cwd?: string;\n lockfile?: string;\n includeDev?: boolean;\n prodOnly?: boolean;\n cache?: boolean;\n fetchImpl?: typeof fetch;\n}\n\nexport interface ScanLockfileOptions {\n lockfilePath: string;\n includeDev?: boolean;\n prodOnly?: boolean;\n fetchImpl?: typeof fetch;\n}\n\nexport type FailOnLevel = Severity | \"none\";\n"],"mappings":"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","names":["resolve","resolve"]}
|
|
1
|
+
{"version":3,"sources":["../src/scanner.ts","../src/extractors/npm-package-lock.ts","../src/extractors/pnpm-lock.ts","../src/extractors/yarn-lock.ts","../src/sources/osv.ts","../src/types.ts"],"sourcesContent":["import { existsSync, statSync } from \"node:fs\";\nimport { basename, resolve, join } from \"node:path\";\nimport { parseNpmPackageLock } from \"./extractors/npm-package-lock.js\";\nimport { parsePnpmLock } from \"./extractors/pnpm-lock.js\";\nimport { parseYarnLock } from \"./extractors/yarn-lock.js\";\nimport { queryOsv } from \"./sources/osv.js\";\nimport { SEVERITY_RANK } from \"./types.js\";\nimport type {\n Finding,\n PackageInstance,\n ScanError,\n ScanLockfileOptions,\n ScanProjectOptions,\n ScanResult,\n Severity,\n} from \"./types.js\";\n\nexport async function scanProject(\n options: ScanProjectOptions = {},\n): Promise<ScanResult> {\n const cwd = resolve(options.cwd ?? process.cwd());\n const lockfilePath = options.lockfile\n ? resolve(cwd, options.lockfile)\n : detectLockfile(cwd);\n\n if (!lockfilePath) {\n throw new ScanInputError(\n `No supported lockfile found in ${cwd}. Pass --lockfile or run in a directory with package-lock.json, pnpm-lock.yaml, or yarn.lock.`,\n );\n }\n\n return scanLockfile({\n lockfilePath,\n includeDev: options.includeDev,\n prodOnly: options.prodOnly,\n fetchImpl: options.fetchImpl,\n });\n}\n\nexport async function scanLockfile(\n options: ScanLockfileOptions,\n): Promise<ScanResult> {\n const { lockfilePath } = options;\n if (!existsSync(lockfilePath)) {\n throw new ScanInputError(`Lockfile does not exist: ${lockfilePath}`);\n }\n const stat = statSync(lockfilePath);\n if (!stat.isFile()) {\n throw new ScanInputError(`Lockfile path is not a file: ${lockfilePath}`);\n }\n\n const allInstances = parseLockfile(lockfilePath);\n const instances = filterInstances(allInstances, options);\n const errors: ScanError[] = [];\n\n let findings: Finding[] = [];\n try {\n findings = await queryOsv(instances, { fetchImpl: options.fetchImpl });\n } catch (err) {\n errors.push({\n message: \"Failed to query OSV advisory database\",\n cause: (err as Error).message,\n });\n }\n\n findings.sort(compareFindings);\n\n return {\n scannedAt: new Date().toISOString(),\n packagesScanned: instances.length,\n findings,\n summary: summarize(findings),\n errors,\n };\n}\n\nexport class ScanInputError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"ScanInputError\";\n }\n}\n\nconst LOCKFILE_CANDIDATES = [\n \"pnpm-lock.yaml\",\n \"yarn.lock\",\n \"package-lock.json\",\n \"npm-shrinkwrap.json\",\n] as const;\n\nfunction detectLockfile(cwd: string): string | undefined {\n for (const file of LOCKFILE_CANDIDATES) {\n const candidate = join(cwd, file);\n if (existsSync(candidate)) return candidate;\n }\n return undefined;\n}\n\nfunction parseLockfile(lockfilePath: string): PackageInstance[] {\n const name = basename(lockfilePath);\n if (name === \"pnpm-lock.yaml\") return parsePnpmLock(lockfilePath);\n if (name === \"yarn.lock\") return parseYarnLock(lockfilePath);\n if (name === \"package-lock.json\" || name === \"npm-shrinkwrap.json\") {\n return parseNpmPackageLock(lockfilePath);\n }\n throw new ScanInputError(\n `Unsupported lockfile name: ${name}. Supported: package-lock.json, npm-shrinkwrap.json, pnpm-lock.yaml, yarn.lock.`,\n );\n}\n\nfunction filterInstances(\n instances: PackageInstance[],\n options: { includeDev?: boolean; prodOnly?: boolean },\n): PackageInstance[] {\n const includeDev = options.prodOnly ? false : options.includeDev !== false;\n if (includeDev) return instances;\n return instances.filter((p) => !p.dev);\n}\n\nexport function compareFindings(a: Finding, b: Finding): number {\n const sev = SEVERITY_RANK[b.severity] - SEVERITY_RANK[a.severity];\n if (sev !== 0) return sev;\n if (a.packageName !== b.packageName) {\n return a.packageName.localeCompare(b.packageName);\n }\n if (a.installedVersion !== b.installedVersion) {\n return a.installedVersion.localeCompare(b.installedVersion);\n }\n return a.id.localeCompare(b.id);\n}\n\nexport function summarize(findings: Finding[]): Record<Severity, number> {\n const summary: Record<Severity, number> = {\n critical: 0,\n high: 0,\n moderate: 0,\n low: 0,\n unknown: 0,\n };\n for (const f of findings) summary[f.severity]++;\n return summary;\n}\n\n/**\n * Returns true when any finding meets or exceeds the given severity threshold.\n * \"none\" means never fail.\n */\nexport function meetsThreshold(\n findings: Finding[],\n threshold: Severity | \"none\",\n): boolean {\n if (threshold === \"none\") return false;\n const min = SEVERITY_RANK[threshold];\n return findings.some((f) => SEVERITY_RANK[f.severity] >= min);\n}\n","import { readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport type { PackageInstance } from \"../types.js\";\n\ninterface NpmLockEntry {\n version?: string;\n resolved?: string;\n integrity?: string;\n dev?: boolean;\n devOptional?: boolean;\n optional?: boolean;\n peer?: boolean;\n link?: boolean;\n // Present on the root (\"\") entry only.\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n optionalDependencies?: Record<string, string>;\n peerDependencies?: Record<string, string>;\n}\n\ninterface NpmLockfile {\n name?: string;\n lockfileVersion?: number;\n packages?: Record<string, NpmLockEntry>;\n}\n\n/**\n * Parse an npm `package-lock.json` (v2 or v3) and return one\n * PackageInstance per node in the `packages` map.\n *\n * The empty-string key represents the root project and is skipped.\n */\nexport function parseNpmPackageLock(filePath: string): PackageInstance[] {\n const absolute = resolve(filePath);\n const raw = readFileSync(absolute, \"utf8\");\n let parsed: NpmLockfile;\n try {\n parsed = JSON.parse(raw) as NpmLockfile;\n } catch (err) {\n throw new Error(\n `Failed to parse ${absolute}: ${(err as Error).message}`,\n );\n }\n\n if (parsed.lockfileVersion !== 2 && parsed.lockfileVersion !== 3) {\n throw new Error(\n `Unsupported npm lockfileVersion ${String(\n parsed.lockfileVersion,\n )} in ${absolute}. Only v2 and v3 are supported.`,\n );\n }\n\n const packages = parsed.packages;\n if (!packages || typeof packages !== \"object\") {\n throw new Error(\n `Lockfile ${absolute} has no \"packages\" map; cannot extract installed versions.`,\n );\n }\n\n const directDeps = collectDirectDependencyNames(packages[\"\"] ?? {});\n const instances: PackageInstance[] = [];\n\n for (const [path, entry] of Object.entries(packages)) {\n if (path === \"\") continue;\n if (entry.link) continue; // workspace symlink, not a real install\n const name = packagePathToName(path);\n if (!name) continue;\n if (!entry.version) continue;\n\n instances.push({\n name,\n version: entry.version,\n ecosystem: \"npm\",\n path,\n direct: directDeps.has(name) && isTopLevelInstance(path),\n dev: Boolean(entry.dev || entry.devOptional),\n optional: Boolean(entry.optional || entry.devOptional),\n resolved: entry.resolved,\n integrity: entry.integrity,\n });\n }\n\n return instances;\n}\n\nfunction collectDirectDependencyNames(rootEntry: NpmLockEntry): Set<string> {\n const names = new Set<string>();\n for (const key of [\n \"dependencies\",\n \"devDependencies\",\n \"optionalDependencies\",\n \"peerDependencies\",\n ] as const) {\n const block = rootEntry[key];\n if (!block) continue;\n for (const name of Object.keys(block)) names.add(name);\n }\n return names;\n}\n\n/**\n * \"node_modules/foo\" -> \"foo\"\n * \"node_modules/@scope/bar\" -> \"@scope/bar\"\n * \"node_modules/foo/node_modules/bar\" -> \"bar\"\n */\nexport function packagePathToName(path: string): string | null {\n const marker = \"node_modules/\";\n const idx = path.lastIndexOf(marker);\n if (idx === -1) return null;\n const tail = path.slice(idx + marker.length);\n if (!tail) return null;\n if (tail.startsWith(\"@\")) {\n const firstSlash = tail.indexOf(\"/\");\n if (firstSlash === -1) return null;\n const secondSlash = tail.indexOf(\"/\", firstSlash + 1);\n return secondSlash === -1 ? tail : tail.slice(0, secondSlash);\n }\n const next = tail.indexOf(\"/\");\n return next === -1 ? tail : tail.slice(0, next);\n}\n\n/** A direct-install path has exactly one `node_modules/` segment. */\nfunction isTopLevelInstance(path: string): boolean {\n const first = path.indexOf(\"node_modules/\");\n if (first === -1) return false;\n return path.indexOf(\"node_modules/\", first + 1) === -1;\n}\n","import { readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport yaml from \"js-yaml\";\nimport type { PackageInstance } from \"../types.js\";\n\ninterface PnpmDepRef {\n specifier?: string;\n version?: string;\n}\n\ntype PnpmImporterDeps = Record<string, string | PnpmDepRef>;\n\ninterface PnpmImporter {\n dependencies?: PnpmImporterDeps;\n devDependencies?: PnpmImporterDeps;\n optionalDependencies?: PnpmImporterDeps;\n}\n\ninterface PnpmPackageEntry {\n resolution?: { integrity?: string; tarball?: string };\n name?: string;\n version?: string;\n dev?: boolean;\n optional?: boolean;\n}\n\ninterface PnpmLockfile {\n lockfileVersion?: number | string;\n importers?: Record<string, PnpmImporter>;\n packages?: Record<string, PnpmPackageEntry>;\n // v6 root-level deps when no importers section\n dependencies?: PnpmImporterDeps;\n devDependencies?: PnpmImporterDeps;\n optionalDependencies?: PnpmImporterDeps;\n}\n\nconst SUPPORTED_MAJOR_VERSIONS = new Set([6, 9]);\n\nexport function parsePnpmLock(filePath: string): PackageInstance[] {\n const absolute = resolve(filePath);\n const raw = readFileSync(absolute, \"utf8\");\n let parsed: PnpmLockfile;\n try {\n parsed = (yaml.load(raw) ?? {}) as PnpmLockfile;\n } catch (err) {\n throw new Error(\n `Failed to parse ${absolute}: ${(err as Error).message}`,\n );\n }\n\n const versionRaw = parsed.lockfileVersion;\n const major = parseLockfileMajor(versionRaw);\n if (major === null || !SUPPORTED_MAJOR_VERSIONS.has(major)) {\n throw new Error(\n `Unsupported pnpm lockfileVersion ${String(versionRaw)} in ${absolute}. Supported: 6.x, 9.x.`,\n );\n }\n\n const packages = parsed.packages;\n if (!packages || typeof packages !== \"object\") {\n throw new Error(\n `Lockfile ${absolute} has no \"packages\" map; cannot extract installed versions.`,\n );\n }\n\n const importers = parsed.importers ?? {\n \".\": {\n dependencies: parsed.dependencies,\n devDependencies: parsed.devDependencies,\n optionalDependencies: parsed.optionalDependencies,\n },\n };\n\n const direct = collectDirectFromImporters(importers);\n const instances: PackageInstance[] = [];\n\n for (const [key, entry] of Object.entries(packages)) {\n const parsed = parsePnpmPackageKey(key);\n if (!parsed) continue;\n const name = entry.name ?? parsed.name;\n const version = entry.version ?? parsed.version;\n if (!name || !version) continue;\n\n const isDirect = direct.prod.has(name) || direct.dev.has(name) || direct.optional.has(name);\n const onlyDev = direct.dev.has(name) && !direct.prod.has(name);\n const onlyOptional =\n direct.optional.has(name) && !direct.prod.has(name) && !direct.dev.has(name);\n\n instances.push({\n name,\n version,\n ecosystem: \"npm\",\n path: key,\n direct: isDirect,\n dev: Boolean(entry.dev) || onlyDev,\n optional: Boolean(entry.optional) || onlyOptional,\n resolved: entry.resolution?.tarball,\n integrity: entry.resolution?.integrity,\n });\n }\n\n return instances;\n}\n\nfunction parseLockfileMajor(value: unknown): number | null {\n if (typeof value === \"number\") return Math.trunc(value);\n if (typeof value === \"string\") {\n const num = parseInt(value, 10);\n return Number.isNaN(num) ? null : num;\n }\n return null;\n}\n\ninterface DirectSets {\n prod: Set<string>;\n dev: Set<string>;\n optional: Set<string>;\n}\n\nfunction collectDirectFromImporters(\n importers: Record<string, PnpmImporter>,\n): DirectSets {\n const prod = new Set<string>();\n const dev = new Set<string>();\n const optional = new Set<string>();\n for (const importer of Object.values(importers)) {\n if (!importer) continue;\n addDepNames(importer.dependencies, prod);\n addDepNames(importer.devDependencies, dev);\n addDepNames(importer.optionalDependencies, optional);\n }\n return { prod, dev, optional };\n}\n\nfunction addDepNames(block: PnpmImporterDeps | undefined, into: Set<string>): void {\n if (!block) return;\n for (const name of Object.keys(block)) into.add(name);\n}\n\n/**\n * pnpm v9: \"lodash@4.17.21\", \"@scope/foo@1.2.3\", \"vitest@2.0.0(peer)\"\n * pnpm v6: \"/lodash@4.17.21\", \"/@scope/foo@1.2.3\", \"/vitest@2.0.0(peer)\"\n */\nexport function parsePnpmPackageKey(\n key: string,\n): { name: string; version: string } | null {\n let working = key.startsWith(\"/\") ? key.slice(1) : key;\n // Strip peer-deps suffix (everything from the first paren onward).\n const parenIdx = working.indexOf(\"(\");\n if (parenIdx !== -1) working = working.slice(0, parenIdx);\n\n // Find the @ separating name from version. For scoped names (@scope/foo@x.y.z)\n // we must skip the leading @.\n const startSearch = working.startsWith(\"@\") ? 1 : 0;\n const atIdx = working.indexOf(\"@\", startSearch);\n if (atIdx <= 0) return null;\n const name = working.slice(0, atIdx);\n const version = working.slice(atIdx + 1);\n if (!name || !version) return null;\n return { name, version };\n}\n","import { existsSync, readFileSync } from \"node:fs\";\nimport { dirname, join, resolve } from \"node:path\";\nimport yaml from \"js-yaml\";\nimport type { PackageInstance } from \"../types.js\";\n\ninterface YarnEntry {\n specs: string[];\n fields: Record<string, string>;\n}\n\ninterface BerryEntry {\n version?: string;\n resolution?: string;\n checksum?: string;\n // language and linker keys are ignored\n}\n\n/**\n * Parse a yarn lockfile (classic v1 or berry v2+) and return one\n * PackageInstance per resolved entry.\n *\n * Direct/dev flags are inferred by reading the sibling package.json,\n * since yarn lockfiles don't carry that information themselves.\n */\nexport function parseYarnLock(filePath: string): PackageInstance[] {\n const absolute = resolve(filePath);\n const content = readFileSync(absolute, \"utf8\");\n const projectDir = dirname(absolute);\n const directs = readDirectDepsFromPackageJson(projectDir);\n const isBerry = /^__metadata:/m.test(content);\n return isBerry\n ? parseBerry(absolute, content, directs)\n : parseClassic(absolute, content, directs);\n}\n\ninterface DirectSets {\n prod: Set<string>;\n dev: Set<string>;\n optional: Set<string>;\n any: Set<string>;\n}\n\nfunction readDirectDepsFromPackageJson(projectDir: string): DirectSets {\n const result: DirectSets = {\n prod: new Set(),\n dev: new Set(),\n optional: new Set(),\n any: new Set(),\n };\n const pkgPath = join(projectDir, \"package.json\");\n if (!existsSync(pkgPath)) return result;\n try {\n const pkg = JSON.parse(readFileSync(pkgPath, \"utf8\")) as {\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n optionalDependencies?: Record<string, string>;\n peerDependencies?: Record<string, string>;\n };\n for (const name of Object.keys(pkg.dependencies ?? {})) result.prod.add(name);\n for (const name of Object.keys(pkg.devDependencies ?? {})) result.dev.add(name);\n for (const name of Object.keys(pkg.optionalDependencies ?? {}))\n result.optional.add(name);\n for (const set of [result.prod, result.dev, result.optional]) {\n for (const n of set) result.any.add(n);\n }\n for (const name of Object.keys(pkg.peerDependencies ?? {})) result.any.add(name);\n } catch {\n // malformed package.json: leave sets empty\n }\n return result;\n}\n\n// ---------- yarn classic v1 ----------\n\nfunction parseClassic(\n absolute: string,\n content: string,\n directs: DirectSets,\n): PackageInstance[] {\n const entries = parseClassicEntries(content);\n const instances: PackageInstance[] = [];\n for (const entry of entries) {\n const version = entry.fields.version;\n if (!version) continue;\n const names = uniq(entry.specs.map((s) => parseYarnSpec(s).name).filter(Boolean));\n const name = names[0];\n if (!name) continue;\n const isDirect = names.some((n) => directs.any.has(n));\n const inProd = names.some((n) => directs.prod.has(n));\n const inDev = names.some((n) => directs.dev.has(n));\n const inOpt = names.some((n) => directs.optional.has(n));\n instances.push({\n name,\n version,\n ecosystem: \"npm\",\n path: `${name}@${version}`,\n direct: isDirect,\n dev: inDev && !inProd,\n optional: inOpt && !inProd && !inDev,\n resolved: entry.fields.resolved,\n integrity: entry.fields.integrity,\n });\n }\n void absolute;\n return instances;\n}\n\nexport function parseClassicEntries(content: string): YarnEntry[] {\n const lines = content.split(/\\r?\\n/);\n const entries: YarnEntry[] = [];\n let current: YarnEntry | null = null;\n for (const rawLine of lines) {\n if (rawLine === \"\" || rawLine.trimStart().startsWith(\"#\")) continue;\n if (!/^\\s/.test(rawLine)) {\n // Header line ending with \":\"\n if (current) entries.push(current);\n const header = rawLine.replace(/:\\s*$/, \"\");\n current = { specs: splitClassicSpecs(header), fields: {} };\n continue;\n }\n if (!current) continue;\n // Top-level field for this entry has indent of exactly 2 spaces.\n const indent = rawLine.match(/^ +/)?.[0].length ?? 0;\n if (indent !== 2) continue;\n const trimmed = rawLine.trim();\n const m =\n trimmed.match(/^([^\\s\"]+)\\s+\"((?:[^\"\\\\]|\\\\.)*)\"$/) ??\n trimmed.match(/^([^\\s\"]+)\\s+(\\S+)$/);\n if (m && m[1] !== undefined && m[2] !== undefined) {\n current.fields[m[1]] = m[2];\n }\n }\n if (current) entries.push(current);\n return entries;\n}\n\nfunction splitClassicSpecs(header: string): string[] {\n const out: string[] = [];\n let cur = \"\";\n let inQuote = false;\n for (const ch of header) {\n if (ch === '\"') {\n inQuote = !inQuote;\n continue;\n }\n if (ch === \",\" && !inQuote) {\n const spec = cur.trim();\n if (spec) out.push(spec);\n cur = \"\";\n continue;\n }\n cur += ch;\n }\n const last = cur.trim();\n if (last) out.push(last);\n return out;\n}\n\n// ---------- yarn berry (v2+) ----------\n\nfunction parseBerry(\n absolute: string,\n content: string,\n directs: DirectSets,\n): PackageInstance[] {\n let parsed: Record<string, BerryEntry | unknown>;\n try {\n parsed = (yaml.load(content) ?? {}) as Record<string, BerryEntry | unknown>;\n } catch (err) {\n throw new Error(\n `Failed to parse ${absolute}: ${(err as Error).message}`,\n );\n }\n const instances: PackageInstance[] = [];\n for (const [key, value] of Object.entries(parsed)) {\n if (key === \"__metadata\") continue;\n if (!value || typeof value !== \"object\") continue;\n const entry = value as BerryEntry;\n if (!entry.version) continue;\n const specs = splitClassicSpecs(key);\n const names = uniq(specs.map((s) => parseYarnSpec(s).name).filter(Boolean));\n const name = names[0];\n if (!name) continue;\n const isDirect = names.some((n) => directs.any.has(n));\n const inProd = names.some((n) => directs.prod.has(n));\n const inDev = names.some((n) => directs.dev.has(n));\n const inOpt = names.some((n) => directs.optional.has(n));\n instances.push({\n name,\n version: entry.version,\n ecosystem: \"npm\",\n path: `${name}@${entry.version}`,\n direct: isDirect,\n dev: inDev && !inProd,\n optional: inOpt && !inProd && !inDev,\n resolved: entry.resolution,\n integrity: entry.checksum,\n });\n }\n return instances;\n}\n\n// ---------- shared ----------\n\n/**\n * \"lodash@^4.17.20\" -> { name: \"lodash\", selector: \"^4.17.20\" }\n * \"@scope/foo@^1.0.0\" -> { name: \"@scope/foo\", selector: \"^1.0.0\" }\n * Berry: \"lodash@npm:^4.17.20\" -> { name: \"lodash\", selector: \"^4.17.20\" }\n */\nexport function parseYarnSpec(spec: string): { name: string; selector: string } {\n const startSearch = spec.startsWith(\"@\") ? 1 : 0;\n const atIdx = spec.indexOf(\"@\", startSearch);\n if (atIdx <= 0) return { name: spec, selector: \"\" };\n const name = spec.slice(0, atIdx);\n let selector = spec.slice(atIdx + 1);\n if (selector.startsWith(\"npm:\")) selector = selector.slice(4);\n return { name, selector };\n}\n\nfunction uniq(values: string[]): string[] {\n return Array.from(new Set(values));\n}\n","import type { Finding, PackageInstance, Severity } from \"../types.js\";\n\nconst OSV_QUERYBATCH_URL = \"https://api.osv.dev/v1/querybatch\";\nconst OSV_VULN_URL = \"https://api.osv.dev/v1/vulns\";\nconst QUERY_CHUNK_SIZE = 500;\nconst REQUEST_TIMEOUT_MS = 15_000;\nconst MAX_RETRIES = 2;\n\ninterface OsvQueryBatchResponse {\n results: Array<{ vulns?: Array<{ id: string; modified?: string }> }>;\n}\n\ninterface OsvSeverity {\n type: string;\n score: string;\n}\n\ninterface OsvAffectedRange {\n type: string;\n events: Array<{ introduced?: string; fixed?: string; last_affected?: string }>;\n}\n\ninterface OsvAffectedPackage {\n package?: { ecosystem?: string; name?: string };\n ranges?: OsvAffectedRange[];\n versions?: string[];\n}\n\ninterface OsvVulnDetail {\n id: string;\n summary?: string;\n details?: string;\n references?: Array<{ type?: string; url?: string }>;\n severity?: OsvSeverity[];\n database_specific?: { severity?: string };\n affected?: OsvAffectedPackage[];\n}\n\nexport interface OsvQueryDeps {\n fetchImpl?: typeof fetch;\n}\n\ninterface UniquePackage {\n name: string;\n version: string;\n}\n\n/**\n * Build the deduplicated list of unique name@version pairs to query OSV with.\n */\nexport function dedupeForQuery(\n packages: PackageInstance[],\n): UniquePackage[] {\n const seen = new Set<string>();\n const out: UniquePackage[] = [];\n for (const pkg of packages) {\n const key = `${pkg.name}@${pkg.version}`;\n if (seen.has(key)) continue;\n seen.add(key);\n out.push({ name: pkg.name, version: pkg.version });\n }\n return out;\n}\n\n/**\n * Query OSV for the given installed packages and return one Finding per\n * (advisory, affected package instance) pair.\n */\nexport async function queryOsv(\n packages: PackageInstance[],\n deps: OsvQueryDeps = {},\n): Promise<Finding[]> {\n const fetchImpl = deps.fetchImpl ?? fetch;\n const unique = dedupeForQuery(packages);\n if (unique.length === 0) return [];\n\n const idsByPackage = new Map<string, Set<string>>();\n for (const chunk of chunked(unique, QUERY_CHUNK_SIZE)) {\n const body = {\n queries: chunk.map((q) => ({\n package: { ecosystem: \"npm\", name: q.name },\n version: q.version,\n })),\n };\n const res = await postJson<OsvQueryBatchResponse>(\n fetchImpl,\n OSV_QUERYBATCH_URL,\n body,\n );\n res.results.forEach((result, i) => {\n const q = chunk[i];\n if (!q) return;\n const key = `${q.name}@${q.version}`;\n if (!result.vulns || result.vulns.length === 0) return;\n const ids = idsByPackage.get(key) ?? new Set<string>();\n for (const v of result.vulns) ids.add(v.id);\n idsByPackage.set(key, ids);\n });\n }\n\n const allIds = new Set<string>();\n for (const ids of idsByPackage.values()) {\n for (const id of ids) allIds.add(id);\n }\n\n const detailsById = new Map<string, OsvVulnDetail>();\n for (const id of allIds) {\n try {\n const detail = await getJson<OsvVulnDetail>(\n fetchImpl,\n `${OSV_VULN_URL}/${encodeURIComponent(id)}`,\n );\n detailsById.set(id, detail);\n } catch {\n // Skip missing/broken records; we still have the id reported below.\n }\n }\n\n const findings: Finding[] = [];\n for (const pkg of packages) {\n const key = `${pkg.name}@${pkg.version}`;\n const ids = idsByPackage.get(key);\n if (!ids) continue;\n for (const id of ids) {\n const detail = detailsById.get(id);\n findings.push(buildFinding(pkg, id, detail));\n }\n }\n return findings;\n}\n\nfunction buildFinding(\n pkg: PackageInstance,\n id: string,\n detail: OsvVulnDetail | undefined,\n): Finding {\n const severity = detail ? parseSeverity(detail) : \"unknown\";\n const summary = detail?.summary ?? detail?.details ?? id;\n return {\n id,\n source: \"osv\",\n type: \"vulnerability\",\n severity,\n packageName: pkg.name,\n installedVersion: pkg.version,\n summary: truncate(summary, 240),\n url: pickAdvisoryUrl(detail) ?? `https://osv.dev/vulnerability/${id}`,\n fixedVersions: detail ? collectFixedVersions(detail, pkg.name) : [],\n affectedPaths: [pkg.path],\n };\n}\n\nexport function parseSeverity(detail: OsvVulnDetail): Severity {\n // GHSA records expose a normalized severity in database_specific.severity.\n const dbSpecific = detail.database_specific?.severity?.toLowerCase();\n if (\n dbSpecific === \"critical\" ||\n dbSpecific === \"high\" ||\n dbSpecific === \"moderate\" ||\n dbSpecific === \"low\"\n ) {\n return dbSpecific;\n }\n if (dbSpecific === \"medium\") return \"moderate\";\n\n const cvss = detail.severity?.find((s) => s.type?.startsWith(\"CVSS_\"));\n if (cvss) {\n const score = parseCvssScore(cvss.score);\n if (score === undefined) return \"unknown\";\n if (score >= 9.0) return \"critical\";\n if (score >= 7.0) return \"high\";\n if (score >= 4.0) return \"moderate\";\n if (score > 0) return \"low\";\n }\n return \"unknown\";\n}\n\nfunction parseCvssScore(vector: string): number | undefined {\n const direct = Number.parseFloat(vector);\n if (!Number.isNaN(direct) && vector.trim() !== \"\") return direct;\n // Some entries store the full CVSS vector string; we don't compute it here.\n return undefined;\n}\n\nfunction pickAdvisoryUrl(detail: OsvVulnDetail | undefined): string | undefined {\n if (!detail?.references) return undefined;\n const advisory = detail.references.find((r) => r.type === \"ADVISORY\");\n return advisory?.url ?? detail.references[0]?.url;\n}\n\nexport function collectFixedVersions(\n detail: OsvVulnDetail,\n packageName: string,\n): string[] {\n const out = new Set<string>();\n for (const aff of detail.affected ?? []) {\n if (aff.package?.name && aff.package.name !== packageName) continue;\n for (const range of aff.ranges ?? []) {\n for (const event of range.events ?? []) {\n if (event.fixed) out.add(event.fixed);\n }\n }\n }\n return [...out];\n}\n\nfunction* chunked<T>(items: T[], size: number): Generator<T[]> {\n for (let i = 0; i < items.length; i += size) {\n yield items.slice(i, i + size);\n }\n}\n\nasync function postJson<T>(\n fetchImpl: typeof fetch,\n url: string,\n body: unknown,\n): Promise<T> {\n return withRetry(async () => {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);\n try {\n const res = await fetchImpl(url, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n if (!res.ok) {\n throw new HttpError(`OSV ${res.status}: ${res.statusText}`, res.status);\n }\n return (await res.json()) as T;\n } finally {\n clearTimeout(timer);\n }\n });\n}\n\nasync function getJson<T>(fetchImpl: typeof fetch, url: string): Promise<T> {\n return withRetry(async () => {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);\n try {\n const res = await fetchImpl(url, { signal: controller.signal });\n if (!res.ok) {\n throw new HttpError(`OSV ${res.status}: ${res.statusText}`, res.status);\n }\n return (await res.json()) as T;\n } finally {\n clearTimeout(timer);\n }\n });\n}\n\nclass HttpError extends Error {\n constructor(message: string, public readonly status: number) {\n super(message);\n }\n}\n\nasync function withRetry<T>(fn: () => Promise<T>): Promise<T> {\n let lastErr: unknown;\n for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {\n try {\n return await fn();\n } catch (err) {\n lastErr = err;\n if (!isRetryable(err) || attempt === MAX_RETRIES) break;\n await new Promise((r) => setTimeout(r, 250 * 2 ** attempt));\n }\n }\n throw lastErr;\n}\n\nfunction isRetryable(err: unknown): boolean {\n if (err instanceof HttpError) return err.status >= 500;\n // AbortError (timeout) and network errors are retryable.\n return true;\n}\n\nfunction truncate(s: string, max: number): string {\n if (s.length <= max) return s;\n return `${s.slice(0, max - 1)}…`;\n}\n","export type Severity = \"critical\" | \"high\" | \"moderate\" | \"low\" | \"unknown\";\n\nexport type Ecosystem = \"npm\";\n\nexport type FindingType =\n | \"vulnerability\"\n | \"malware\"\n | \"risk-signal\"\n | \"integrity\";\n\nexport const SEVERITY_RANK: Record<Severity, number> = {\n critical: 4,\n high: 3,\n moderate: 2,\n low: 1,\n unknown: 0,\n};\n\nexport interface PackageInstance {\n name: string;\n version: string;\n ecosystem: Ecosystem;\n /** Path within the lockfile's `packages` map (e.g. \"node_modules/foo\"). */\n path: string;\n direct: boolean;\n dev: boolean;\n optional: boolean;\n resolved?: string;\n integrity?: string;\n}\n\nexport interface Finding {\n id: string;\n source: \"osv\";\n type: FindingType;\n severity: Severity;\n packageName: string;\n installedVersion: string;\n summary: string;\n url?: string;\n fixedVersions: string[];\n affectedPaths: string[];\n}\n\nexport interface ScanError {\n message: string;\n cause?: string;\n}\n\nexport interface ScanResult {\n scannedAt: string;\n packagesScanned: number;\n findings: Finding[];\n summary: Record<Severity, number>;\n errors: ScanError[];\n}\n\nexport interface ScanProjectOptions {\n cwd?: string;\n lockfile?: string;\n includeDev?: boolean;\n prodOnly?: boolean;\n cache?: boolean;\n fetchImpl?: typeof fetch;\n}\n\nexport interface ScanLockfileOptions {\n lockfilePath: string;\n includeDev?: boolean;\n prodOnly?: boolean;\n fetchImpl?: typeof fetch;\n}\n\nexport type FailOnLevel = Severity | \"none\";\n"],"mappings":"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","names":["existsSync","resolve","join","readFileSync","resolve","parsed","readFileSync","resolve","yaml","resolve","existsSync","join"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trawly",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
4
4
|
"description": "Dependency sanity scanner for npm projects. Scans installed package versions against OSV and reports known vulnerabilities.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -45,10 +45,12 @@
|
|
|
45
45
|
],
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"commander": "^12.1.0",
|
|
48
|
+
"js-yaml": "^4.1.1",
|
|
48
49
|
"kleur": "^4.1.5",
|
|
49
50
|
"zod": "^3.23.8"
|
|
50
51
|
},
|
|
51
52
|
"devDependencies": {
|
|
53
|
+
"@types/js-yaml": "^4.0.9",
|
|
52
54
|
"@types/node": "^20.14.0",
|
|
53
55
|
"tsup": "^8.3.0",
|
|
54
56
|
"tsx": "^4.19.0",
|