@lockhawk/core 0.2.4 → 0.2.6
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/README.md +7 -7
- package/dist/index.d.ts +10 -5
- package/dist/index.js +38 -2335
- package/package.json +3 -4
- package/dist/index.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,2174 +1,4 @@
|
|
|
1
|
-
// src/types.ts
|
|
2
|
-
var SEVERITY_RANK = {
|
|
3
|
-
none: 0,
|
|
4
|
-
unknown: 1,
|
|
5
|
-
low: 2,
|
|
6
|
-
medium: 3,
|
|
7
|
-
high: 4,
|
|
8
|
-
critical: 5
|
|
9
|
-
};
|
|
10
|
-
function severityAtLeast(level, threshold) {
|
|
11
|
-
return SEVERITY_RANK[level] >= SEVERITY_RANK[threshold];
|
|
12
|
-
}
|
|
13
|
-
function pkgKey(name, version) {
|
|
14
|
-
return `${name}@${version}`;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
// src/lockfiles/normalize.ts
|
|
18
|
-
import { resolve } from "path";
|
|
19
|
-
|
|
20
|
-
// src/lockfiles/detect.ts
|
|
21
|
-
import { existsSync, readFileSync, statSync } from "fs";
|
|
22
|
-
import { join } from "path";
|
|
23
|
-
var LOCKFILES = [
|
|
24
|
-
{ filename: "pnpm-lock.yaml", manager: "pnpm" },
|
|
25
|
-
{ filename: "yarn.lock", manager: "yarn" },
|
|
26
|
-
{ filename: "package-lock.json", manager: "npm" },
|
|
27
|
-
{ filename: "npm-shrinkwrap.json", manager: "npm" }
|
|
28
|
-
];
|
|
29
|
-
function detectLockfile(dir) {
|
|
30
|
-
for (const { filename, manager } of LOCKFILES) {
|
|
31
|
-
const path = join(dir, filename);
|
|
32
|
-
if (existsSync(path) && statSync(path).isFile()) {
|
|
33
|
-
return { manager, path, filename };
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
return void 0;
|
|
37
|
-
}
|
|
38
|
-
function readRootManifest(dir) {
|
|
39
|
-
const path = join(dir, "package.json");
|
|
40
|
-
let raw = {};
|
|
41
|
-
if (existsSync(path)) {
|
|
42
|
-
try {
|
|
43
|
-
raw = JSON.parse(readFileSync(path, "utf8"));
|
|
44
|
-
} catch {
|
|
45
|
-
raw = {};
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
const asMap = (v) => v && typeof v === "object" ? v : {};
|
|
49
|
-
return {
|
|
50
|
-
name: typeof raw.name === "string" ? raw.name : "project",
|
|
51
|
-
version: typeof raw.version === "string" ? raw.version : void 0,
|
|
52
|
-
dependencies: asMap(raw.dependencies),
|
|
53
|
-
devDependencies: asMap(raw.devDependencies),
|
|
54
|
-
optionalDependencies: asMap(raw.optionalDependencies),
|
|
55
|
-
peerDependencies: asMap(raw.peerDependencies)
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// src/lockfiles/raw.ts
|
|
60
|
-
function reachable(start, nodes) {
|
|
61
|
-
const seen = /* @__PURE__ */ new Set();
|
|
62
|
-
const queue = [];
|
|
63
|
-
for (const k of start) {
|
|
64
|
-
if (!seen.has(k)) {
|
|
65
|
-
seen.add(k);
|
|
66
|
-
queue.push(k);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
for (let i = 0; i < queue.length; i++) {
|
|
70
|
-
const node = nodes[queue[i]];
|
|
71
|
-
if (!node) continue;
|
|
72
|
-
for (const dep of node.dependencies) {
|
|
73
|
-
if (!seen.has(dep)) {
|
|
74
|
-
seen.add(dep);
|
|
75
|
-
queue.push(dep);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
return seen;
|
|
80
|
-
}
|
|
81
|
-
function buildDependencyGraph(raw) {
|
|
82
|
-
const prodReach = reachable(raw.directProd, raw.nodes);
|
|
83
|
-
const optionalReach = reachable(raw.directOptional, raw.nodes);
|
|
84
|
-
const devReach = reachable(raw.directDev, raw.nodes);
|
|
85
|
-
const direct = /* @__PURE__ */ new Set([...raw.directProd, ...raw.directOptional, ...raw.directDev]);
|
|
86
|
-
const nodes = {};
|
|
87
|
-
for (const [key, rn] of Object.entries(raw.nodes)) {
|
|
88
|
-
let scope;
|
|
89
|
-
if (prodReach.has(key)) scope = "prod";
|
|
90
|
-
else if (optionalReach.has(key)) scope = "optional";
|
|
91
|
-
else if (devReach.has(key)) scope = "dev";
|
|
92
|
-
else scope = "prod";
|
|
93
|
-
nodes[key] = {
|
|
94
|
-
key,
|
|
95
|
-
name: rn.name,
|
|
96
|
-
version: rn.version,
|
|
97
|
-
scope,
|
|
98
|
-
direct: direct.has(key),
|
|
99
|
-
dependencies: dedupe(rn.dependencies),
|
|
100
|
-
...rn.unscannable ? { unscannable: rn.unscannable } : {}
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
return {
|
|
104
|
-
manager: raw.manager,
|
|
105
|
-
lockfilePath: raw.lockfilePath,
|
|
106
|
-
lockfileVersion: raw.lockfileVersion,
|
|
107
|
-
root: raw.root,
|
|
108
|
-
nodes,
|
|
109
|
-
directKeys: [...direct].filter((k) => nodes[k] !== void 0)
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
function dedupe(keys) {
|
|
113
|
-
return keys.length > 1 ? [...new Set(keys)] : keys;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// src/lockfiles/npm.ts
|
|
117
|
-
import { readFileSync as readFileSync2 } from "fs";
|
|
118
|
-
import { dirname } from "path";
|
|
119
|
-
function parseNpm(lockfilePath) {
|
|
120
|
-
const data = JSON.parse(readFileSync2(lockfilePath, "utf8"));
|
|
121
|
-
if (data.packages) {
|
|
122
|
-
return parsePackages(data, lockfilePath);
|
|
123
|
-
}
|
|
124
|
-
return parseLegacy(data, lockfilePath);
|
|
125
|
-
}
|
|
126
|
-
function nameFromPath(p) {
|
|
127
|
-
const marker = "node_modules/";
|
|
128
|
-
const idx = p.lastIndexOf(marker);
|
|
129
|
-
return idx === -1 ? p : p.slice(idx + marker.length);
|
|
130
|
-
}
|
|
131
|
-
function parsePackages(data, lockfilePath) {
|
|
132
|
-
const packages = data.packages ?? {};
|
|
133
|
-
const rootEntry = packages[""] ?? {};
|
|
134
|
-
const root = {
|
|
135
|
-
name: rootEntry.name ?? data.name ?? "project",
|
|
136
|
-
version: rootEntry.version ?? data.version
|
|
137
|
-
};
|
|
138
|
-
const nodes = /* @__PURE__ */ Object.create(null);
|
|
139
|
-
const unscannable = [];
|
|
140
|
-
const pathToKey = /* @__PURE__ */ Object.create(null);
|
|
141
|
-
for (const [p, entry] of Object.entries(packages)) {
|
|
142
|
-
if (p === "" || entry.link) continue;
|
|
143
|
-
const name = entry.name ?? nameFromPath(p);
|
|
144
|
-
const version = entry.version;
|
|
145
|
-
if (!version) continue;
|
|
146
|
-
const key = pkgKey(name, version);
|
|
147
|
-
pathToKey[p] = key;
|
|
148
|
-
if (!nodes[key]) {
|
|
149
|
-
const node = { key, name, version, dependencies: [] };
|
|
150
|
-
if (!p.includes("node_modules")) {
|
|
151
|
-
node.unscannable = { reason: "local workspace package" };
|
|
152
|
-
unscannable.push({ name, version, reason: "local workspace package" });
|
|
153
|
-
}
|
|
154
|
-
nodes[key] = node;
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
const resolveDep = (fromPath, depName) => {
|
|
158
|
-
let prefix = fromPath;
|
|
159
|
-
for (; ; ) {
|
|
160
|
-
const candidate = `${prefix ? `${prefix}/` : ""}node_modules/${depName}`;
|
|
161
|
-
const entry = packages[candidate];
|
|
162
|
-
if (entry) {
|
|
163
|
-
if (entry.link && typeof entry.resolved === "string") return entry.resolved;
|
|
164
|
-
return candidate;
|
|
165
|
-
}
|
|
166
|
-
if (!prefix) return void 0;
|
|
167
|
-
const i = prefix.lastIndexOf("/node_modules/");
|
|
168
|
-
prefix = i === -1 ? "" : prefix.slice(0, i);
|
|
169
|
-
}
|
|
170
|
-
};
|
|
171
|
-
const directProd = /* @__PURE__ */ new Set();
|
|
172
|
-
const directDev = /* @__PURE__ */ new Set();
|
|
173
|
-
const directOptional = /* @__PURE__ */ new Set();
|
|
174
|
-
for (const [p, entry] of Object.entries(packages)) {
|
|
175
|
-
if (entry.link) continue;
|
|
176
|
-
const fromKey = p === "" ? void 0 : pathToKey[p];
|
|
177
|
-
const isLocalRoot = p === "" || !p.includes("node_modules");
|
|
178
|
-
const addEdges = (names, bucket) => {
|
|
179
|
-
for (const depName of names) {
|
|
180
|
-
const targetPath = resolveDep(p, depName);
|
|
181
|
-
if (!targetPath) continue;
|
|
182
|
-
const targetKey = pathToKey[targetPath];
|
|
183
|
-
if (!targetKey) continue;
|
|
184
|
-
if (fromKey && fromKey !== targetKey) nodes[fromKey].dependencies.push(targetKey);
|
|
185
|
-
if (bucket) bucket.add(targetKey);
|
|
186
|
-
}
|
|
187
|
-
};
|
|
188
|
-
addEdges(Object.keys(entry.dependencies ?? {}), isLocalRoot ? directProd : void 0);
|
|
189
|
-
addEdges(
|
|
190
|
-
Object.keys(entry.optionalDependencies ?? {}),
|
|
191
|
-
isLocalRoot ? directOptional : void 0
|
|
192
|
-
);
|
|
193
|
-
if (isLocalRoot) {
|
|
194
|
-
addEdges(Object.keys(entry.devDependencies ?? {}), directDev);
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
return {
|
|
198
|
-
manager: "npm",
|
|
199
|
-
lockfilePath,
|
|
200
|
-
lockfileVersion: data.lockfileVersion,
|
|
201
|
-
root,
|
|
202
|
-
nodes,
|
|
203
|
-
directProd: [...directProd],
|
|
204
|
-
directDev: [...directDev],
|
|
205
|
-
directOptional: [...directOptional],
|
|
206
|
-
unscannable
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
|
-
function parseLegacy(data, lockfilePath) {
|
|
210
|
-
const manifest = readRootManifest(dirname(lockfilePath));
|
|
211
|
-
const root = { name: data.name ?? manifest.name, version: data.version ?? manifest.version };
|
|
212
|
-
const nodes = /* @__PURE__ */ Object.create(null);
|
|
213
|
-
const buildLevel = (deps) => {
|
|
214
|
-
const m2 = /* @__PURE__ */ new Map();
|
|
215
|
-
for (const [name, info] of Object.entries(deps ?? {})) {
|
|
216
|
-
if (info?.version) m2.set(name, info.version);
|
|
217
|
-
}
|
|
218
|
-
return m2;
|
|
219
|
-
};
|
|
220
|
-
const resolveFromChain = (name, chain) => {
|
|
221
|
-
for (let i = chain.length - 1; i >= 0; i--) {
|
|
222
|
-
const v = chain[i].get(name);
|
|
223
|
-
if (v) return v;
|
|
224
|
-
}
|
|
225
|
-
return void 0;
|
|
226
|
-
};
|
|
227
|
-
const visit = (deps, ancestors) => {
|
|
228
|
-
const level = buildLevel(deps);
|
|
229
|
-
const chain = [...ancestors, level];
|
|
230
|
-
for (const [name, info] of Object.entries(deps ?? {})) {
|
|
231
|
-
const version = info?.version;
|
|
232
|
-
if (!version) continue;
|
|
233
|
-
const key = pkgKey(name, version);
|
|
234
|
-
if (!nodes[key]) nodes[key] = { key, name, version, dependencies: [] };
|
|
235
|
-
const ownChain = [...chain, buildLevel(info.dependencies)];
|
|
236
|
-
for (const reqName of Object.keys(info.requires ?? {})) {
|
|
237
|
-
const v = resolveFromChain(reqName, ownChain);
|
|
238
|
-
if (v) {
|
|
239
|
-
const targetKey = pkgKey(reqName, v);
|
|
240
|
-
if (targetKey !== key) nodes[key].dependencies.push(targetKey);
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
if (info.dependencies) visit(info.dependencies, chain);
|
|
244
|
-
}
|
|
245
|
-
};
|
|
246
|
-
visit(data.dependencies, []);
|
|
247
|
-
const topLevel = buildLevel(data.dependencies);
|
|
248
|
-
const directProd = [];
|
|
249
|
-
const directDev = [];
|
|
250
|
-
const directOptional = [];
|
|
251
|
-
const classify = (names, out) => {
|
|
252
|
-
for (const name of names) {
|
|
253
|
-
const v = topLevel.get(name);
|
|
254
|
-
if (v) out.push(pkgKey(name, v));
|
|
255
|
-
}
|
|
256
|
-
};
|
|
257
|
-
classify(Object.keys(manifest.dependencies), directProd);
|
|
258
|
-
classify(Object.keys(manifest.optionalDependencies), directOptional);
|
|
259
|
-
classify(Object.keys(manifest.devDependencies), directDev);
|
|
260
|
-
return {
|
|
261
|
-
manager: "npm",
|
|
262
|
-
lockfilePath,
|
|
263
|
-
lockfileVersion: data.lockfileVersion,
|
|
264
|
-
root,
|
|
265
|
-
nodes,
|
|
266
|
-
directProd,
|
|
267
|
-
directDev,
|
|
268
|
-
directOptional,
|
|
269
|
-
unscannable: []
|
|
270
|
-
};
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
// src/lockfiles/yarn.ts
|
|
274
|
-
import { readFileSync as readFileSync3 } from "fs";
|
|
275
|
-
import { dirname as dirname2 } from "path";
|
|
276
|
-
import { createRequire } from "module";
|
|
277
|
-
import yaml from "js-yaml";
|
|
278
|
-
var require2 = createRequire(import.meta.url);
|
|
279
|
-
var yarnLockfile = require2("@yarnpkg/lockfile");
|
|
280
|
-
function parseYarn(lockfilePath) {
|
|
281
|
-
const content = readFileSync3(lockfilePath, "utf8");
|
|
282
|
-
const manifest = readRootManifest(dirname2(lockfilePath));
|
|
283
|
-
const isBerry = content.includes("__metadata");
|
|
284
|
-
const { descriptorToVersion, entryByVersion, lockfileVersion } = isBerry ? parseBerry(content) : parseClassic(content);
|
|
285
|
-
return assemble(
|
|
286
|
-
lockfilePath,
|
|
287
|
-
manifest,
|
|
288
|
-
descriptorToVersion,
|
|
289
|
-
entryByVersion,
|
|
290
|
-
lockfileVersion,
|
|
291
|
-
isBerry
|
|
292
|
-
);
|
|
293
|
-
}
|
|
294
|
-
function splitDescriptor(descriptor) {
|
|
295
|
-
const at = descriptor.lastIndexOf("@");
|
|
296
|
-
if (at <= 0) return void 0;
|
|
297
|
-
return { name: descriptor.slice(0, at), range: descriptor.slice(at + 1) };
|
|
298
|
-
}
|
|
299
|
-
function parseClassic(content) {
|
|
300
|
-
const parsed = yarnLockfile.parse(content);
|
|
301
|
-
const descriptorToVersion = /* @__PURE__ */ new Map();
|
|
302
|
-
const entryByVersion = /* @__PURE__ */ new Map();
|
|
303
|
-
for (const [rawKey, entry] of Object.entries(parsed.object)) {
|
|
304
|
-
if (!entry.version) continue;
|
|
305
|
-
for (const descriptor of rawKey.split(/,\s*/)) {
|
|
306
|
-
const split = splitDescriptor(descriptor);
|
|
307
|
-
if (split) descriptorToVersion.set(`${split.name}@${split.range}`, entry.version);
|
|
308
|
-
if (split) entryByVersion.set(pkgKey(split.name, entry.version), entry);
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
return { descriptorToVersion, entryByVersion, lockfileVersion: 1 };
|
|
312
|
-
}
|
|
313
|
-
function parseBerry(content) {
|
|
314
|
-
const doc = yaml.load(content) ?? {};
|
|
315
|
-
const descriptorToVersion = /* @__PURE__ */ new Map();
|
|
316
|
-
const entryByVersion = /* @__PURE__ */ new Map();
|
|
317
|
-
let lockfileVersion = "berry";
|
|
318
|
-
for (const [rawKey, entry] of Object.entries(doc)) {
|
|
319
|
-
if (rawKey === "__metadata") {
|
|
320
|
-
const v = entry.version;
|
|
321
|
-
if (v !== void 0) lockfileVersion = v;
|
|
322
|
-
continue;
|
|
323
|
-
}
|
|
324
|
-
if (!entry.version) continue;
|
|
325
|
-
for (const descriptor of rawKey.split(/,\s*/)) {
|
|
326
|
-
const split = splitDescriptor(descriptor);
|
|
327
|
-
if (!split) continue;
|
|
328
|
-
descriptorToVersion.set(`${split.name}@${split.range}`, entry.version);
|
|
329
|
-
entryByVersion.set(pkgKey(split.name, entry.version), entry);
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
return { descriptorToVersion, entryByVersion, lockfileVersion };
|
|
333
|
-
}
|
|
334
|
-
function assemble(lockfilePath, manifest, descriptorToVersion, entryByVersion, lockfileVersion, isBerry) {
|
|
335
|
-
const resolve3 = (name, range) => {
|
|
336
|
-
const candidates = isBerry ? [`${name}@${range}`, `${name}@npm:${range}`] : [`${name}@${range}`];
|
|
337
|
-
for (const c of candidates) {
|
|
338
|
-
const v = descriptorToVersion.get(c);
|
|
339
|
-
if (v) return v;
|
|
340
|
-
}
|
|
341
|
-
return void 0;
|
|
342
|
-
};
|
|
343
|
-
const nodes = /* @__PURE__ */ Object.create(null);
|
|
344
|
-
for (const [key, entry] of entryByVersion) {
|
|
345
|
-
const at = key.lastIndexOf("@");
|
|
346
|
-
const name = key.slice(0, at);
|
|
347
|
-
const version = key.slice(at + 1);
|
|
348
|
-
if (!nodes[key]) nodes[key] = { key, name, version, dependencies: [] };
|
|
349
|
-
const addEdges = (deps) => {
|
|
350
|
-
for (const [depName, depRange] of Object.entries(deps ?? {})) {
|
|
351
|
-
const v = resolve3(depName, depRange);
|
|
352
|
-
if (!v) continue;
|
|
353
|
-
const targetKey = pkgKey(depName, v);
|
|
354
|
-
if (targetKey !== key) nodes[key].dependencies.push(targetKey);
|
|
355
|
-
}
|
|
356
|
-
};
|
|
357
|
-
addEdges(entry.dependencies);
|
|
358
|
-
addEdges(entry.optionalDependencies);
|
|
359
|
-
}
|
|
360
|
-
const toDirect = (deps) => {
|
|
361
|
-
const out = [];
|
|
362
|
-
for (const [name, range] of Object.entries(deps)) {
|
|
363
|
-
const v = resolve3(name, range);
|
|
364
|
-
if (v) out.push(pkgKey(name, v));
|
|
365
|
-
}
|
|
366
|
-
return out;
|
|
367
|
-
};
|
|
368
|
-
return {
|
|
369
|
-
manager: "yarn",
|
|
370
|
-
lockfilePath,
|
|
371
|
-
lockfileVersion,
|
|
372
|
-
root: { name: manifest.name, version: manifest.version },
|
|
373
|
-
nodes,
|
|
374
|
-
directProd: toDirect(manifest.dependencies),
|
|
375
|
-
directDev: toDirect(manifest.devDependencies),
|
|
376
|
-
directOptional: toDirect(manifest.optionalDependencies),
|
|
377
|
-
unscannable: []
|
|
378
|
-
};
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
// src/lockfiles/pnpm.ts
|
|
382
|
-
import { readFileSync as readFileSync4 } from "fs";
|
|
383
|
-
import { dirname as dirname3 } from "path";
|
|
384
|
-
import yaml2 from "js-yaml";
|
|
385
|
-
function parsePnpm(lockfilePath) {
|
|
386
|
-
const doc = yaml2.load(readFileSync4(lockfilePath, "utf8")) ?? {};
|
|
387
|
-
const manifest = readRootManifest(dirname3(lockfilePath));
|
|
388
|
-
const major = majorVersion(doc.lockfileVersion);
|
|
389
|
-
const nodes = /* @__PURE__ */ Object.create(null);
|
|
390
|
-
for (const key of Object.keys(doc.packages ?? {})) {
|
|
391
|
-
const parsed = parseKey(key, major);
|
|
392
|
-
if (!parsed) continue;
|
|
393
|
-
const nodeKey = pkgKey(parsed.name, parsed.version);
|
|
394
|
-
if (!nodes[nodeKey]) {
|
|
395
|
-
nodes[nodeKey] = {
|
|
396
|
-
key: nodeKey,
|
|
397
|
-
name: parsed.name,
|
|
398
|
-
version: parsed.version,
|
|
399
|
-
dependencies: []
|
|
400
|
-
};
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
const edgeSource = major >= 9 ? doc.snapshots ?? {} : doc.packages ?? {};
|
|
404
|
-
for (const [key, entry] of Object.entries(edgeSource)) {
|
|
405
|
-
const parsed = parseKey(key, major);
|
|
406
|
-
if (!parsed) continue;
|
|
407
|
-
const fromKey = pkgKey(parsed.name, parsed.version);
|
|
408
|
-
if (!nodes[fromKey]) {
|
|
409
|
-
nodes[fromKey] = {
|
|
410
|
-
key: fromKey,
|
|
411
|
-
name: parsed.name,
|
|
412
|
-
version: parsed.version,
|
|
413
|
-
dependencies: []
|
|
414
|
-
};
|
|
415
|
-
}
|
|
416
|
-
const addEdges = (deps) => {
|
|
417
|
-
for (const [depName, depValue] of Object.entries(deps ?? {})) {
|
|
418
|
-
const targetKey = pkgKey(depName, stripVersion(depValue, major));
|
|
419
|
-
if (targetKey !== fromKey && nodes[targetKey]) nodes[fromKey].dependencies.push(targetKey);
|
|
420
|
-
}
|
|
421
|
-
};
|
|
422
|
-
addEdges(entry.dependencies);
|
|
423
|
-
addEdges(entry.optionalDependencies);
|
|
424
|
-
}
|
|
425
|
-
const importers = doc.importers ?? {
|
|
426
|
-
".": {
|
|
427
|
-
dependencies: doc.dependencies,
|
|
428
|
-
devDependencies: doc.devDependencies,
|
|
429
|
-
optionalDependencies: doc.optionalDependencies
|
|
430
|
-
}
|
|
431
|
-
};
|
|
432
|
-
const directProd = /* @__PURE__ */ new Set();
|
|
433
|
-
const directDev = /* @__PURE__ */ new Set();
|
|
434
|
-
const directOptional = /* @__PURE__ */ new Set();
|
|
435
|
-
const collect = (deps, into) => {
|
|
436
|
-
for (const [name, val] of Object.entries(deps ?? {})) {
|
|
437
|
-
const version = importerVersion(val, major);
|
|
438
|
-
if (!version) continue;
|
|
439
|
-
const key = pkgKey(name, version);
|
|
440
|
-
if (nodes[key]) into.add(key);
|
|
441
|
-
}
|
|
442
|
-
};
|
|
443
|
-
for (const imp of Object.values(importers)) {
|
|
444
|
-
collect(imp.dependencies, directProd);
|
|
445
|
-
collect(imp.optionalDependencies, directOptional);
|
|
446
|
-
collect(imp.devDependencies, directDev);
|
|
447
|
-
}
|
|
448
|
-
return {
|
|
449
|
-
manager: "pnpm",
|
|
450
|
-
lockfilePath,
|
|
451
|
-
lockfileVersion: doc.lockfileVersion,
|
|
452
|
-
root: { name: manifest.name, version: manifest.version },
|
|
453
|
-
nodes,
|
|
454
|
-
directProd: [...directProd],
|
|
455
|
-
directDev: [...directDev],
|
|
456
|
-
directOptional: [...directOptional],
|
|
457
|
-
unscannable: []
|
|
458
|
-
};
|
|
459
|
-
}
|
|
460
|
-
function majorVersion(v) {
|
|
461
|
-
const n = parseFloat(String(v ?? "9"));
|
|
462
|
-
return Number.isNaN(n) ? 9 : Math.floor(n);
|
|
463
|
-
}
|
|
464
|
-
function parseKey(key, major) {
|
|
465
|
-
const k = key.startsWith("/") ? key.slice(1) : key;
|
|
466
|
-
if (major < 6) {
|
|
467
|
-
const lastSlash = k.lastIndexOf("/");
|
|
468
|
-
if (lastSlash <= 0) return void 0;
|
|
469
|
-
const name2 = k.slice(0, lastSlash);
|
|
470
|
-
const version2 = stripVersion(k.slice(lastSlash + 1), major);
|
|
471
|
-
return version2 ? { name: name2, version: version2 } : void 0;
|
|
472
|
-
}
|
|
473
|
-
const at = k.lastIndexOf("@");
|
|
474
|
-
if (at <= 0) return void 0;
|
|
475
|
-
const name = k.slice(0, at);
|
|
476
|
-
const version = stripVersion(k.slice(at + 1), major);
|
|
477
|
-
return version ? { name, version } : void 0;
|
|
478
|
-
}
|
|
479
|
-
function stripVersion(value, major) {
|
|
480
|
-
let v = value;
|
|
481
|
-
if (major < 6) {
|
|
482
|
-
const us = v.indexOf("_");
|
|
483
|
-
if (us !== -1) v = v.slice(0, us);
|
|
484
|
-
}
|
|
485
|
-
const paren = v.indexOf("(");
|
|
486
|
-
if (paren !== -1) v = v.slice(0, paren);
|
|
487
|
-
return v;
|
|
488
|
-
}
|
|
489
|
-
function importerVersion(val, major) {
|
|
490
|
-
let ref;
|
|
491
|
-
if (typeof val === "string") ref = val;
|
|
492
|
-
else if (val && typeof val === "object" && typeof val.version === "string") {
|
|
493
|
-
ref = val.version;
|
|
494
|
-
}
|
|
495
|
-
if (!ref) return void 0;
|
|
496
|
-
if (ref.startsWith("link:") || ref.startsWith("file:") || ref.startsWith("workspace:"))
|
|
497
|
-
return void 0;
|
|
498
|
-
return stripVersion(ref, major);
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
// src/lockfiles/normalize.ts
|
|
502
|
-
var LockfileError = class extends Error {
|
|
503
|
-
constructor(message) {
|
|
504
|
-
super(message);
|
|
505
|
-
this.name = "LockfileError";
|
|
506
|
-
}
|
|
507
|
-
};
|
|
508
|
-
function parseLockfile(detected) {
|
|
509
|
-
try {
|
|
510
|
-
switch (detected.manager) {
|
|
511
|
-
case "npm":
|
|
512
|
-
return parseNpm(detected.path);
|
|
513
|
-
case "yarn":
|
|
514
|
-
return parseYarn(detected.path);
|
|
515
|
-
case "pnpm":
|
|
516
|
-
return parsePnpm(detected.path);
|
|
517
|
-
}
|
|
518
|
-
} catch (err) {
|
|
519
|
-
throw new LockfileError(
|
|
520
|
-
`Failed to parse ${detected.filename}: ${err instanceof Error ? err.message : String(err)}`
|
|
521
|
-
);
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
function loadDependencyGraph(dir) {
|
|
525
|
-
const detected = detectLockfile(resolve(dir));
|
|
526
|
-
if (!detected) {
|
|
527
|
-
throw new LockfileError(
|
|
528
|
-
`No lockfile found in ${dir}. Expected one of: package-lock.json, yarn.lock, pnpm-lock.yaml.`
|
|
529
|
-
);
|
|
530
|
-
}
|
|
531
|
-
return buildDependencyGraph(parseLockfile(detected));
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
// src/graph/paths.ts
|
|
535
|
-
function rootLabel(graph) {
|
|
536
|
-
return graph.root.version ? `${graph.root.name}@${graph.root.version}` : graph.root.name;
|
|
537
|
-
}
|
|
538
|
-
function shortestPath(graph, targetKey) {
|
|
539
|
-
if (!graph.nodes[targetKey]) return null;
|
|
540
|
-
const VIRTUAL_ROOT = "\0root";
|
|
541
|
-
const prev = /* @__PURE__ */ new Map();
|
|
542
|
-
const visited = /* @__PURE__ */ new Set([VIRTUAL_ROOT]);
|
|
543
|
-
const queue = [VIRTUAL_ROOT];
|
|
544
|
-
for (let i = 0; i < queue.length; i++) {
|
|
545
|
-
const current = queue[i];
|
|
546
|
-
const neighbors = current === VIRTUAL_ROOT ? graph.directKeys : graph.nodes[current]?.dependencies ?? [];
|
|
547
|
-
for (const next of neighbors) {
|
|
548
|
-
if (visited.has(next)) continue;
|
|
549
|
-
visited.add(next);
|
|
550
|
-
prev.set(next, current);
|
|
551
|
-
if (next === targetKey) return reconstruct(graph, prev, targetKey, VIRTUAL_ROOT);
|
|
552
|
-
queue.push(next);
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
return [rootLabel(graph), targetKey];
|
|
556
|
-
}
|
|
557
|
-
function reconstruct(graph, prev, target, virtualRoot) {
|
|
558
|
-
const path = [];
|
|
559
|
-
let cursor = target;
|
|
560
|
-
while (cursor !== void 0 && cursor !== virtualRoot) {
|
|
561
|
-
path.unshift(cursor);
|
|
562
|
-
cursor = prev.get(cursor);
|
|
563
|
-
}
|
|
564
|
-
path.unshift(rootLabel(graph));
|
|
565
|
-
return path;
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
// src/match/ranges.ts
|
|
569
|
-
import semver from "semver";
|
|
570
|
-
function isVersionAffected(version, affected) {
|
|
571
|
-
if (affected.versions?.includes(version)) return true;
|
|
572
|
-
const installed = semver.parse(version, { loose: true });
|
|
573
|
-
if (!installed) return false;
|
|
574
|
-
for (const range of affected.ranges ?? []) {
|
|
575
|
-
if (range.type === "GIT") continue;
|
|
576
|
-
if (rangeAffects(installed, range.events)) return true;
|
|
577
|
-
}
|
|
578
|
-
return false;
|
|
579
|
-
}
|
|
580
|
-
function rangeAffects(installed, events) {
|
|
581
|
-
const boundaries = [];
|
|
582
|
-
for (const event of events) {
|
|
583
|
-
if (event.introduced !== void 0) {
|
|
584
|
-
boundaries.push({
|
|
585
|
-
kind: "introduced",
|
|
586
|
-
version: event.introduced === "0" ? null : parse(event.introduced)
|
|
587
|
-
});
|
|
588
|
-
} else if (event.fixed !== void 0) {
|
|
589
|
-
boundaries.push({ kind: "fixed", version: parse(event.fixed) });
|
|
590
|
-
} else if (event.last_affected !== void 0) {
|
|
591
|
-
boundaries.push({ kind: "last_affected", version: parse(event.last_affected) });
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
boundaries.sort((a, b) => {
|
|
595
|
-
if (a.version === null) return b.version === null ? 0 : -1;
|
|
596
|
-
if (b.version === null) return 1;
|
|
597
|
-
return a.version.compare(b.version);
|
|
598
|
-
});
|
|
599
|
-
let affected = false;
|
|
600
|
-
for (const boundary of boundaries) {
|
|
601
|
-
if (boundary.version === null) {
|
|
602
|
-
affected = true;
|
|
603
|
-
continue;
|
|
604
|
-
}
|
|
605
|
-
const cmp = installed.compare(boundary.version);
|
|
606
|
-
switch (boundary.kind) {
|
|
607
|
-
case "introduced":
|
|
608
|
-
if (cmp >= 0) affected = true;
|
|
609
|
-
break;
|
|
610
|
-
case "fixed":
|
|
611
|
-
if (cmp >= 0) affected = false;
|
|
612
|
-
break;
|
|
613
|
-
case "last_affected":
|
|
614
|
-
if (cmp > 0) affected = false;
|
|
615
|
-
break;
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
return affected;
|
|
619
|
-
}
|
|
620
|
-
function parse(version) {
|
|
621
|
-
return semver.parse(version, { loose: true }) ?? semver.coerce(version);
|
|
622
|
-
}
|
|
623
|
-
function vulnerabilityAffects(name, version, vuln) {
|
|
624
|
-
if (vuln.withdrawn) return { affected: false, fixedVersions: [] };
|
|
625
|
-
let affected = false;
|
|
626
|
-
const fixed = /* @__PURE__ */ new Set();
|
|
627
|
-
for (const entry of vuln.affected ?? []) {
|
|
628
|
-
const ecosystem = entry.package?.ecosystem;
|
|
629
|
-
if (ecosystem && ecosystem !== "npm") continue;
|
|
630
|
-
if (entry.package?.name && entry.package.name !== name) continue;
|
|
631
|
-
if (!isVersionAffected(version, entry)) continue;
|
|
632
|
-
affected = true;
|
|
633
|
-
for (const range of entry.ranges ?? []) {
|
|
634
|
-
for (const event of range.events) {
|
|
635
|
-
if (event.fixed) fixed.add(event.fixed);
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
}
|
|
639
|
-
return { affected, fixedVersions: [...fixed] };
|
|
640
|
-
}
|
|
641
|
-
function nearestFix(installed, fixedVersions) {
|
|
642
|
-
const current = semver.parse(installed, { loose: true });
|
|
643
|
-
const candidates = fixedVersions.map((v) => semver.parse(v, { loose: true }) ?? semver.coerce(v)).filter((v) => v !== null).filter((v) => current ? v.compare(current) > 0 : true).sort((a, b) => a.compare(b));
|
|
644
|
-
return candidates[0]?.version;
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
// src/match/dedupe.ts
|
|
648
|
-
function tokensOf(vuln) {
|
|
649
|
-
return [vuln.id, ...vuln.aliases ?? []];
|
|
650
|
-
}
|
|
651
|
-
function canonicalId(ids) {
|
|
652
|
-
const list = [...ids];
|
|
653
|
-
return list.find((id) => id.startsWith("CVE-")) ?? list.find((id) => id.startsWith("GHSA-")) ?? list[0];
|
|
654
|
-
}
|
|
655
|
-
function dedupeVulnerabilities(vulns) {
|
|
656
|
-
if (vulns.length <= 1) return vulns;
|
|
657
|
-
const parent = /* @__PURE__ */ new Map();
|
|
658
|
-
const find = (x) => {
|
|
659
|
-
let root = x;
|
|
660
|
-
while (parent.get(root) !== void 0 && parent.get(root) !== root) root = parent.get(root);
|
|
661
|
-
parent.set(x, root);
|
|
662
|
-
return root;
|
|
663
|
-
};
|
|
664
|
-
const union = (a, b) => {
|
|
665
|
-
parent.set(find(a), find(b));
|
|
666
|
-
};
|
|
667
|
-
for (const vuln of vulns) {
|
|
668
|
-
const tokens = tokensOf(vuln);
|
|
669
|
-
if (!parent.has(tokens[0])) parent.set(tokens[0], tokens[0]);
|
|
670
|
-
for (const token of tokens.slice(1)) {
|
|
671
|
-
if (!parent.has(token)) parent.set(token, token);
|
|
672
|
-
union(tokens[0], token);
|
|
673
|
-
}
|
|
674
|
-
}
|
|
675
|
-
const groups = /* @__PURE__ */ new Map();
|
|
676
|
-
for (const vuln of vulns) {
|
|
677
|
-
const root = find(vuln.id);
|
|
678
|
-
const group = groups.get(root);
|
|
679
|
-
if (group) group.push(vuln);
|
|
680
|
-
else groups.set(root, [vuln]);
|
|
681
|
-
}
|
|
682
|
-
return [...groups.values()].map(mergeGroup);
|
|
683
|
-
}
|
|
684
|
-
function mergeGroup(group) {
|
|
685
|
-
if (group.length === 1) return group[0];
|
|
686
|
-
const allIds = /* @__PURE__ */ new Set();
|
|
687
|
-
const references = /* @__PURE__ */ new Map();
|
|
688
|
-
const severity = [];
|
|
689
|
-
const affected = [];
|
|
690
|
-
let summary;
|
|
691
|
-
let details;
|
|
692
|
-
let database_specific;
|
|
693
|
-
for (const vuln of group) {
|
|
694
|
-
for (const token of tokensOf(vuln)) allIds.add(token);
|
|
695
|
-
for (const ref of vuln.references ?? []) references.set(ref.url, ref);
|
|
696
|
-
for (const sev of vuln.severity ?? []) severity.push(sev);
|
|
697
|
-
for (const aff of vuln.affected ?? []) affected.push(aff);
|
|
698
|
-
summary ??= vuln.summary;
|
|
699
|
-
details ??= vuln.details;
|
|
700
|
-
database_specific ??= vuln.database_specific;
|
|
701
|
-
}
|
|
702
|
-
const id = canonicalId(allIds);
|
|
703
|
-
return {
|
|
704
|
-
id,
|
|
705
|
-
aliases: [...allIds].filter((token) => token !== id),
|
|
706
|
-
summary,
|
|
707
|
-
details,
|
|
708
|
-
references: [...references.values()],
|
|
709
|
-
severity,
|
|
710
|
-
affected,
|
|
711
|
-
database_specific
|
|
712
|
-
};
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
// src/match/findings.ts
|
|
716
|
-
import semver2 from "semver";
|
|
717
|
-
|
|
718
|
-
// src/score/cvss4-data.ts
|
|
719
|
-
var CVSS4_LOOKUP = {
|
|
720
|
-
"100000": 9.8,
|
|
721
|
-
"100001": 9.5,
|
|
722
|
-
"100010": 9.4,
|
|
723
|
-
"100011": 8.7,
|
|
724
|
-
"100020": 9.1,
|
|
725
|
-
"100021": 8.1,
|
|
726
|
-
"100100": 9.4,
|
|
727
|
-
"100101": 8.9,
|
|
728
|
-
"100110": 8.6,
|
|
729
|
-
"100111": 7.4,
|
|
730
|
-
"100120": 7.7,
|
|
731
|
-
"100121": 6.4,
|
|
732
|
-
"100200": 8.7,
|
|
733
|
-
"100201": 7.5,
|
|
734
|
-
"100210": 7.4,
|
|
735
|
-
"100211": 6.3,
|
|
736
|
-
"100220": 6.3,
|
|
737
|
-
"100221": 4.9,
|
|
738
|
-
"101000": 9.4,
|
|
739
|
-
"101001": 8.9,
|
|
740
|
-
"101010": 8.8,
|
|
741
|
-
"101011": 7.7,
|
|
742
|
-
"101020": 7.6,
|
|
743
|
-
"101021": 6.7,
|
|
744
|
-
"101100": 8.6,
|
|
745
|
-
"101101": 7.6,
|
|
746
|
-
"101110": 7.4,
|
|
747
|
-
"101111": 5.8,
|
|
748
|
-
"101120": 5.9,
|
|
749
|
-
"101121": 5,
|
|
750
|
-
"101200": 7.2,
|
|
751
|
-
"101201": 5.7,
|
|
752
|
-
"101210": 5.7,
|
|
753
|
-
"101211": 5.2,
|
|
754
|
-
"101220": 5.2,
|
|
755
|
-
"101221": 2.5,
|
|
756
|
-
"102001": 8.3,
|
|
757
|
-
"102011": 7,
|
|
758
|
-
"102021": 5.4,
|
|
759
|
-
"102101": 6.5,
|
|
760
|
-
"102111": 5.8,
|
|
761
|
-
"102121": 2.6,
|
|
762
|
-
"102201": 5.3,
|
|
763
|
-
"102211": 2.1,
|
|
764
|
-
"102221": 1.3,
|
|
765
|
-
"110000": 9.5,
|
|
766
|
-
"110001": 9,
|
|
767
|
-
"110010": 8.8,
|
|
768
|
-
"110011": 7.6,
|
|
769
|
-
"110020": 7.6,
|
|
770
|
-
"110021": 7,
|
|
771
|
-
"110100": 9,
|
|
772
|
-
"110101": 7.7,
|
|
773
|
-
"110110": 7.5,
|
|
774
|
-
"110111": 6.2,
|
|
775
|
-
"110120": 6.1,
|
|
776
|
-
"110121": 5.3,
|
|
777
|
-
"110200": 7.7,
|
|
778
|
-
"110201": 6.6,
|
|
779
|
-
"110210": 6.8,
|
|
780
|
-
"110211": 5.9,
|
|
781
|
-
"110220": 5.2,
|
|
782
|
-
"110221": 3,
|
|
783
|
-
"111000": 8.9,
|
|
784
|
-
"111001": 7.8,
|
|
785
|
-
"111010": 7.6,
|
|
786
|
-
"111011": 6.7,
|
|
787
|
-
"111020": 6.2,
|
|
788
|
-
"111021": 5.8,
|
|
789
|
-
"111100": 7.4,
|
|
790
|
-
"111101": 5.9,
|
|
791
|
-
"111110": 5.7,
|
|
792
|
-
"111111": 5.7,
|
|
793
|
-
"111120": 4.7,
|
|
794
|
-
"111121": 2.3,
|
|
795
|
-
"111200": 6.1,
|
|
796
|
-
"111201": 5.2,
|
|
797
|
-
"111210": 5.7,
|
|
798
|
-
"111211": 2.9,
|
|
799
|
-
"111220": 2.4,
|
|
800
|
-
"111221": 1.6,
|
|
801
|
-
"112001": 7.1,
|
|
802
|
-
"112011": 5.9,
|
|
803
|
-
"112021": 3,
|
|
804
|
-
"112101": 5.8,
|
|
805
|
-
"112111": 2.6,
|
|
806
|
-
"112121": 1.5,
|
|
807
|
-
"112201": 2.3,
|
|
808
|
-
"112211": 1.3,
|
|
809
|
-
"112221": 0.6,
|
|
810
|
-
"200000": 9.3,
|
|
811
|
-
"200001": 8.7,
|
|
812
|
-
"200010": 8.6,
|
|
813
|
-
"200011": 7.2,
|
|
814
|
-
"200020": 7.5,
|
|
815
|
-
"200021": 5.8,
|
|
816
|
-
"200100": 8.6,
|
|
817
|
-
"200101": 7.4,
|
|
818
|
-
"200110": 7.4,
|
|
819
|
-
"200111": 6.1,
|
|
820
|
-
"200120": 5.6,
|
|
821
|
-
"200121": 3.4,
|
|
822
|
-
"200200": 7,
|
|
823
|
-
"200201": 5.4,
|
|
824
|
-
"200210": 5.2,
|
|
825
|
-
"200211": 4,
|
|
826
|
-
"200220": 4,
|
|
827
|
-
"200221": 2.2,
|
|
828
|
-
"201000": 8.5,
|
|
829
|
-
"201001": 7.5,
|
|
830
|
-
"201010": 7.4,
|
|
831
|
-
"201011": 5.5,
|
|
832
|
-
"201020": 6.2,
|
|
833
|
-
"201021": 5.1,
|
|
834
|
-
"201100": 7.2,
|
|
835
|
-
"201101": 5.7,
|
|
836
|
-
"201110": 5.5,
|
|
837
|
-
"201111": 4.1,
|
|
838
|
-
"201120": 4.6,
|
|
839
|
-
"201121": 1.9,
|
|
840
|
-
"201200": 5.3,
|
|
841
|
-
"201201": 3.6,
|
|
842
|
-
"201210": 3.4,
|
|
843
|
-
"201211": 1.9,
|
|
844
|
-
"201220": 1.9,
|
|
845
|
-
"201221": 0.8,
|
|
846
|
-
"202001": 6.4,
|
|
847
|
-
"202011": 5.1,
|
|
848
|
-
"202021": 2,
|
|
849
|
-
"202101": 4.7,
|
|
850
|
-
"202111": 2.1,
|
|
851
|
-
"202121": 1.1,
|
|
852
|
-
"202201": 2.4,
|
|
853
|
-
"202211": 0.9,
|
|
854
|
-
"202221": 0.4,
|
|
855
|
-
"210000": 8.8,
|
|
856
|
-
"210001": 7.5,
|
|
857
|
-
"210010": 7.3,
|
|
858
|
-
"210011": 5.3,
|
|
859
|
-
"210020": 6,
|
|
860
|
-
"210021": 5,
|
|
861
|
-
"210100": 7.3,
|
|
862
|
-
"210101": 5.5,
|
|
863
|
-
"210110": 5.9,
|
|
864
|
-
"210111": 4,
|
|
865
|
-
"210120": 4.1,
|
|
866
|
-
"210121": 2,
|
|
867
|
-
"210200": 5.4,
|
|
868
|
-
"210201": 4.3,
|
|
869
|
-
"210210": 4.5,
|
|
870
|
-
"210211": 2.2,
|
|
871
|
-
"210220": 2,
|
|
872
|
-
"210221": 1.1,
|
|
873
|
-
"211000": 7.5,
|
|
874
|
-
"211001": 5.5,
|
|
875
|
-
"211010": 5.8,
|
|
876
|
-
"211011": 4.5,
|
|
877
|
-
"211020": 4,
|
|
878
|
-
"211021": 2.1,
|
|
879
|
-
"211100": 6.1,
|
|
880
|
-
"211101": 5.1,
|
|
881
|
-
"211110": 4.8,
|
|
882
|
-
"211111": 1.8,
|
|
883
|
-
"211120": 2,
|
|
884
|
-
"211121": 0.9,
|
|
885
|
-
"211200": 4.6,
|
|
886
|
-
"211201": 1.8,
|
|
887
|
-
"211210": 1.7,
|
|
888
|
-
"211211": 0.7,
|
|
889
|
-
"211220": 0.8,
|
|
890
|
-
"211221": 0.2,
|
|
891
|
-
"212001": 5.3,
|
|
892
|
-
"212011": 2.4,
|
|
893
|
-
"212021": 1.4,
|
|
894
|
-
"212101": 2.4,
|
|
895
|
-
"212111": 1.2,
|
|
896
|
-
"212121": 0.5,
|
|
897
|
-
"212201": 1,
|
|
898
|
-
"212211": 0.3,
|
|
899
|
-
"212221": 0.1,
|
|
900
|
-
"000000": 10,
|
|
901
|
-
"000001": 9.9,
|
|
902
|
-
"000010": 9.8,
|
|
903
|
-
"000011": 9.5,
|
|
904
|
-
"000020": 9.5,
|
|
905
|
-
"000021": 9.2,
|
|
906
|
-
"000100": 10,
|
|
907
|
-
"000101": 9.6,
|
|
908
|
-
"000110": 9.3,
|
|
909
|
-
"000111": 8.7,
|
|
910
|
-
"000120": 9.1,
|
|
911
|
-
"000121": 8.1,
|
|
912
|
-
"000200": 9.3,
|
|
913
|
-
"000201": 9,
|
|
914
|
-
"000210": 8.9,
|
|
915
|
-
"000211": 8,
|
|
916
|
-
"000220": 8.1,
|
|
917
|
-
"000221": 6.8,
|
|
918
|
-
"001000": 9.8,
|
|
919
|
-
"001001": 9.5,
|
|
920
|
-
"001010": 9.5,
|
|
921
|
-
"001011": 9.2,
|
|
922
|
-
"001020": 9,
|
|
923
|
-
"001021": 8.4,
|
|
924
|
-
"001100": 9.3,
|
|
925
|
-
"001101": 9.2,
|
|
926
|
-
"001110": 8.9,
|
|
927
|
-
"001111": 8.1,
|
|
928
|
-
"001120": 8.1,
|
|
929
|
-
"001121": 6.5,
|
|
930
|
-
"001200": 8.8,
|
|
931
|
-
"001201": 8,
|
|
932
|
-
"001210": 7.8,
|
|
933
|
-
"001211": 7,
|
|
934
|
-
"001220": 6.9,
|
|
935
|
-
"001221": 4.8,
|
|
936
|
-
"002001": 9.2,
|
|
937
|
-
"002011": 8.2,
|
|
938
|
-
"002021": 7.2,
|
|
939
|
-
"002101": 7.9,
|
|
940
|
-
"002111": 6.9,
|
|
941
|
-
"002121": 5,
|
|
942
|
-
"002201": 6.9,
|
|
943
|
-
"002211": 5.5,
|
|
944
|
-
"002221": 2.7,
|
|
945
|
-
"010000": 9.9,
|
|
946
|
-
"010001": 9.7,
|
|
947
|
-
"010010": 9.5,
|
|
948
|
-
"010011": 9.2,
|
|
949
|
-
"010020": 9.2,
|
|
950
|
-
"010021": 8.5,
|
|
951
|
-
"010100": 9.5,
|
|
952
|
-
"010101": 9.1,
|
|
953
|
-
"010110": 9,
|
|
954
|
-
"010111": 8.3,
|
|
955
|
-
"010120": 8.4,
|
|
956
|
-
"010121": 7.1,
|
|
957
|
-
"010200": 9.2,
|
|
958
|
-
"010201": 8.1,
|
|
959
|
-
"010210": 8.2,
|
|
960
|
-
"010211": 7.1,
|
|
961
|
-
"010220": 7.2,
|
|
962
|
-
"010221": 5.3,
|
|
963
|
-
"011000": 9.5,
|
|
964
|
-
"011001": 9.3,
|
|
965
|
-
"011010": 9.2,
|
|
966
|
-
"011011": 8.5,
|
|
967
|
-
"011020": 8.5,
|
|
968
|
-
"011021": 7.3,
|
|
969
|
-
"011100": 9.2,
|
|
970
|
-
"011101": 8.2,
|
|
971
|
-
"011110": 8,
|
|
972
|
-
"011111": 7.2,
|
|
973
|
-
"011120": 7,
|
|
974
|
-
"011121": 5.9,
|
|
975
|
-
"011200": 8.4,
|
|
976
|
-
"011201": 7,
|
|
977
|
-
"011210": 7.1,
|
|
978
|
-
"011211": 5.2,
|
|
979
|
-
"011220": 5,
|
|
980
|
-
"011221": 3,
|
|
981
|
-
"012001": 8.6,
|
|
982
|
-
"012011": 7.5,
|
|
983
|
-
"012021": 5.2,
|
|
984
|
-
"012101": 7.1,
|
|
985
|
-
"012111": 5.2,
|
|
986
|
-
"012121": 2.9,
|
|
987
|
-
"012201": 6.3,
|
|
988
|
-
"012211": 2.9,
|
|
989
|
-
"012221": 1.7
|
|
990
|
-
};
|
|
991
|
-
var CVSS4_MAX_COMPOSED = {
|
|
992
|
-
eq1: {
|
|
993
|
-
"0": ["AV:N/PR:N/UI:N/"],
|
|
994
|
-
"1": ["AV:A/PR:N/UI:N/", "AV:N/PR:L/UI:N/", "AV:N/PR:N/UI:P/"],
|
|
995
|
-
"2": ["AV:P/PR:N/UI:N/", "AV:A/PR:L/UI:P/"]
|
|
996
|
-
},
|
|
997
|
-
eq2: {
|
|
998
|
-
"0": ["AC:L/AT:N/"],
|
|
999
|
-
"1": ["AC:H/AT:N/", "AC:L/AT:P/"]
|
|
1000
|
-
},
|
|
1001
|
-
eq3: {
|
|
1002
|
-
"0": {
|
|
1003
|
-
"0": ["VC:H/VI:H/VA:H/CR:H/IR:H/AR:H/"],
|
|
1004
|
-
"1": ["VC:H/VI:H/VA:L/CR:M/IR:M/AR:H/", "VC:H/VI:H/VA:H/CR:M/IR:M/AR:M/"]
|
|
1005
|
-
},
|
|
1006
|
-
"1": {
|
|
1007
|
-
"0": ["VC:L/VI:H/VA:H/CR:H/IR:H/AR:H/", "VC:H/VI:L/VA:H/CR:H/IR:H/AR:H/"],
|
|
1008
|
-
"1": [
|
|
1009
|
-
"VC:L/VI:H/VA:L/CR:H/IR:M/AR:H/",
|
|
1010
|
-
"VC:L/VI:H/VA:H/CR:H/IR:M/AR:M/",
|
|
1011
|
-
"VC:H/VI:L/VA:H/CR:M/IR:H/AR:M/",
|
|
1012
|
-
"VC:H/VI:L/VA:L/CR:M/IR:H/AR:H/",
|
|
1013
|
-
"VC:L/VI:L/VA:H/CR:H/IR:H/AR:M/"
|
|
1014
|
-
]
|
|
1015
|
-
},
|
|
1016
|
-
"2": {
|
|
1017
|
-
"1": ["VC:L/VI:L/VA:L/CR:H/IR:H/AR:H/"]
|
|
1018
|
-
}
|
|
1019
|
-
},
|
|
1020
|
-
eq4: {
|
|
1021
|
-
"0": ["SC:H/SI:S/SA:S/"],
|
|
1022
|
-
"1": ["SC:H/SI:H/SA:H/"],
|
|
1023
|
-
"2": ["SC:L/SI:L/SA:L/"]
|
|
1024
|
-
},
|
|
1025
|
-
eq5: {
|
|
1026
|
-
"0": ["E:A/"],
|
|
1027
|
-
"1": ["E:P/"],
|
|
1028
|
-
"2": ["E:U/"]
|
|
1029
|
-
}
|
|
1030
|
-
};
|
|
1031
|
-
var CVSS4_MAX_SEVERITY = {
|
|
1032
|
-
eq1: {
|
|
1033
|
-
"0": 1,
|
|
1034
|
-
"1": 4,
|
|
1035
|
-
"2": 5
|
|
1036
|
-
},
|
|
1037
|
-
eq2: {
|
|
1038
|
-
"0": 1,
|
|
1039
|
-
"1": 2
|
|
1040
|
-
},
|
|
1041
|
-
eq3eq6: {
|
|
1042
|
-
"0": {
|
|
1043
|
-
"0": 7,
|
|
1044
|
-
"1": 6
|
|
1045
|
-
},
|
|
1046
|
-
"1": {
|
|
1047
|
-
"0": 8,
|
|
1048
|
-
"1": 8
|
|
1049
|
-
},
|
|
1050
|
-
"2": {
|
|
1051
|
-
"1": 10
|
|
1052
|
-
}
|
|
1053
|
-
},
|
|
1054
|
-
eq4: {
|
|
1055
|
-
"0": 6,
|
|
1056
|
-
"1": 5,
|
|
1057
|
-
"2": 4
|
|
1058
|
-
},
|
|
1059
|
-
eq5: {
|
|
1060
|
-
"0": 1,
|
|
1061
|
-
"1": 1,
|
|
1062
|
-
"2": 1
|
|
1063
|
-
}
|
|
1064
|
-
};
|
|
1065
|
-
|
|
1066
|
-
// src/score/cvss4.ts
|
|
1067
|
-
var BASE_METRICS = ["AV", "AC", "AT", "PR", "UI", "VC", "VI", "VA", "SC", "SI", "SA"];
|
|
1068
|
-
var AV_levels = { N: 0, A: 0.1, L: 0.2, P: 0.3 };
|
|
1069
|
-
var PR_levels = { N: 0, L: 0.1, H: 0.2 };
|
|
1070
|
-
var UI_levels = { N: 0, P: 0.1, A: 0.2 };
|
|
1071
|
-
var AC_levels = { L: 0, H: 0.1 };
|
|
1072
|
-
var AT_levels = { N: 0, P: 0.1 };
|
|
1073
|
-
var VC_levels = { H: 0, L: 0.1, N: 0.2 };
|
|
1074
|
-
var VI_levels = { H: 0, L: 0.1, N: 0.2 };
|
|
1075
|
-
var VA_levels = { H: 0, L: 0.1, N: 0.2 };
|
|
1076
|
-
var SC_levels = { H: 0.1, L: 0.2, N: 0.3 };
|
|
1077
|
-
var SI_levels = { S: 0, H: 0.1, L: 0.2, N: 0.3 };
|
|
1078
|
-
var SA_levels = { S: 0, H: 0.1, L: 0.2, N: 0.3 };
|
|
1079
|
-
var CR_levels = { H: 0, M: 0.1, L: 0.2 };
|
|
1080
|
-
var IR_levels = { H: 0, M: 0.1, L: 0.2 };
|
|
1081
|
-
var AR_levels = { H: 0, M: 0.1, L: 0.2 };
|
|
1082
|
-
function scoreV4(vector) {
|
|
1083
|
-
const sel = parseV4(vector);
|
|
1084
|
-
if (!sel) return null;
|
|
1085
|
-
const macro = macroVector(sel);
|
|
1086
|
-
if (CVSS4_LOOKUP[macro] === void 0) return null;
|
|
1087
|
-
return cvssScore(sel, macro);
|
|
1088
|
-
}
|
|
1089
|
-
function parseV4(vector) {
|
|
1090
|
-
const parts = vector.split("/");
|
|
1091
|
-
if (parts[0] !== "CVSS:4.0") return null;
|
|
1092
|
-
const sel = {};
|
|
1093
|
-
for (const part of parts.slice(1)) {
|
|
1094
|
-
const [k, v] = part.split(":");
|
|
1095
|
-
if (k && v) sel[k] = v;
|
|
1096
|
-
}
|
|
1097
|
-
for (const metric of BASE_METRICS) if (!(metric in sel)) return null;
|
|
1098
|
-
for (const metric of ["E", "CR", "IR", "AR"]) if (!(metric in sel)) sel[metric] = "X";
|
|
1099
|
-
return sel;
|
|
1100
|
-
}
|
|
1101
|
-
function m(sel, metric) {
|
|
1102
|
-
const selected = sel[metric];
|
|
1103
|
-
if (metric === "E" && selected === "X") return "A";
|
|
1104
|
-
if ((metric === "CR" || metric === "IR" || metric === "AR") && selected === "X") return "H";
|
|
1105
|
-
const modified = sel[`M${metric}`];
|
|
1106
|
-
if (modified !== void 0 && modified !== "X") return modified;
|
|
1107
|
-
return selected ?? "";
|
|
1108
|
-
}
|
|
1109
|
-
function macroVector(sel) {
|
|
1110
|
-
const av = m(sel, "AV");
|
|
1111
|
-
const pr = m(sel, "PR");
|
|
1112
|
-
const ui = m(sel, "UI");
|
|
1113
|
-
let eq1 = "2";
|
|
1114
|
-
if (av === "N" && pr === "N" && ui === "N") eq1 = "0";
|
|
1115
|
-
else if ((av === "N" || pr === "N" || ui === "N") && av !== "P") eq1 = "1";
|
|
1116
|
-
const eq2 = m(sel, "AC") === "L" && m(sel, "AT") === "N" ? "0" : "1";
|
|
1117
|
-
const vc = m(sel, "VC");
|
|
1118
|
-
const vi = m(sel, "VI");
|
|
1119
|
-
const va = m(sel, "VA");
|
|
1120
|
-
let eq3 = "2";
|
|
1121
|
-
if (vc === "H" && vi === "H") eq3 = "0";
|
|
1122
|
-
else if (vc === "H" || vi === "H" || va === "H") eq3 = "1";
|
|
1123
|
-
const msi = m(sel, "MSI");
|
|
1124
|
-
const msa = m(sel, "MSA");
|
|
1125
|
-
let eq4 = "2";
|
|
1126
|
-
if (msi === "S" || msa === "S") eq4 = "0";
|
|
1127
|
-
else if (m(sel, "SC") === "H" || m(sel, "SI") === "H" || m(sel, "SA") === "H") eq4 = "1";
|
|
1128
|
-
const e = m(sel, "E");
|
|
1129
|
-
const eq5 = e === "A" ? "0" : e === "P" ? "1" : "2";
|
|
1130
|
-
const cr = m(sel, "CR");
|
|
1131
|
-
const ir = m(sel, "IR");
|
|
1132
|
-
const ar = m(sel, "AR");
|
|
1133
|
-
const eq6 = cr === "H" && vc === "H" || ir === "H" && vi === "H" || ar === "H" && va === "H" ? "0" : "1";
|
|
1134
|
-
return `${eq1}${eq2}${eq3}${eq4}${eq5}${eq6}`;
|
|
1135
|
-
}
|
|
1136
|
-
function lvl(map, key) {
|
|
1137
|
-
return map[key] ?? 0;
|
|
1138
|
-
}
|
|
1139
|
-
function eqMaxes(macro, eq) {
|
|
1140
|
-
const group = CVSS4_MAX_COMPOSED[`eq${eq}`];
|
|
1141
|
-
return group[macro[eq - 1]] ?? [];
|
|
1142
|
-
}
|
|
1143
|
-
function extractValueMetric(metric, str) {
|
|
1144
|
-
const start = str.indexOf(metric) + metric.length + 1;
|
|
1145
|
-
const rest = str.slice(start);
|
|
1146
|
-
const slash = rest.indexOf("/");
|
|
1147
|
-
return slash > 0 ? rest.substring(0, slash) : rest;
|
|
1148
|
-
}
|
|
1149
|
-
function cvssScore(sel, macro) {
|
|
1150
|
-
if (BASE_METRICS.slice(5).every((metric) => m(sel, metric) === "N")) return 0;
|
|
1151
|
-
let value = CVSS4_LOOKUP[macro];
|
|
1152
|
-
const eq1 = Number(macro[0]);
|
|
1153
|
-
const eq3 = Number(macro[2]);
|
|
1154
|
-
const eq6 = Number(macro[5]);
|
|
1155
|
-
const lower = (parts) => CVSS4_LOOKUP[parts.join("")] ?? NaN;
|
|
1156
|
-
const d = macro.split("").map(Number);
|
|
1157
|
-
const scoreEq1Lower = lower([d[0] + 1, d[1], d[2], d[3], d[4], d[5]]);
|
|
1158
|
-
const scoreEq2Lower = lower([d[0], d[1] + 1, d[2], d[3], d[4], d[5]]);
|
|
1159
|
-
let scoreEq3Eq6Lower;
|
|
1160
|
-
if (eq3 === 1 && eq6 === 1) scoreEq3Eq6Lower = lower([d[0], d[1], d[2] + 1, d[3], d[4], d[5]]);
|
|
1161
|
-
else if (eq3 === 0 && eq6 === 1)
|
|
1162
|
-
scoreEq3Eq6Lower = lower([d[0], d[1], d[2] + 1, d[3], d[4], d[5]]);
|
|
1163
|
-
else if (eq3 === 1 && eq6 === 0)
|
|
1164
|
-
scoreEq3Eq6Lower = lower([d[0], d[1], d[2], d[3], d[4], d[5] + 1]);
|
|
1165
|
-
else if (eq3 === 0 && eq6 === 0) {
|
|
1166
|
-
const left = lower([d[0], d[1], d[2], d[3], d[4], d[5] + 1]);
|
|
1167
|
-
const right = lower([d[0], d[1], d[2] + 1, d[3], d[4], d[5]]);
|
|
1168
|
-
scoreEq3Eq6Lower = left > right ? left : right;
|
|
1169
|
-
} else scoreEq3Eq6Lower = lower([d[0], d[1], d[2] + 1, d[3], d[4], d[5] + 1]);
|
|
1170
|
-
const scoreEq4Lower = lower([d[0], d[1], d[2], d[3] + 1, d[4], d[5]]);
|
|
1171
|
-
const scoreEq5Lower = lower([d[0], d[1], d[2], d[3], d[4] + 1, d[5]]);
|
|
1172
|
-
const eq1Maxes = eqMaxes(macro, 1);
|
|
1173
|
-
const eq2Maxes = eqMaxes(macro, 2);
|
|
1174
|
-
const eq3Eq6Maxes = CVSS4_MAX_COMPOSED.eq3[macro[2]][macro[5]];
|
|
1175
|
-
const eq4Maxes = eqMaxes(macro, 4);
|
|
1176
|
-
const eq5Maxes = eqMaxes(macro, 5);
|
|
1177
|
-
const maxVectors = [];
|
|
1178
|
-
for (const a of eq1Maxes)
|
|
1179
|
-
for (const b of eq2Maxes)
|
|
1180
|
-
for (const c of eq3Eq6Maxes)
|
|
1181
|
-
for (const e of eq4Maxes) for (const f of eq5Maxes) maxVectors.push(a + b + c + e + f);
|
|
1182
|
-
let sdAV = 0, sdPR = 0, sdUI = 0, sdAC = 0, sdAT = 0, sdVC = 0, sdVI = 0, sdVA = 0, sdSC = 0, sdSI = 0, sdSA = 0, sdCR = 0, sdIR = 0, sdAR = 0;
|
|
1183
|
-
for (const maxVector of maxVectors) {
|
|
1184
|
-
sdAV = lvl(AV_levels, m(sel, "AV")) - lvl(AV_levels, extractValueMetric("AV", maxVector));
|
|
1185
|
-
sdPR = lvl(PR_levels, m(sel, "PR")) - lvl(PR_levels, extractValueMetric("PR", maxVector));
|
|
1186
|
-
sdUI = lvl(UI_levels, m(sel, "UI")) - lvl(UI_levels, extractValueMetric("UI", maxVector));
|
|
1187
|
-
sdAC = lvl(AC_levels, m(sel, "AC")) - lvl(AC_levels, extractValueMetric("AC", maxVector));
|
|
1188
|
-
sdAT = lvl(AT_levels, m(sel, "AT")) - lvl(AT_levels, extractValueMetric("AT", maxVector));
|
|
1189
|
-
sdVC = lvl(VC_levels, m(sel, "VC")) - lvl(VC_levels, extractValueMetric("VC", maxVector));
|
|
1190
|
-
sdVI = lvl(VI_levels, m(sel, "VI")) - lvl(VI_levels, extractValueMetric("VI", maxVector));
|
|
1191
|
-
sdVA = lvl(VA_levels, m(sel, "VA")) - lvl(VA_levels, extractValueMetric("VA", maxVector));
|
|
1192
|
-
sdSC = lvl(SC_levels, m(sel, "SC")) - lvl(SC_levels, extractValueMetric("SC", maxVector));
|
|
1193
|
-
sdSI = lvl(SI_levels, m(sel, "SI")) - lvl(SI_levels, extractValueMetric("SI", maxVector));
|
|
1194
|
-
sdSA = lvl(SA_levels, m(sel, "SA")) - lvl(SA_levels, extractValueMetric("SA", maxVector));
|
|
1195
|
-
sdCR = lvl(CR_levels, m(sel, "CR")) - lvl(CR_levels, extractValueMetric("CR", maxVector));
|
|
1196
|
-
sdIR = lvl(IR_levels, m(sel, "IR")) - lvl(IR_levels, extractValueMetric("IR", maxVector));
|
|
1197
|
-
sdAR = lvl(AR_levels, m(sel, "AR")) - lvl(AR_levels, extractValueMetric("AR", maxVector));
|
|
1198
|
-
const distances = [
|
|
1199
|
-
sdAV,
|
|
1200
|
-
sdPR,
|
|
1201
|
-
sdUI,
|
|
1202
|
-
sdAC,
|
|
1203
|
-
sdAT,
|
|
1204
|
-
sdVC,
|
|
1205
|
-
sdVI,
|
|
1206
|
-
sdVA,
|
|
1207
|
-
sdSC,
|
|
1208
|
-
sdSI,
|
|
1209
|
-
sdSA,
|
|
1210
|
-
sdCR,
|
|
1211
|
-
sdIR,
|
|
1212
|
-
sdAR
|
|
1213
|
-
];
|
|
1214
|
-
if (distances.some((x) => x < 0)) continue;
|
|
1215
|
-
break;
|
|
1216
|
-
}
|
|
1217
|
-
const csdEq1 = sdAV + sdPR + sdUI;
|
|
1218
|
-
const csdEq2 = sdAC + sdAT;
|
|
1219
|
-
const csdEq3Eq6 = sdVC + sdVI + sdVA + sdCR + sdIR + sdAR;
|
|
1220
|
-
const csdEq4 = sdSC + sdSI + sdSA;
|
|
1221
|
-
const step = 0.1;
|
|
1222
|
-
const availEq1 = value - scoreEq1Lower;
|
|
1223
|
-
const availEq2 = value - scoreEq2Lower;
|
|
1224
|
-
const availEq3Eq6 = value - scoreEq3Eq6Lower;
|
|
1225
|
-
const availEq4 = value - scoreEq4Lower;
|
|
1226
|
-
const availEq5 = value - scoreEq5Lower;
|
|
1227
|
-
const maxSev = CVSS4_MAX_SEVERITY;
|
|
1228
|
-
const maxSevEq1 = maxSev.eq1[String(eq1)] * step;
|
|
1229
|
-
const maxSevEq2 = maxSev.eq2[macro[1]] * step;
|
|
1230
|
-
const maxSevEq3Eq6 = maxSev.eq3eq6[String(eq3)][String(eq6)] * step;
|
|
1231
|
-
const maxSevEq4 = maxSev.eq4[macro[3]] * step;
|
|
1232
|
-
let n = 0;
|
|
1233
|
-
let nsEq1 = 0, nsEq2 = 0, nsEq3Eq6 = 0, nsEq4 = 0;
|
|
1234
|
-
if (!Number.isNaN(availEq1)) {
|
|
1235
|
-
n++;
|
|
1236
|
-
nsEq1 = availEq1 * (csdEq1 / maxSevEq1);
|
|
1237
|
-
}
|
|
1238
|
-
if (!Number.isNaN(availEq2)) {
|
|
1239
|
-
n++;
|
|
1240
|
-
nsEq2 = availEq2 * (csdEq2 / maxSevEq2);
|
|
1241
|
-
}
|
|
1242
|
-
if (!Number.isNaN(availEq3Eq6)) {
|
|
1243
|
-
n++;
|
|
1244
|
-
nsEq3Eq6 = availEq3Eq6 * (csdEq3Eq6 / maxSevEq3Eq6);
|
|
1245
|
-
}
|
|
1246
|
-
if (!Number.isNaN(availEq4)) {
|
|
1247
|
-
n++;
|
|
1248
|
-
nsEq4 = availEq4 * (csdEq4 / maxSevEq4);
|
|
1249
|
-
}
|
|
1250
|
-
if (!Number.isNaN(availEq5)) {
|
|
1251
|
-
n++;
|
|
1252
|
-
}
|
|
1253
|
-
const meanDistance = n === 0 ? 0 : (nsEq1 + nsEq2 + nsEq3Eq6 + nsEq4) / n;
|
|
1254
|
-
value -= meanDistance;
|
|
1255
|
-
if (value < 0) value = 0;
|
|
1256
|
-
if (value > 10) value = 10;
|
|
1257
|
-
return Math.round(value * 10) / 10;
|
|
1258
|
-
}
|
|
1259
|
-
|
|
1260
|
-
// src/score/cvss.ts
|
|
1261
|
-
var AV = { N: 0.85, A: 0.62, L: 0.55, P: 0.2 };
|
|
1262
|
-
var AC = { L: 0.77, H: 0.44 };
|
|
1263
|
-
var UI = { N: 0.85, R: 0.62 };
|
|
1264
|
-
var PR_UNCHANGED = { N: 0.85, L: 0.62, H: 0.27 };
|
|
1265
|
-
var PR_CHANGED = { N: 0.85, L: 0.68, H: 0.5 };
|
|
1266
|
-
var CIA = { H: 0.56, L: 0.22, N: 0 };
|
|
1267
|
-
function levelFromScore(score) {
|
|
1268
|
-
if (score <= 0) return "none";
|
|
1269
|
-
if (score < 4) return "low";
|
|
1270
|
-
if (score < 7) return "medium";
|
|
1271
|
-
if (score < 9) return "high";
|
|
1272
|
-
return "critical";
|
|
1273
|
-
}
|
|
1274
|
-
function levelFromLabel(label) {
|
|
1275
|
-
switch (label.trim().toUpperCase()) {
|
|
1276
|
-
case "CRITICAL":
|
|
1277
|
-
return "critical";
|
|
1278
|
-
case "HIGH":
|
|
1279
|
-
return "high";
|
|
1280
|
-
case "MODERATE":
|
|
1281
|
-
case "MEDIUM":
|
|
1282
|
-
return "medium";
|
|
1283
|
-
case "LOW":
|
|
1284
|
-
return "low";
|
|
1285
|
-
case "NONE":
|
|
1286
|
-
return "none";
|
|
1287
|
-
default:
|
|
1288
|
-
return "unknown";
|
|
1289
|
-
}
|
|
1290
|
-
}
|
|
1291
|
-
function roundUp(input) {
|
|
1292
|
-
const scaled = Math.round(input * 1e5);
|
|
1293
|
-
if (scaled % 1e4 === 0) return scaled / 1e5;
|
|
1294
|
-
return (Math.floor(scaled / 1e4) + 1) / 10;
|
|
1295
|
-
}
|
|
1296
|
-
function scoreFromVector(vector) {
|
|
1297
|
-
const parts = vector.split("/");
|
|
1298
|
-
const header = parts[0] ?? "";
|
|
1299
|
-
if (header === "CVSS:4.0") {
|
|
1300
|
-
const score2 = scoreV4(vector);
|
|
1301
|
-
return score2 === null ? null : { score: score2, level: levelFromScore(score2), version: header };
|
|
1302
|
-
}
|
|
1303
|
-
if (!/^CVSS:3\.[01]$/.test(header)) return null;
|
|
1304
|
-
const m2 = {};
|
|
1305
|
-
for (const part of parts.slice(1)) {
|
|
1306
|
-
const [k, v] = part.split(":");
|
|
1307
|
-
if (k && v) m2[k] = v;
|
|
1308
|
-
}
|
|
1309
|
-
const av = AV[m2.AV ?? ""];
|
|
1310
|
-
const ac = AC[m2.AC ?? ""];
|
|
1311
|
-
const ui = UI[m2.UI ?? ""];
|
|
1312
|
-
const scopeChanged = m2.S === "C";
|
|
1313
|
-
const pr = (scopeChanged ? PR_CHANGED : PR_UNCHANGED)[m2.PR ?? ""];
|
|
1314
|
-
const c = CIA[m2.C ?? ""];
|
|
1315
|
-
const i = CIA[m2.I ?? ""];
|
|
1316
|
-
const a = CIA[m2.A ?? ""];
|
|
1317
|
-
if ([av, ac, ui, pr, c, i, a].some((x) => x === void 0)) return null;
|
|
1318
|
-
const iss = 1 - (1 - c) * (1 - i) * (1 - a);
|
|
1319
|
-
const impact = scopeChanged ? 7.52 * (iss - 0.029) - 3.25 * Math.pow(iss - 0.02, 15) : 6.42 * iss;
|
|
1320
|
-
const exploitability = 8.22 * av * ac * pr * ui;
|
|
1321
|
-
let score;
|
|
1322
|
-
if (impact <= 0) score = 0;
|
|
1323
|
-
else if (scopeChanged) score = roundUp(Math.min(1.08 * (impact + exploitability), 10));
|
|
1324
|
-
else score = roundUp(Math.min(impact + exploitability, 10));
|
|
1325
|
-
return { score, level: levelFromScore(score), version: header };
|
|
1326
|
-
}
|
|
1327
|
-
function resolveSeverity(vuln) {
|
|
1328
|
-
const vectors = [];
|
|
1329
|
-
for (const s of vuln.severity ?? []) if (s.score) vectors.push(s.score);
|
|
1330
|
-
for (const affected of vuln.affected ?? []) {
|
|
1331
|
-
for (const s of affected.severity ?? []) if (s.score) vectors.push(s.score);
|
|
1332
|
-
}
|
|
1333
|
-
let best = null;
|
|
1334
|
-
for (const vector of vectors) {
|
|
1335
|
-
const result = scoreFromVector(vector);
|
|
1336
|
-
if (result && (!best || result.score > best.score)) best = result;
|
|
1337
|
-
}
|
|
1338
|
-
if (best) {
|
|
1339
|
-
return {
|
|
1340
|
-
level: best.level,
|
|
1341
|
-
score: best.score,
|
|
1342
|
-
vector: vectorFor(best, vectors),
|
|
1343
|
-
cvssVersion: best.version,
|
|
1344
|
-
source: "cvss"
|
|
1345
|
-
};
|
|
1346
|
-
}
|
|
1347
|
-
const label = qualitativeLabel(vuln);
|
|
1348
|
-
if (label) return { level: levelFromLabel(label), source: "database" };
|
|
1349
|
-
return { level: "unknown", source: "unknown" };
|
|
1350
|
-
}
|
|
1351
|
-
function vectorFor(best, vectors) {
|
|
1352
|
-
return vectors.find((v) => v.startsWith(best.version));
|
|
1353
|
-
}
|
|
1354
|
-
function qualitativeLabel(vuln) {
|
|
1355
|
-
const fromRecord = (db) => {
|
|
1356
|
-
const sev = db?.severity;
|
|
1357
|
-
return typeof sev === "string" ? sev : void 0;
|
|
1358
|
-
};
|
|
1359
|
-
const top = fromRecord(vuln.database_specific);
|
|
1360
|
-
if (top) return top;
|
|
1361
|
-
for (const affected of vuln.affected ?? []) {
|
|
1362
|
-
const label = fromRecord(affected.database_specific);
|
|
1363
|
-
if (label) return label;
|
|
1364
|
-
}
|
|
1365
|
-
return void 0;
|
|
1366
|
-
}
|
|
1367
|
-
|
|
1368
|
-
// src/match/findings.ts
|
|
1369
|
-
function buildFindings(graph, candidatesFor, options = {}) {
|
|
1370
|
-
const findings = [];
|
|
1371
|
-
for (const node of Object.values(graph.nodes)) {
|
|
1372
|
-
if (node.unscannable) continue;
|
|
1373
|
-
if (options.prodOnly && node.scope === "dev") continue;
|
|
1374
|
-
const candidates = candidatesFor(node.name);
|
|
1375
|
-
if (candidates.length === 0) continue;
|
|
1376
|
-
const matched = candidates.filter(
|
|
1377
|
-
(vuln) => vulnerabilityAffects(node.name, node.version, vuln).affected
|
|
1378
|
-
);
|
|
1379
|
-
if (matched.length === 0) continue;
|
|
1380
|
-
const path = shortestPath(graph, node.key);
|
|
1381
|
-
for (const vuln of dedupeVulnerabilities(matched)) {
|
|
1382
|
-
if (isIgnored(vuln, options.ignore)) continue;
|
|
1383
|
-
const { fixedVersions } = vulnerabilityAffects(node.name, node.version, vuln);
|
|
1384
|
-
const sortedFixes = sortVersions(fixedVersions);
|
|
1385
|
-
const nearest = nearestFix(node.version, fixedVersions);
|
|
1386
|
-
findings.push({
|
|
1387
|
-
id: vuln.id,
|
|
1388
|
-
aliases: vuln.aliases ?? [],
|
|
1389
|
-
packageName: node.name,
|
|
1390
|
-
version: node.version,
|
|
1391
|
-
scope: node.scope,
|
|
1392
|
-
direct: node.direct,
|
|
1393
|
-
severity: resolveSeverity(vuln),
|
|
1394
|
-
summary: vuln.summary ?? vuln.id,
|
|
1395
|
-
details: vuln.details,
|
|
1396
|
-
references: (vuln.references ?? []).map((ref) => ref.url),
|
|
1397
|
-
fixedVersions: sortedFixes,
|
|
1398
|
-
recommendation: buildRecommendation(node.name, node.direct, nearest, graph.manager),
|
|
1399
|
-
dependencyPaths: path ? [path] : [],
|
|
1400
|
-
source: "osv"
|
|
1401
|
-
});
|
|
1402
|
-
}
|
|
1403
|
-
}
|
|
1404
|
-
return findings;
|
|
1405
|
-
}
|
|
1406
|
-
function isIgnored(vuln, ignore) {
|
|
1407
|
-
if (!ignore || ignore.size === 0) return false;
|
|
1408
|
-
if (ignore.has(vuln.id)) return true;
|
|
1409
|
-
return (vuln.aliases ?? []).some((alias) => ignore.has(alias));
|
|
1410
|
-
}
|
|
1411
|
-
function sortVersions(versions) {
|
|
1412
|
-
return [...new Set(versions)].sort((a, b) => {
|
|
1413
|
-
const pa = semver2.parse(a, { loose: true });
|
|
1414
|
-
const pb = semver2.parse(b, { loose: true });
|
|
1415
|
-
if (pa && pb) return pa.compare(pb);
|
|
1416
|
-
return a.localeCompare(b);
|
|
1417
|
-
});
|
|
1418
|
-
}
|
|
1419
|
-
var INSTALL_COMMAND = {
|
|
1420
|
-
npm: "npm install",
|
|
1421
|
-
yarn: "yarn add",
|
|
1422
|
-
pnpm: "pnpm add"
|
|
1423
|
-
};
|
|
1424
|
-
function buildRecommendation(name, direct, nearest, manager) {
|
|
1425
|
-
if (!nearest) {
|
|
1426
|
-
return direct ? "No fixed version is available yet. Watch the advisory, or evaluate an alternative package." : "No fixed version is available yet. Pressure the parent dependency to update, or override the version.";
|
|
1427
|
-
}
|
|
1428
|
-
if (direct) {
|
|
1429
|
-
return `Upgrade to ${name}@${nearest} or later (e.g. \`${INSTALL_COMMAND[manager]} ${name}@${nearest}\`).`;
|
|
1430
|
-
}
|
|
1431
|
-
return `Update the dependency that pulls in ${name} so it resolves to ${name}@${nearest} or later. If it cannot, force the resolution via an override.`;
|
|
1432
|
-
}
|
|
1433
|
-
|
|
1434
|
-
// src/osv/database.ts
|
|
1435
|
-
var OsvDatabase = class {
|
|
1436
|
-
byName = /* @__PURE__ */ new Map();
|
|
1437
|
-
ids = /* @__PURE__ */ new Set();
|
|
1438
|
-
add(vuln) {
|
|
1439
|
-
if (vuln.withdrawn) return;
|
|
1440
|
-
if (this.ids.has(vuln.id)) return;
|
|
1441
|
-
this.ids.add(vuln.id);
|
|
1442
|
-
const names = /* @__PURE__ */ new Set();
|
|
1443
|
-
for (const affected of vuln.affected ?? []) {
|
|
1444
|
-
const ecosystem = affected.package?.ecosystem;
|
|
1445
|
-
if (ecosystem && ecosystem !== "npm") continue;
|
|
1446
|
-
if (affected.package?.name) names.add(affected.package.name);
|
|
1447
|
-
}
|
|
1448
|
-
for (const name of names) {
|
|
1449
|
-
const list = this.byName.get(name);
|
|
1450
|
-
if (list) list.push(vuln);
|
|
1451
|
-
else this.byName.set(name, [vuln]);
|
|
1452
|
-
}
|
|
1453
|
-
}
|
|
1454
|
-
addAll(vulns) {
|
|
1455
|
-
for (const vuln of vulns) this.add(vuln);
|
|
1456
|
-
}
|
|
1457
|
-
/** Candidate advisories that name `packageName` (still need a version check). */
|
|
1458
|
-
vulnerabilitiesFor(packageName) {
|
|
1459
|
-
return this.byName.get(packageName) ?? [];
|
|
1460
|
-
}
|
|
1461
|
-
/** Number of distinct advisories indexed. */
|
|
1462
|
-
get size() {
|
|
1463
|
-
return this.ids.size;
|
|
1464
|
-
}
|
|
1465
|
-
};
|
|
1466
|
-
|
|
1467
|
-
// src/engine.ts
|
|
1468
|
-
import { resolve as resolve2 } from "path";
|
|
1469
|
-
|
|
1470
|
-
// src/cache/paths.ts
|
|
1471
|
-
import { join as join2 } from "path";
|
|
1472
|
-
import envPaths from "env-paths";
|
|
1473
|
-
function resolveCacheDir(override) {
|
|
1474
|
-
if (override) return override;
|
|
1475
|
-
if (process.env.LOCKHAWK_CACHE) return process.env.LOCKHAWK_CACHE;
|
|
1476
|
-
return envPaths("lockhawk", { suffix: "" }).cache;
|
|
1477
|
-
}
|
|
1478
|
-
function offlineDbDir(cacheDir) {
|
|
1479
|
-
return join2(cacheDir, "osv-db", "npm");
|
|
1480
|
-
}
|
|
1481
|
-
var SHARD_BUCKETS = 4096;
|
|
1482
|
-
function shardBucket(packageName) {
|
|
1483
|
-
let hash = 2166136261;
|
|
1484
|
-
for (let i = 0; i < packageName.length; i++) {
|
|
1485
|
-
hash ^= packageName.charCodeAt(i);
|
|
1486
|
-
hash = Math.imul(hash, 16777619);
|
|
1487
|
-
}
|
|
1488
|
-
return (hash >>> 0) % SHARD_BUCKETS;
|
|
1489
|
-
}
|
|
1490
|
-
function offlineMetaPath(cacheDir) {
|
|
1491
|
-
return join2(offlineDbDir(cacheDir), "meta.json");
|
|
1492
|
-
}
|
|
1493
|
-
function queryCachePath(cacheDir, id) {
|
|
1494
|
-
return join2(cacheDir, "query-cache", `${encodeURIComponent(id)}.json`);
|
|
1495
|
-
}
|
|
1496
|
-
|
|
1497
|
-
// src/osv/client.ts
|
|
1498
|
-
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
1499
|
-
import { dirname as dirname4 } from "path";
|
|
1500
|
-
import pLimit from "p-limit";
|
|
1501
|
-
import pRetry, { AbortError } from "p-retry";
|
|
1502
|
-
var OSV_API = "https://api.osv.dev/v1";
|
|
1503
|
-
var BATCH_SIZE = 1e3;
|
|
1504
|
-
var OsvClient = class {
|
|
1505
|
-
constructor(opts) {
|
|
1506
|
-
this.opts = opts;
|
|
1507
|
-
this.limit = pLimit(opts.concurrency ?? 5);
|
|
1508
|
-
}
|
|
1509
|
-
opts;
|
|
1510
|
-
limit;
|
|
1511
|
-
/** Fetch advisories affecting the given packages, keyed by package name. */
|
|
1512
|
-
async fetchAdvisories(packages) {
|
|
1513
|
-
const unique = dedupePackages(packages);
|
|
1514
|
-
const idsByName = await this.queryBatch(unique);
|
|
1515
|
-
const allIds = /* @__PURE__ */ new Set();
|
|
1516
|
-
for (const ids of idsByName.values()) for (const id of ids) allIds.add(id);
|
|
1517
|
-
const records = await this.hydrate([...allIds]);
|
|
1518
|
-
const byName = /* @__PURE__ */ new Map();
|
|
1519
|
-
for (const [name, ids] of idsByName) {
|
|
1520
|
-
const list = [];
|
|
1521
|
-
for (const id of ids) {
|
|
1522
|
-
const record = records.get(id);
|
|
1523
|
-
if (record) list.push(record);
|
|
1524
|
-
}
|
|
1525
|
-
if (list.length) byName.set(name, list);
|
|
1526
|
-
}
|
|
1527
|
-
return byName;
|
|
1528
|
-
}
|
|
1529
|
-
async queryBatch(packages) {
|
|
1530
|
-
const idsByName = /* @__PURE__ */ new Map();
|
|
1531
|
-
for (let i = 0; i < packages.length; i += BATCH_SIZE) {
|
|
1532
|
-
const chunk = packages.slice(i, i + BATCH_SIZE);
|
|
1533
|
-
const body = {
|
|
1534
|
-
queries: chunk.map((p) => ({
|
|
1535
|
-
package: { name: p.name, ecosystem: "npm" },
|
|
1536
|
-
version: p.version
|
|
1537
|
-
}))
|
|
1538
|
-
};
|
|
1539
|
-
const data = await this.request(`${OSV_API}/querybatch`, body);
|
|
1540
|
-
for (let j = 0; j < chunk.length; j++) {
|
|
1541
|
-
const pkg = chunk[j];
|
|
1542
|
-
const result = data.results[j];
|
|
1543
|
-
if (!result) continue;
|
|
1544
|
-
const set = idsByName.get(pkg.name) ?? /* @__PURE__ */ new Set();
|
|
1545
|
-
for (const vuln of result.vulns ?? []) set.add(vuln.id);
|
|
1546
|
-
let pageToken = result.next_page_token;
|
|
1547
|
-
while (pageToken) {
|
|
1548
|
-
const page = await this.request(`${OSV_API}/query`, {
|
|
1549
|
-
package: { name: pkg.name, ecosystem: "npm" },
|
|
1550
|
-
version: pkg.version,
|
|
1551
|
-
page_token: pageToken
|
|
1552
|
-
});
|
|
1553
|
-
for (const vuln of page.vulns ?? []) set.add(vuln.id);
|
|
1554
|
-
pageToken = page.next_page_token;
|
|
1555
|
-
}
|
|
1556
|
-
if (set.size) idsByName.set(pkg.name, set);
|
|
1557
|
-
}
|
|
1558
|
-
}
|
|
1559
|
-
return idsByName;
|
|
1560
|
-
}
|
|
1561
|
-
async hydrate(ids) {
|
|
1562
|
-
const records = /* @__PURE__ */ new Map();
|
|
1563
|
-
await Promise.all(
|
|
1564
|
-
ids.map(
|
|
1565
|
-
(id) => this.limit(async () => {
|
|
1566
|
-
const record = await this.getVulnerability(id);
|
|
1567
|
-
if (record) records.set(id, record);
|
|
1568
|
-
})
|
|
1569
|
-
)
|
|
1570
|
-
);
|
|
1571
|
-
return records;
|
|
1572
|
-
}
|
|
1573
|
-
async getVulnerability(id) {
|
|
1574
|
-
const cached = await this.readCache(id);
|
|
1575
|
-
if (cached) return cached;
|
|
1576
|
-
const record = await this.request(
|
|
1577
|
-
`${OSV_API}/vulns/${encodeURIComponent(id)}`
|
|
1578
|
-
);
|
|
1579
|
-
await this.writeCache(id, record);
|
|
1580
|
-
return record;
|
|
1581
|
-
}
|
|
1582
|
-
async request(url, body) {
|
|
1583
|
-
const retries = this.opts.retries ?? 3;
|
|
1584
|
-
return pRetry(
|
|
1585
|
-
async () => {
|
|
1586
|
-
const res = await fetch(url, {
|
|
1587
|
-
method: body ? "POST" : "GET",
|
|
1588
|
-
headers: body ? { "content-type": "application/json" } : void 0,
|
|
1589
|
-
body: body ? JSON.stringify(body) : void 0
|
|
1590
|
-
});
|
|
1591
|
-
if (res.ok) return await res.json();
|
|
1592
|
-
if (res.status === 429 || res.status >= 500) {
|
|
1593
|
-
throw new Error(`OSV ${res.status} for ${url}`);
|
|
1594
|
-
}
|
|
1595
|
-
throw new AbortError(`OSV ${res.status} for ${url}`);
|
|
1596
|
-
},
|
|
1597
|
-
{ retries }
|
|
1598
|
-
);
|
|
1599
|
-
}
|
|
1600
|
-
async readCache(id) {
|
|
1601
|
-
if (this.opts.noCache) return void 0;
|
|
1602
|
-
try {
|
|
1603
|
-
const parsed = JSON.parse(await readFile(queryCachePath(this.opts.cacheDir, id), "utf8"));
|
|
1604
|
-
const ttlMs = (this.opts.cacheTtlHours ?? 24) * 36e5;
|
|
1605
|
-
if (Date.now() - parsed.fetchedAt > ttlMs) return void 0;
|
|
1606
|
-
return parsed.vuln;
|
|
1607
|
-
} catch {
|
|
1608
|
-
return void 0;
|
|
1609
|
-
}
|
|
1610
|
-
}
|
|
1611
|
-
async writeCache(id, vuln) {
|
|
1612
|
-
if (this.opts.noCache) return;
|
|
1613
|
-
try {
|
|
1614
|
-
const path = queryCachePath(this.opts.cacheDir, id);
|
|
1615
|
-
await mkdir(dirname4(path), { recursive: true });
|
|
1616
|
-
await writeFile(path, JSON.stringify({ fetchedAt: Date.now(), vuln }));
|
|
1617
|
-
} catch {
|
|
1618
|
-
}
|
|
1619
|
-
}
|
|
1620
|
-
};
|
|
1621
|
-
function dedupePackages(packages) {
|
|
1622
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1623
|
-
const out = [];
|
|
1624
|
-
for (const p of packages) {
|
|
1625
|
-
const key = `${p.name}@${p.version}`;
|
|
1626
|
-
if (!seen.has(key)) {
|
|
1627
|
-
seen.add(key);
|
|
1628
|
-
out.push(p);
|
|
1629
|
-
}
|
|
1630
|
-
}
|
|
1631
|
-
return out;
|
|
1632
|
-
}
|
|
1633
|
-
|
|
1634
|
-
// src/osv/offline-db.ts
|
|
1635
|
-
import { createWriteStream } from "fs";
|
|
1636
|
-
import { mkdir as mkdir2, mkdtemp, readFile as readFile2, rm, writeFile as writeFile2 } from "fs/promises";
|
|
1637
|
-
import { tmpdir } from "os";
|
|
1638
|
-
import { join as join3 } from "path";
|
|
1639
|
-
import { Readable } from "stream";
|
|
1640
|
-
import { pipeline } from "stream/promises";
|
|
1641
|
-
import { gunzipSync, gzipSync } from "zlib";
|
|
1642
|
-
import yauzl from "yauzl";
|
|
1643
|
-
var OSV_NPM_ZIP_URL = "https://storage.googleapis.com/osv-vulnerabilities/npm/all.zip";
|
|
1644
|
-
var MAX_ENTRY_BYTES = 8 * 1024 * 1024;
|
|
1645
|
-
var OfflineDbMissingError = class extends Error {
|
|
1646
|
-
constructor() {
|
|
1647
|
-
super("No offline OSV database found. Run `lockhawk db update` first.");
|
|
1648
|
-
this.name = "OfflineDbMissingError";
|
|
1649
|
-
}
|
|
1650
|
-
};
|
|
1651
|
-
var IMF_FIXDATE = /^[A-Za-z]{3}, \d{2} [A-Za-z]{3} \d{4} \d{2}:\d{2}:\d{2} GMT$/;
|
|
1652
|
-
var ENTITY_TAG = /^(?:W\/)?"[\x21\x23-\x7e]*"$/;
|
|
1653
|
-
function sanitizeHttpDate(value) {
|
|
1654
|
-
return value && IMF_FIXDATE.test(value) ? value : void 0;
|
|
1655
|
-
}
|
|
1656
|
-
function sanitizeETag(value) {
|
|
1657
|
-
return value && ENTITY_TAG.test(value) ? value : void 0;
|
|
1658
|
-
}
|
|
1659
|
-
async function readOfflineMeta(cacheDir) {
|
|
1660
|
-
try {
|
|
1661
|
-
return JSON.parse(await readFile2(offlineMetaPath(cacheDir), "utf8"));
|
|
1662
|
-
} catch {
|
|
1663
|
-
return void 0;
|
|
1664
|
-
}
|
|
1665
|
-
}
|
|
1666
|
-
async function updateOfflineDatabase(opts) {
|
|
1667
|
-
const { cacheDir, nowIso, force, onProgress } = opts;
|
|
1668
|
-
const dir = offlineDbDir(cacheDir);
|
|
1669
|
-
await mkdir2(dir, { recursive: true });
|
|
1670
|
-
const previous = force ? void 0 : await readOfflineMeta(cacheDir);
|
|
1671
|
-
const headers = {};
|
|
1672
|
-
const etag = sanitizeETag(previous?.etag);
|
|
1673
|
-
const lastModified = sanitizeHttpDate(previous?.lastModified);
|
|
1674
|
-
if (etag) headers["If-None-Match"] = etag;
|
|
1675
|
-
if (lastModified) headers["If-Modified-Since"] = lastModified;
|
|
1676
|
-
onProgress?.("Fetching npm advisory database from OSV.dev\u2026");
|
|
1677
|
-
const res = await fetch(OSV_NPM_ZIP_URL, { headers });
|
|
1678
|
-
if (res.status === 304 && previous) {
|
|
1679
|
-
const meta2 = { ...previous, lastUpdated: nowIso };
|
|
1680
|
-
await writeFile2(offlineMetaPath(cacheDir), JSON.stringify(meta2, null, 2));
|
|
1681
|
-
return { status: "unchanged", meta: meta2 };
|
|
1682
|
-
}
|
|
1683
|
-
if (!res.ok || !res.body) {
|
|
1684
|
-
throw new Error(`OSV database download failed: HTTP ${res.status}`);
|
|
1685
|
-
}
|
|
1686
|
-
const tmp = await mkdtemp(join3(tmpdir(), "lockhawk-osv-"));
|
|
1687
|
-
const zipPath = join3(tmp, "all.zip");
|
|
1688
|
-
let records;
|
|
1689
|
-
try {
|
|
1690
|
-
await pipeline(
|
|
1691
|
-
Readable.fromWeb(res.body),
|
|
1692
|
-
createWriteStream(zipPath)
|
|
1693
|
-
);
|
|
1694
|
-
onProgress?.("Extracting advisories\u2026");
|
|
1695
|
-
records = await readZipRecords(zipPath);
|
|
1696
|
-
} finally {
|
|
1697
|
-
await rm(tmp, { recursive: true, force: true });
|
|
1698
|
-
}
|
|
1699
|
-
onProgress?.("Building package index\u2026");
|
|
1700
|
-
const byName = groupByPackage(records);
|
|
1701
|
-
await writeShards(cacheDir, byName);
|
|
1702
|
-
const meta = {
|
|
1703
|
-
ecosystem: "npm",
|
|
1704
|
-
source: "osv.dev",
|
|
1705
|
-
lastUpdated: nowIso,
|
|
1706
|
-
recordCount: records.length,
|
|
1707
|
-
packageCount: byName.size,
|
|
1708
|
-
etag: res.headers.get("etag") ?? void 0,
|
|
1709
|
-
lastModified: res.headers.get("last-modified") ?? void 0
|
|
1710
|
-
};
|
|
1711
|
-
await writeFile2(offlineMetaPath(cacheDir), JSON.stringify(meta, null, 2));
|
|
1712
|
-
return { status: "updated", meta };
|
|
1713
|
-
}
|
|
1714
|
-
function bucketDir(cacheDir) {
|
|
1715
|
-
return join3(offlineDbDir(cacheDir), "by-name");
|
|
1716
|
-
}
|
|
1717
|
-
function bucketPath(cacheDir, bucket) {
|
|
1718
|
-
return join3(bucketDir(cacheDir), `${bucket}.json.gz`);
|
|
1719
|
-
}
|
|
1720
|
-
async function loadAdvisoriesForPackages(cacheDir, names) {
|
|
1721
|
-
if (!await readOfflineMeta(cacheDir)) throw new OfflineDbMissingError();
|
|
1722
|
-
const namesByBucket = /* @__PURE__ */ new Map();
|
|
1723
|
-
for (const name of new Set(names)) {
|
|
1724
|
-
const bucket = shardBucket(name);
|
|
1725
|
-
const list = namesByBucket.get(bucket);
|
|
1726
|
-
if (list) list.push(name);
|
|
1727
|
-
else namesByBucket.set(bucket, [name]);
|
|
1728
|
-
}
|
|
1729
|
-
const result = /* @__PURE__ */ new Map();
|
|
1730
|
-
await Promise.all(
|
|
1731
|
-
[...namesByBucket.entries()].map(async ([bucket, bucketNames]) => {
|
|
1732
|
-
try {
|
|
1733
|
-
const data = JSON.parse(
|
|
1734
|
-
gunzipSync(await readFile2(bucketPath(cacheDir, bucket))).toString("utf8")
|
|
1735
|
-
);
|
|
1736
|
-
for (const name of bucketNames) {
|
|
1737
|
-
const records = data[name];
|
|
1738
|
-
if (records?.length) result.set(name, records);
|
|
1739
|
-
}
|
|
1740
|
-
} catch {
|
|
1741
|
-
}
|
|
1742
|
-
})
|
|
1743
|
-
);
|
|
1744
|
-
return result;
|
|
1745
|
-
}
|
|
1746
|
-
function groupByPackage(records) {
|
|
1747
|
-
const byName = /* @__PURE__ */ new Map();
|
|
1748
|
-
for (const record of records) {
|
|
1749
|
-
if (record.withdrawn) continue;
|
|
1750
|
-
const names = /* @__PURE__ */ new Set();
|
|
1751
|
-
for (const affected of record.affected ?? []) {
|
|
1752
|
-
const ecosystem = affected.package?.ecosystem;
|
|
1753
|
-
if (ecosystem && ecosystem !== "npm") continue;
|
|
1754
|
-
if (affected.package?.name) names.add(affected.package.name);
|
|
1755
|
-
}
|
|
1756
|
-
for (const name of names) {
|
|
1757
|
-
const list = byName.get(name);
|
|
1758
|
-
if (list) list.push(record);
|
|
1759
|
-
else byName.set(name, [record]);
|
|
1760
|
-
}
|
|
1761
|
-
}
|
|
1762
|
-
return byName;
|
|
1763
|
-
}
|
|
1764
|
-
async function writeShards(cacheDir, byName) {
|
|
1765
|
-
const dir = bucketDir(cacheDir);
|
|
1766
|
-
await rm(dir, { recursive: true, force: true });
|
|
1767
|
-
await mkdir2(dir, { recursive: true });
|
|
1768
|
-
const buckets = /* @__PURE__ */ new Map();
|
|
1769
|
-
for (const [name, records] of byName) {
|
|
1770
|
-
const bucket = shardBucket(name);
|
|
1771
|
-
const map = buckets.get(bucket) ?? {};
|
|
1772
|
-
map[name] = records;
|
|
1773
|
-
buckets.set(bucket, map);
|
|
1774
|
-
}
|
|
1775
|
-
const entries = [...buckets.entries()];
|
|
1776
|
-
const BATCH = 256;
|
|
1777
|
-
for (let i = 0; i < entries.length; i += BATCH) {
|
|
1778
|
-
await Promise.all(
|
|
1779
|
-
entries.slice(i, i + BATCH).map(
|
|
1780
|
-
([bucket, map]) => writeFile2(bucketPath(cacheDir, bucket), gzipSync(Buffer.from(JSON.stringify(map))))
|
|
1781
|
-
)
|
|
1782
|
-
);
|
|
1783
|
-
}
|
|
1784
|
-
}
|
|
1785
|
-
function readZipRecords(zipPath) {
|
|
1786
|
-
return new Promise((resolve3, reject) => {
|
|
1787
|
-
const out = [];
|
|
1788
|
-
yauzl.open(zipPath, { lazyEntries: true }, (err, zip) => {
|
|
1789
|
-
if (err || !zip) return reject(err ?? new Error("Failed to open OSV zip"));
|
|
1790
|
-
zip.on("entry", (entry) => {
|
|
1791
|
-
if (!entry.fileName.endsWith(".json")) return zip.readEntry();
|
|
1792
|
-
if (entry.uncompressedSize > MAX_ENTRY_BYTES) return zip.readEntry();
|
|
1793
|
-
zip.openReadStream(entry, (streamErr, stream) => {
|
|
1794
|
-
if (streamErr || !stream) return zip.readEntry();
|
|
1795
|
-
const chunks = [];
|
|
1796
|
-
stream.on("data", (chunk) => chunks.push(chunk));
|
|
1797
|
-
stream.on("error", () => zip.readEntry());
|
|
1798
|
-
stream.on("end", () => {
|
|
1799
|
-
try {
|
|
1800
|
-
out.push(JSON.parse(Buffer.concat(chunks).toString("utf8")));
|
|
1801
|
-
} catch {
|
|
1802
|
-
}
|
|
1803
|
-
zip.readEntry();
|
|
1804
|
-
});
|
|
1805
|
-
});
|
|
1806
|
-
});
|
|
1807
|
-
zip.on("end", () => resolve3(out));
|
|
1808
|
-
zip.on("error", reject);
|
|
1809
|
-
zip.readEntry();
|
|
1810
|
-
});
|
|
1811
|
-
});
|
|
1812
|
-
}
|
|
1813
|
-
|
|
1814
|
-
// src/osv/source.ts
|
|
1815
|
-
var HOUR_MS = 36e5;
|
|
1816
|
-
var OfflineSource = class {
|
|
1817
|
-
constructor(opts) {
|
|
1818
|
-
this.opts = opts;
|
|
1819
|
-
}
|
|
1820
|
-
opts;
|
|
1821
|
-
async prepare(packages) {
|
|
1822
|
-
const meta = await readOfflineMeta(this.opts.cacheDir);
|
|
1823
|
-
if (!meta) throw new OfflineDbMissingError();
|
|
1824
|
-
const map = await loadAdvisoriesForPackages(
|
|
1825
|
-
this.opts.cacheDir,
|
|
1826
|
-
packages.map((p) => p.name)
|
|
1827
|
-
);
|
|
1828
|
-
const ageHours = (Date.now() - Date.parse(meta.lastUpdated)) / HOUR_MS;
|
|
1829
|
-
const stale = ageHours > (this.opts.staleAfterHours ?? 24);
|
|
1830
|
-
const warnings = stale ? [
|
|
1831
|
-
`Offline database is ${Math.round(ageHours)}h old. Run \`lockhawk db update\` for current results.`
|
|
1832
|
-
] : [];
|
|
1833
|
-
return {
|
|
1834
|
-
candidatesFor: (name) => map.get(name) ?? [],
|
|
1835
|
-
database: {
|
|
1836
|
-
source: "offline",
|
|
1837
|
-
recordCount: meta.recordCount,
|
|
1838
|
-
lastUpdated: meta.lastUpdated,
|
|
1839
|
-
ageHours: Math.round(ageHours * 10) / 10,
|
|
1840
|
-
stale,
|
|
1841
|
-
warnings
|
|
1842
|
-
}
|
|
1843
|
-
};
|
|
1844
|
-
}
|
|
1845
|
-
};
|
|
1846
|
-
var OnlineSource = class {
|
|
1847
|
-
constructor(opts) {
|
|
1848
|
-
this.opts = opts;
|
|
1849
|
-
}
|
|
1850
|
-
opts;
|
|
1851
|
-
async prepare(packages) {
|
|
1852
|
-
const client = new OsvClient({
|
|
1853
|
-
cacheDir: this.opts.cacheDir,
|
|
1854
|
-
concurrency: this.opts.concurrency,
|
|
1855
|
-
noCache: this.opts.noCache,
|
|
1856
|
-
cacheTtlHours: this.opts.cacheTtlHours
|
|
1857
|
-
});
|
|
1858
|
-
try {
|
|
1859
|
-
const map = await client.fetchAdvisories(packages);
|
|
1860
|
-
return {
|
|
1861
|
-
candidatesFor: (name) => map.get(name) ?? [],
|
|
1862
|
-
database: { source: "online", stale: false, warnings: [] }
|
|
1863
|
-
};
|
|
1864
|
-
} catch (err) {
|
|
1865
|
-
if (this.opts.strictNetwork) throw err;
|
|
1866
|
-
const reason = err instanceof Error ? err.message : String(err);
|
|
1867
|
-
return {
|
|
1868
|
-
candidatesFor: () => [],
|
|
1869
|
-
database: {
|
|
1870
|
-
source: "online",
|
|
1871
|
-
stale: true,
|
|
1872
|
-
warnings: [
|
|
1873
|
-
`OSV.dev was unreachable (${reason}). Scan completed without online data \u2014 results may be incomplete.`
|
|
1874
|
-
]
|
|
1875
|
-
}
|
|
1876
|
-
};
|
|
1877
|
-
}
|
|
1878
|
-
}
|
|
1879
|
-
};
|
|
1880
|
-
var AutoSource = class {
|
|
1881
|
-
constructor(opts) {
|
|
1882
|
-
this.opts = opts;
|
|
1883
|
-
}
|
|
1884
|
-
opts;
|
|
1885
|
-
async prepare(packages) {
|
|
1886
|
-
const meta = await readOfflineMeta(this.opts.cacheDir);
|
|
1887
|
-
const ttl = this.opts.staleAfterHours ?? 24;
|
|
1888
|
-
if (meta) {
|
|
1889
|
-
const ageHours = (Date.now() - Date.parse(meta.lastUpdated)) / HOUR_MS;
|
|
1890
|
-
if (ageHours <= ttl) {
|
|
1891
|
-
return new OfflineSource(this.opts).prepare(packages);
|
|
1892
|
-
}
|
|
1893
|
-
}
|
|
1894
|
-
try {
|
|
1895
|
-
return await new OnlineSource({ ...this.opts, strictNetwork: true }).prepare(packages);
|
|
1896
|
-
} catch (err) {
|
|
1897
|
-
if (meta) {
|
|
1898
|
-
const resolved = await new OfflineSource(this.opts).prepare(packages);
|
|
1899
|
-
resolved.database.warnings.push(
|
|
1900
|
-
"Used the stale offline database because OSV.dev was unreachable."
|
|
1901
|
-
);
|
|
1902
|
-
return resolved;
|
|
1903
|
-
}
|
|
1904
|
-
if (this.opts.strictNetwork) throw err;
|
|
1905
|
-
const reason = err instanceof Error ? err.message : String(err);
|
|
1906
|
-
return {
|
|
1907
|
-
candidatesFor: () => [],
|
|
1908
|
-
database: {
|
|
1909
|
-
source: "online",
|
|
1910
|
-
stale: true,
|
|
1911
|
-
warnings: [
|
|
1912
|
-
`No offline database and OSV.dev was unreachable (${reason}). Run \`lockhawk db update\`.`
|
|
1913
|
-
]
|
|
1914
|
-
}
|
|
1915
|
-
};
|
|
1916
|
-
}
|
|
1917
|
-
}
|
|
1918
|
-
};
|
|
1919
|
-
function createSource(mode, opts) {
|
|
1920
|
-
switch (mode) {
|
|
1921
|
-
case "offline":
|
|
1922
|
-
return new OfflineSource(opts);
|
|
1923
|
-
case "online":
|
|
1924
|
-
return new OnlineSource(opts);
|
|
1925
|
-
default:
|
|
1926
|
-
return new AutoSource(opts);
|
|
1927
|
-
}
|
|
1928
|
-
}
|
|
1929
|
-
|
|
1930
|
-
// src/engine.ts
|
|
1931
|
-
var TOOL = { name: "lockhawk", version: "0.1.0" };
|
|
1932
|
-
async function scan(options = {}, source) {
|
|
1933
|
-
const start = Date.now();
|
|
1934
|
-
const dir = resolve2(options.path ?? ".");
|
|
1935
|
-
const graph = loadDependencyGraph(dir);
|
|
1936
|
-
const detected = detectLockfile(dir);
|
|
1937
|
-
const scannable = uniqueScannablePackages(graph, options.prodOnly);
|
|
1938
|
-
const cacheDir = resolveCacheDir(options.cacheDir);
|
|
1939
|
-
const src = source ?? createSource(options.mode ?? "auto", {
|
|
1940
|
-
cacheDir,
|
|
1941
|
-
concurrency: options.concurrency,
|
|
1942
|
-
noCache: options.noCache,
|
|
1943
|
-
cacheTtlHours: options.cacheTtlHours,
|
|
1944
|
-
strictNetwork: options.strictNetwork
|
|
1945
|
-
});
|
|
1946
|
-
options.onProgress?.("Checking dependencies against OSV.dev\u2026");
|
|
1947
|
-
const resolved = await src.prepare(scannable);
|
|
1948
|
-
let findings = buildFindings(graph, resolved.candidatesFor, {
|
|
1949
|
-
prodOnly: options.prodOnly,
|
|
1950
|
-
ignore: options.ignore && options.ignore.length ? new Set(options.ignore) : void 0
|
|
1951
|
-
});
|
|
1952
|
-
if (options.severityThreshold && options.severityThreshold !== "none") {
|
|
1953
|
-
findings = findings.filter(
|
|
1954
|
-
(f) => severityAtLeast(f.severity.level, options.severityThreshold)
|
|
1955
|
-
);
|
|
1956
|
-
}
|
|
1957
|
-
findings.sort(bySeverityThenName);
|
|
1958
|
-
const unscannable = unscannablePackages(graph);
|
|
1959
|
-
return {
|
|
1960
|
-
schemaVersion: 1,
|
|
1961
|
-
tool: { ...TOOL },
|
|
1962
|
-
scannedAt: new Date(start).toISOString(),
|
|
1963
|
-
target: {
|
|
1964
|
-
path: dir,
|
|
1965
|
-
manager: graph.manager,
|
|
1966
|
-
lockfile: detected?.filename ?? "",
|
|
1967
|
-
root: graph.root
|
|
1968
|
-
},
|
|
1969
|
-
database: resolved.database,
|
|
1970
|
-
summary: summarize(findings),
|
|
1971
|
-
stats: {
|
|
1972
|
-
totalPackages: Object.keys(graph.nodes).length,
|
|
1973
|
-
uniquePackages: scannable.length,
|
|
1974
|
-
directDependencies: graph.directKeys.length,
|
|
1975
|
-
unscannable: unscannable.length,
|
|
1976
|
-
durationMs: Date.now() - start
|
|
1977
|
-
},
|
|
1978
|
-
findings,
|
|
1979
|
-
unscannable
|
|
1980
|
-
};
|
|
1981
|
-
}
|
|
1982
|
-
function uniqueScannablePackages(graph, prodOnly) {
|
|
1983
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1984
|
-
const out = [];
|
|
1985
|
-
for (const node of Object.values(graph.nodes)) {
|
|
1986
|
-
if (node.unscannable) continue;
|
|
1987
|
-
if (prodOnly && node.scope === "dev") continue;
|
|
1988
|
-
if (seen.has(node.key)) continue;
|
|
1989
|
-
seen.add(node.key);
|
|
1990
|
-
out.push({ name: node.name, version: node.version });
|
|
1991
|
-
}
|
|
1992
|
-
return out;
|
|
1993
|
-
}
|
|
1994
|
-
function unscannablePackages(graph) {
|
|
1995
|
-
const out = [];
|
|
1996
|
-
for (const node of Object.values(graph.nodes)) {
|
|
1997
|
-
if (node.unscannable)
|
|
1998
|
-
out.push({ name: node.name, version: node.version, reason: node.unscannable.reason });
|
|
1999
|
-
}
|
|
2000
|
-
return out;
|
|
2001
|
-
}
|
|
2002
|
-
function summarize(findings) {
|
|
2003
|
-
const summary = {
|
|
2004
|
-
total: findings.length,
|
|
2005
|
-
critical: 0,
|
|
2006
|
-
high: 0,
|
|
2007
|
-
medium: 0,
|
|
2008
|
-
low: 0,
|
|
2009
|
-
unknown: 0,
|
|
2010
|
-
none: 0,
|
|
2011
|
-
vulnerablePackages: 0,
|
|
2012
|
-
fixable: 0
|
|
2013
|
-
};
|
|
2014
|
-
const vulnerable = /* @__PURE__ */ new Set();
|
|
2015
|
-
for (const f of findings) {
|
|
2016
|
-
summary[f.severity.level] += 1;
|
|
2017
|
-
vulnerable.add(`${f.packageName}@${f.version}`);
|
|
2018
|
-
if (f.fixedVersions.length > 0) summary.fixable += 1;
|
|
2019
|
-
}
|
|
2020
|
-
summary.vulnerablePackages = vulnerable.size;
|
|
2021
|
-
return summary;
|
|
2022
|
-
}
|
|
2023
|
-
function bySeverityThenName(a, b) {
|
|
2024
|
-
const rank = SEVERITY_RANK[b.severity.level] - SEVERITY_RANK[a.severity.level];
|
|
2025
|
-
if (rank !== 0) return rank;
|
|
2026
|
-
return a.packageName.localeCompare(b.packageName);
|
|
2027
|
-
}
|
|
2028
|
-
function shouldFail(summary, failOn) {
|
|
2029
|
-
const levels = ["critical", "high", "medium", "low", "unknown"];
|
|
2030
|
-
return levels.some(
|
|
2031
|
-
(level) => SEVERITY_RANK[level] >= SEVERITY_RANK[failOn] && summary[level] > 0
|
|
2032
|
-
);
|
|
2033
|
-
}
|
|
2034
|
-
|
|
2035
|
-
// src/report/json.ts
|
|
2036
|
-
function toJson(result) {
|
|
2037
|
-
return JSON.stringify(result, null, 2);
|
|
2038
|
-
}
|
|
2039
|
-
|
|
2040
|
-
// src/report/sarif.ts
|
|
2041
|
-
var INFO_URI = "https://github.com/lockhawk/lockhawk";
|
|
2042
|
-
function helpUriFor(references) {
|
|
2043
|
-
return references.find((ref) => {
|
|
2044
|
-
try {
|
|
2045
|
-
const u = new URL(ref);
|
|
2046
|
-
return u.protocol === "http:" || u.protocol === "https:";
|
|
2047
|
-
} catch {
|
|
2048
|
-
return false;
|
|
2049
|
-
}
|
|
2050
|
-
});
|
|
2051
|
-
}
|
|
2052
|
-
function securitySeverity(finding) {
|
|
2053
|
-
if (typeof finding.severity.score === "number") return finding.severity.score;
|
|
2054
|
-
switch (finding.severity.level) {
|
|
2055
|
-
case "critical":
|
|
2056
|
-
return 9.5;
|
|
2057
|
-
case "high":
|
|
2058
|
-
return 7.5;
|
|
2059
|
-
case "medium":
|
|
2060
|
-
return 5;
|
|
2061
|
-
case "low":
|
|
2062
|
-
return 2;
|
|
2063
|
-
default:
|
|
2064
|
-
return 0;
|
|
2065
|
-
}
|
|
2066
|
-
}
|
|
2067
|
-
function sarifLevel(severity) {
|
|
2068
|
-
if (severity === "critical" || severity === "high") return "error";
|
|
2069
|
-
if (severity === "medium") return "warning";
|
|
2070
|
-
return "note";
|
|
2071
|
-
}
|
|
2072
|
-
function toSarif(result) {
|
|
2073
|
-
const lockfileUri = result.target.lockfile || "package-lock.json";
|
|
2074
|
-
const rules = /* @__PURE__ */ new Map();
|
|
2075
|
-
const results = [];
|
|
2076
|
-
for (const finding of result.findings) {
|
|
2077
|
-
if (!rules.has(finding.id)) {
|
|
2078
|
-
rules.set(finding.id, {
|
|
2079
|
-
id: finding.id,
|
|
2080
|
-
name: `${finding.packageName} ${finding.id}`,
|
|
2081
|
-
shortDescription: { text: finding.summary },
|
|
2082
|
-
fullDescription: finding.details ? { text: finding.details } : void 0,
|
|
2083
|
-
helpUri: helpUriFor(finding.references),
|
|
2084
|
-
properties: {
|
|
2085
|
-
"security-severity": String(securitySeverity(finding)),
|
|
2086
|
-
tags: ["security", "dependency", finding.severity.level]
|
|
2087
|
-
}
|
|
2088
|
-
});
|
|
2089
|
-
}
|
|
2090
|
-
const path = finding.dependencyPaths[0]?.join(" \u203A ") ?? finding.packageName;
|
|
2091
|
-
const fix = finding.fixedVersions[0] ? ` Fixed in ${finding.fixedVersions[0]}.` : "";
|
|
2092
|
-
results.push({
|
|
2093
|
-
ruleId: finding.id,
|
|
2094
|
-
level: sarifLevel(finding.severity.level),
|
|
2095
|
-
message: {
|
|
2096
|
-
text: `${finding.severity.level.toUpperCase()}: ${finding.packageName}@${finding.version} \u2014 ${finding.summary}.${fix} Path: ${path}.`
|
|
2097
|
-
},
|
|
2098
|
-
locations: [
|
|
2099
|
-
{
|
|
2100
|
-
physicalLocation: {
|
|
2101
|
-
artifactLocation: { uri: lockfileUri },
|
|
2102
|
-
region: { startLine: 1 }
|
|
2103
|
-
}
|
|
2104
|
-
}
|
|
2105
|
-
],
|
|
2106
|
-
partialFingerprints: {
|
|
2107
|
-
lockhawkFinding: `${finding.packageName}@${finding.version}@${finding.id}`
|
|
2108
|
-
},
|
|
2109
|
-
properties: {
|
|
2110
|
-
package: finding.packageName,
|
|
2111
|
-
version: finding.version,
|
|
2112
|
-
scope: finding.scope,
|
|
2113
|
-
fixedVersions: finding.fixedVersions
|
|
2114
|
-
}
|
|
2115
|
-
});
|
|
2116
|
-
}
|
|
2117
|
-
return {
|
|
2118
|
-
$schema: "https://json.schemastore.org/sarif-2.1.0.json",
|
|
2119
|
-
version: "2.1.0",
|
|
2120
|
-
runs: [
|
|
2121
|
-
{
|
|
2122
|
-
tool: {
|
|
2123
|
-
driver: {
|
|
2124
|
-
name: result.tool.name,
|
|
2125
|
-
version: result.tool.version,
|
|
2126
|
-
informationUri: INFO_URI,
|
|
2127
|
-
rules: [...rules.values()]
|
|
2128
|
-
}
|
|
2129
|
-
},
|
|
2130
|
-
results,
|
|
2131
|
-
properties: {
|
|
2132
|
-
scannedAt: result.scannedAt,
|
|
2133
|
-
databaseSource: result.database.source,
|
|
2134
|
-
databaseAgeHours: result.database.ageHours,
|
|
2135
|
-
databaseStale: result.database.stale
|
|
2136
|
-
}
|
|
2137
|
-
}
|
|
2138
|
-
]
|
|
2139
|
-
};
|
|
2140
|
-
}
|
|
2141
|
-
function toSarifString(result) {
|
|
2142
|
-
return JSON.stringify(toSarif(result), null, 2);
|
|
2143
|
-
}
|
|
2144
|
-
|
|
2145
|
-
// src/report/html.ts
|
|
2146
|
-
function toHtml(result) {
|
|
2147
|
-
return renderHtml(result);
|
|
2148
|
-
}
|
|
2149
|
-
var SEVERITY_COLORS = {
|
|
2150
|
-
critical: "#ff5d67",
|
|
2151
|
-
high: "#ff9f43",
|
|
2152
|
-
medium: "#ffd23f",
|
|
2153
|
-
low: "#5b9dff",
|
|
2154
|
-
unknown: "#8b95a5",
|
|
2155
|
-
none: "#34d399"
|
|
2156
|
-
};
|
|
2157
|
-
function esc(text) {
|
|
2158
|
-
return text.replace(
|
|
2159
|
-
/[&<>"]/g,
|
|
2160
|
-
(c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c]
|
|
2161
|
-
);
|
|
2162
|
-
}
|
|
2163
|
-
function httpHref(url) {
|
|
2164
|
-
try {
|
|
2165
|
-
const u = new URL(url);
|
|
2166
|
-
return u.protocol === "http:" || u.protocol === "https:" ? url : void 0;
|
|
2167
|
-
} catch {
|
|
2168
|
-
return void 0;
|
|
2169
|
-
}
|
|
2170
|
-
}
|
|
2171
|
-
var MARK = `<svg class="mark" viewBox="0 0 48 48" fill="none" aria-hidden="true">
|
|
1
|
+
var $={none:0,unknown:1,low:2,medium:3,high:4,critical:5};function _e(e,n){return $[e]>=$[n]}function S(e,n){return`${e}@${n}`}import{statSync as gr}from"fs";import{resolve as mr}from"path";import{existsSync as qe,readFileSync as Gn,statSync as Bn}from"fs";import{join as Fe}from"path";var zn=[{filename:"pnpm-lock.yaml",manager:"pnpm"},{filename:"yarn.lock",manager:"yarn"},{filename:"package-lock.json",manager:"npm"},{filename:"npm-shrinkwrap.json",manager:"npm"}];function F(e){for(let{filename:n,manager:r}of zn){let s=Fe(e,n);if(qe(s)&&Bn(s).isFile())return{manager:r,path:s,filename:n}}}function C(e){let n=Fe(e,"package.json"),r={};if(qe(n))try{r=JSON.parse(Gn(n,"utf8"))}catch{r={}}let s=t=>t&&typeof t=="object"?t:{};return{name:typeof r.name=="string"?r.name:"project",version:typeof r.version=="string"?r.version:void 0,dependencies:s(r.dependencies),devDependencies:s(r.devDependencies),optionalDependencies:s(r.optionalDependencies),peerDependencies:s(r.peerDependencies)}}function le(e,n){let r=new Set,s=[];for(let t of e)r.has(t)||(r.add(t),s.push(t));for(let t=0;t<s.length;t++){let a=n[s[t]];if(a)for(let i of a.dependencies)r.has(i)||(r.add(i),s.push(i))}return r}function pe(e){let n=le(e.directProd,e.nodes),r=le(e.directOptional,e.nodes),s=le(e.directDev,e.nodes),t=new Set([...e.directProd,...e.directOptional,...e.directDev]),a={};for(let[i,o]of Object.entries(e.nodes)){let d;n.has(i)?d="prod":r.has(i)?d="optional":s.has(i)?d="dev":d="prod",a[i]={key:i,name:o.name,version:o.version,scope:d,direct:t.has(i),dependencies:Yn(o.dependencies),...o.unscannable?{unscannable:o.unscannable}:{}}}return{manager:e.manager,lockfilePath:e.lockfilePath,lockfileVersion:e.lockfileVersion,root:e.root,nodes:a,directKeys:[...t].filter(i=>a[i]!==void 0)}}function Yn(e){return e.length>1?[...new Set(e)]:e}import{readFileSync as Wn}from"fs";import{dirname as Jn}from"path";function Ke(e){let n=JSON.parse(Wn(e,"utf8"));return n.packages?Zn(n,e):Qn(n,e)}function Xn(e){let n="node_modules/",r=e.lastIndexOf(n);return r===-1?e:e.slice(r+n.length)}function Zn(e,n){let r=e.packages??{},s=r[""]??{},t={name:s.name??e.name??"project",version:s.version??e.version},a=Object.create(null),i=[],o=Object.create(null);for(let[f,p]of Object.entries(r)){if(f===""||p.link)continue;let m=p.name??Xn(f),v=p.version;if(!v)continue;let g=S(m,v);if(o[f]=g,!a[g]){let x={key:g,name:m,version:v,dependencies:[]};f.includes("node_modules")||(x.unscannable={reason:"local workspace package"},i.push({name:m,version:v,reason:"local workspace package"})),a[g]=x}}let d=(f,p)=>{let m=f;for(;;){let v=`${m?`${m}/`:""}node_modules/${p}`,g=r[v];if(g)return g.link&&typeof g.resolved=="string"?g.resolved:v;if(!m)return;let x=m.lastIndexOf("/node_modules/");m=x===-1?"":m.slice(0,x)}},c=new Set,l=new Set,u=new Set;for(let[f,p]of Object.entries(r)){if(p.link)continue;let m=f===""?void 0:o[f],v=f===""||!f.includes("node_modules"),g=(x,w)=>{for(let R of x){let O=d(f,R);if(!O)continue;let P=o[O];P&&(m&&m!==P&&a[m].dependencies.push(P),w&&w.add(P))}};g(Object.keys(p.dependencies??{}),v?c:void 0),g(Object.keys(p.optionalDependencies??{}),v?u:void 0),v&&g(Object.keys(p.devDependencies??{}),l)}return{manager:"npm",lockfilePath:n,lockfileVersion:e.lockfileVersion,root:t,nodes:a,directProd:[...c],directDev:[...l],directOptional:[...u],unscannable:i}}var Te=500;function Qn(e,n){let r=C(Jn(n)),s={name:e.name??r.name,version:e.version??r.version},t=Object.create(null),a=p=>{let m=new Map;for(let[v,g]of Object.entries(p??{}))g?.version&&m.set(v,g.version);return m},i=(p,m)=>{for(let v=m.length-1;v>=0;v--){let g=m[v].get(p);if(g)return g}},o=(p,m)=>{if(m.length>Te)throw new Error(`dependency nesting exceeds ${Te} levels (malformed or malicious lockfile)`);let v=a(p),g=[...m,v];for(let[x,w]of Object.entries(p??{})){let R=w?.version;if(!R)continue;let O=S(x,R);t[O]||(t[O]={key:O,name:x,version:R,dependencies:[]});let P=[...g,a(w.dependencies)];for(let M of Object.keys(w.requires??{})){let N=i(M,P);if(N){let D=S(M,N);D!==O&&t[O].dependencies.push(D)}}w.dependencies&&o(w.dependencies,g)}};o(e.dependencies,[]);let d=a(e.dependencies),c=[],l=[],u=[],f=(p,m)=>{for(let v of p){let g=d.get(v);g&&m.push(S(v,g))}};return f(Object.keys(r.dependencies),c),f(Object.keys(r.optionalDependencies),u),f(Object.keys(r.devDependencies),l),{manager:"npm",lockfilePath:n,lockfileVersion:e.lockfileVersion,root:s,nodes:t,directProd:c,directDev:l,directOptional:u,unscannable:[]}}import{readFileSync as er}from"fs";import{dirname as nr}from"path";import{createRequire as rr}from"module";import tr from"js-yaml";var sr=rr(import.meta.url),or=sr("@yarnpkg/lockfile");function Ue(e){let n=er(e,"utf8"),r=C(nr(e)),s=n.includes("__metadata"),{descriptorToVersion:t,entryByVersion:a,lockfileVersion:i}=s?ar(n):ir(n);return cr(e,r,t,a,i,s)}function Ge(e){let n=e.lastIndexOf("@");if(!(n<=0))return{name:e.slice(0,n),range:e.slice(n+1)}}function ir(e){let n=or.parse(e),r=new Map,s=new Map;for(let[t,a]of Object.entries(n.object))if(a.version)for(let i of t.split(/,\s*/)){let o=Ge(i);o&&r.set(`${o.name}@${o.range}`,a.version),o&&s.set(S(o.name,a.version),a)}return{descriptorToVersion:r,entryByVersion:s,lockfileVersion:1}}function ar(e){let n=tr.load(e)??{},r=new Map,s=new Map,t="berry";for(let[a,i]of Object.entries(n)){if(a==="__metadata"){let o=i.version;o!==void 0&&(t=o);continue}if(i.version)for(let o of a.split(/,\s*/)){let d=Ge(o);d&&(r.set(`${d.name}@${d.range}`,i.version),s.set(S(d.name,i.version),i))}}return{descriptorToVersion:r,entryByVersion:s,lockfileVersion:t}}function cr(e,n,r,s,t,a){let i=(c,l)=>{let u=a?[`${c}@${l}`,`${c}@npm:${l}`]:[`${c}@${l}`];for(let f of u){let p=r.get(f);if(p)return p}},o=Object.create(null);for(let[c,l]of s){let u=c.lastIndexOf("@"),f=c.slice(0,u),p=c.slice(u+1);o[c]||(o[c]={key:c,name:f,version:p,dependencies:[]});let m=v=>{for(let[g,x]of Object.entries(v??{})){let w=i(g,x);if(!w)continue;let R=S(g,w);R!==c&&o[c].dependencies.push(R)}};m(l.dependencies),m(l.optionalDependencies)}let d=c=>{let l=[];for(let[u,f]of Object.entries(c)){let p=i(u,f);p&&l.push(S(u,p))}return l};return{manager:"yarn",lockfilePath:e,lockfileVersion:t,root:{name:n.name,version:n.version},nodes:o,directProd:d(n.dependencies),directDev:d(n.devDependencies),directOptional:d(n.optionalDependencies),unscannable:[]}}import{readFileSync as dr}from"fs";import{dirname as lr}from"path";import pr from"js-yaml";function ze(e){let n=pr.load(dr(e,"utf8"))??{},r=C(lr(e)),s=fr(n.lockfileVersion),t=Object.create(null);for(let u of Object.keys(n.packages??{})){let f=Be(u,s);if(!f)continue;let p=S(f.name,f.version);t[p]||(t[p]={key:p,name:f.name,version:f.version,dependencies:[]})}let a=s>=9?n.snapshots??{}:n.packages??{};for(let[u,f]of Object.entries(a)){let p=Be(u,s);if(!p)continue;let m=S(p.name,p.version);t[m]||(t[m]={key:m,name:p.name,version:p.version,dependencies:[]});let v=g=>{for(let[x,w]of Object.entries(g??{})){let R=S(x,Y(w,s));R!==m&&t[R]&&t[m].dependencies.push(R)}};v(f.dependencies),v(f.optionalDependencies)}let i=n.importers??{".":{dependencies:n.dependencies,devDependencies:n.devDependencies,optionalDependencies:n.optionalDependencies}},o=new Set,d=new Set,c=new Set,l=(u,f)=>{for(let[p,m]of Object.entries(u??{})){let v=ur(m,s);if(!v)continue;let g=S(p,v);t[g]&&f.add(g)}};for(let u of Object.values(i))l(u.dependencies,o),l(u.optionalDependencies,c),l(u.devDependencies,d);return{manager:"pnpm",lockfilePath:e,lockfileVersion:n.lockfileVersion,root:{name:r.name,version:r.version},nodes:t,directProd:[...o],directDev:[...d],directOptional:[...c],unscannable:[]}}function fr(e){let n=parseFloat(String(e??"9"));return Number.isNaN(n)?9:Math.floor(n)}function Be(e,n){let r=e.startsWith("/")?e.slice(1):e;if(n<6){let i=r.lastIndexOf("/");if(i<=0)return;let o=r.slice(0,i),d=Y(r.slice(i+1),n);return d?{name:o,version:d}:void 0}let s=r.lastIndexOf("@");if(s<=0)return;let t=r.slice(0,s),a=Y(r.slice(s+1),n);return a?{name:t,version:a}:void 0}function Y(e,n){let r=e;if(n<6){let t=r.indexOf("_");t!==-1&&(r=r.slice(0,t))}let s=r.indexOf("(");return s!==-1&&(r=r.slice(0,s)),r}function ur(e,n){let r;if(typeof e=="string"?r=e:e&&typeof e=="object"&&typeof e.version=="string"&&(r=e.version),!!r&&!(r.startsWith("link:")||r.startsWith("file:")||r.startsWith("workspace:")))return Y(r,n)}var E=class extends Error{constructor(n){super(n),this.name="LockfileError"}};function vr(){let e=Number(process.env.LOCKHAWK_MAX_LOCKFILE_BYTES);return Number.isFinite(e)&&e>0?e:64*1024*1024}function Ye(e){let n=0;try{n=gr(e.path).size}catch{}let r=vr();if(n>r)throw new E(`${e.filename} is ${n} bytes, exceeding the ${r}-byte safety limit. Set LOCKHAWK_MAX_LOCKFILE_BYTES to raise it.`);try{switch(e.manager){case"npm":return Ke(e.path);case"yarn":return Ue(e.path);case"pnpm":return ze(e.path)}}catch(s){throw new E(`Failed to parse ${e.filename}: ${s instanceof Error?s.message:String(s)}`)}}function fe(e){let n=F(mr(e));if(!n)throw new E(`No lockfile found in ${e}. Expected one of: package-lock.json, yarn.lock, pnpm-lock.yaml.`);return pe(Ye(n))}function ue(e){return e.root.version?`${e.root.name}@${e.root.version}`:e.root.name}function ge(e,n){if(!e.nodes[n])return null;let r="\0root",s=new Map,t=new Set([r]),a=[r];for(let i=0;i<a.length;i++){let o=a[i],d=o===r?e.directKeys:e.nodes[o]?.dependencies??[];for(let c of d)if(!t.has(c)){if(t.add(c),s.set(c,o),c===n)return br(e,s,n,r);a.push(c)}}return[ue(e),n]}function br(e,n,r,s){let t=[],a=r;for(;a!==void 0&&a!==s;)t.unshift(a),a=n.get(a);return t.unshift(ue(e)),t}import I from"semver";function We(e,n){if(n.versions?.includes(e))return!0;let r=I.parse(e,{loose:!0});if(!r)return!1;for(let s of n.ranges??[])if(s.type!=="GIT"&&yr(r,s.events))return!0;return!1}function yr(e,n){let r=[];for(let t of n)t.introduced!==void 0?r.push({kind:"introduced",version:t.introduced==="0"?null:me(t.introduced)}):t.fixed!==void 0?r.push({kind:"fixed",version:me(t.fixed)}):t.last_affected!==void 0&&r.push({kind:"last_affected",version:me(t.last_affected)});r.sort((t,a)=>t.version===null?a.version===null?0:-1:a.version===null?1:t.version.compare(a.version));let s=!1;for(let t of r){if(t.version===null){s=!0;continue}let a=e.compare(t.version);switch(t.kind){case"introduced":a>=0&&(s=!0);break;case"fixed":a>=0&&(s=!1);break;case"last_affected":a>0&&(s=!1);break}}return s}function me(e){return I.parse(e,{loose:!0})??I.coerce(e)}function W(e,n,r){if(r.withdrawn)return{affected:!1,fixedVersions:[]};let s=!1,t=new Set;for(let a of r.affected??[]){let i=a.package?.ecosystem;if(!(i&&i!=="npm")&&!(a.package?.name&&a.package.name!==e)&&We(n,a)){s=!0;for(let o of a.ranges??[])for(let d of o.events)d.fixed&&t.add(d.fixed)}}return{affected:s,fixedVersions:[...t]}}function ve(e,n){let r=I.parse(e,{loose:!0});return n.map(t=>I.parse(t,{loose:!0})??I.coerce(t)).filter(t=>t!==null).filter(t=>r?t.compare(r)>0:!0).sort((t,a)=>t.compare(a))[0]?.version}function Je(e){return[e.id,...e.aliases??[]]}function Xe(e){let n=[...e];return n.find(r=>r.startsWith("CVE-"))??n.find(r=>r.startsWith("GHSA-"))??n[0]}function be(e){if(e.length<=1)return e;let n=new Map,r=a=>{let i=a;for(;n.get(i)!==void 0&&n.get(i)!==i;)i=n.get(i);return n.set(a,i),i},s=(a,i)=>{n.set(r(a),r(i))};for(let a of e){let i=Je(a);n.has(i[0])||n.set(i[0],i[0]);for(let o of i.slice(1))n.has(o)||n.set(o,o),s(i[0],o)}let t=new Map;for(let a of e){let i=r(a.id),o=t.get(i);o?o.push(a):t.set(i,[a])}return[...t.values()].map(hr)}function hr(e){if(e.length===1)return e[0];let n=new Set,r=new Map,s=[],t=[],a,i,o;for(let c of e){for(let l of Je(c))n.add(l);for(let l of c.references??[])r.set(l.url,l);for(let l of c.severity??[])s.push(l);for(let l of c.affected??[])t.push(l);a??=c.summary,i??=c.details,o??=c.database_specific}let d=Xe(n);return{id:d,aliases:[...n].filter(c=>c!==d),summary:a,details:i,references:[...r.values()],severity:s,affected:t,database_specific:o}}import hn from"semver";var J={1e5:9.8,100001:9.5,100010:9.4,100011:8.7,100020:9.1,100021:8.1,100100:9.4,100101:8.9,100110:8.6,100111:7.4,100120:7.7,100121:6.4,100200:8.7,100201:7.5,100210:7.4,100211:6.3,100220:6.3,100221:4.9,101e3:9.4,101001:8.9,101010:8.8,101011:7.7,101020:7.6,101021:6.7,101100:8.6,101101:7.6,101110:7.4,101111:5.8,101120:5.9,101121:5,101200:7.2,101201:5.7,101210:5.7,101211:5.2,101220:5.2,101221:2.5,102001:8.3,102011:7,102021:5.4,102101:6.5,102111:5.8,102121:2.6,102201:5.3,102211:2.1,102221:1.3,11e4:9.5,110001:9,110010:8.8,110011:7.6,110020:7.6,110021:7,110100:9,110101:7.7,110110:7.5,110111:6.2,110120:6.1,110121:5.3,110200:7.7,110201:6.6,110210:6.8,110211:5.9,110220:5.2,110221:3,111e3:8.9,111001:7.8,111010:7.6,111011:6.7,111020:6.2,111021:5.8,111100:7.4,111101:5.9,111110:5.7,111111:5.7,111120:4.7,111121:2.3,111200:6.1,111201:5.2,111210:5.7,111211:2.9,111220:2.4,111221:1.6,112001:7.1,112011:5.9,112021:3,112101:5.8,112111:2.6,112121:1.5,112201:2.3,112211:1.3,112221:.6,2e5:9.3,200001:8.7,200010:8.6,200011:7.2,200020:7.5,200021:5.8,200100:8.6,200101:7.4,200110:7.4,200111:6.1,200120:5.6,200121:3.4,200200:7,200201:5.4,200210:5.2,200211:4,200220:4,200221:2.2,201e3:8.5,201001:7.5,201010:7.4,201011:5.5,201020:6.2,201021:5.1,201100:7.2,201101:5.7,201110:5.5,201111:4.1,201120:4.6,201121:1.9,201200:5.3,201201:3.6,201210:3.4,201211:1.9,201220:1.9,201221:.8,202001:6.4,202011:5.1,202021:2,202101:4.7,202111:2.1,202121:1.1,202201:2.4,202211:.9,202221:.4,21e4:8.8,210001:7.5,210010:7.3,210011:5.3,210020:6,210021:5,210100:7.3,210101:5.5,210110:5.9,210111:4,210120:4.1,210121:2,210200:5.4,210201:4.3,210210:4.5,210211:2.2,210220:2,210221:1.1,211e3:7.5,211001:5.5,211010:5.8,211011:4.5,211020:4,211021:2.1,211100:6.1,211101:5.1,211110:4.8,211111:1.8,211120:2,211121:.9,211200:4.6,211201:1.8,211210:1.7,211211:.7,211220:.8,211221:.2,212001:5.3,212011:2.4,212021:1.4,212101:2.4,212111:1.2,212121:.5,212201:1,212211:.3,212221:.1,"000000":10,"000001":9.9,"000010":9.8,"000011":9.5,"000020":9.5,"000021":9.2,"000100":10,"000101":9.6,"000110":9.3,"000111":8.7,"000120":9.1,"000121":8.1,"000200":9.3,"000201":9,"000210":8.9,"000211":8,"000220":8.1,"000221":6.8,"001000":9.8,"001001":9.5,"001010":9.5,"001011":9.2,"001020":9,"001021":8.4,"001100":9.3,"001101":9.2,"001110":8.9,"001111":8.1,"001120":8.1,"001121":6.5,"001200":8.8,"001201":8,"001210":7.8,"001211":7,"001220":6.9,"001221":4.8,"002001":9.2,"002011":8.2,"002021":7.2,"002101":7.9,"002111":6.9,"002121":5,"002201":6.9,"002211":5.5,"002221":2.7,"010000":9.9,"010001":9.7,"010010":9.5,"010011":9.2,"010020":9.2,"010021":8.5,"010100":9.5,"010101":9.1,"010110":9,"010111":8.3,"010120":8.4,"010121":7.1,"010200":9.2,"010201":8.1,"010210":8.2,"010211":7.1,"010220":7.2,"010221":5.3,"011000":9.5,"011001":9.3,"011010":9.2,"011011":8.5,"011020":8.5,"011021":7.3,"011100":9.2,"011101":8.2,"011110":8,"011111":7.2,"011120":7,"011121":5.9,"011200":8.4,"011201":7,"011210":7.1,"011211":5.2,"011220":5,"011221":3,"012001":8.6,"012011":7.5,"012021":5.2,"012101":7.1,"012111":5.2,"012121":2.9,"012201":6.3,"012211":2.9,"012221":1.7},ye={eq1:{0:["AV:N/PR:N/UI:N/"],1:["AV:A/PR:N/UI:N/","AV:N/PR:L/UI:N/","AV:N/PR:N/UI:P/"],2:["AV:P/PR:N/UI:N/","AV:A/PR:L/UI:P/"]},eq2:{0:["AC:L/AT:N/"],1:["AC:H/AT:N/","AC:L/AT:P/"]},eq3:{0:{0:["VC:H/VI:H/VA:H/CR:H/IR:H/AR:H/"],1:["VC:H/VI:H/VA:L/CR:M/IR:M/AR:H/","VC:H/VI:H/VA:H/CR:M/IR:M/AR:M/"]},1:{0:["VC:L/VI:H/VA:H/CR:H/IR:H/AR:H/","VC:H/VI:L/VA:H/CR:H/IR:H/AR:H/"],1:["VC:L/VI:H/VA:L/CR:H/IR:M/AR:H/","VC:L/VI:H/VA:H/CR:H/IR:M/AR:M/","VC:H/VI:L/VA:H/CR:M/IR:H/AR:M/","VC:H/VI:L/VA:L/CR:M/IR:H/AR:H/","VC:L/VI:L/VA:H/CR:H/IR:H/AR:M/"]},2:{1:["VC:L/VI:L/VA:L/CR:H/IR:H/AR:H/"]}},eq4:{0:["SC:H/SI:S/SA:S/"],1:["SC:H/SI:H/SA:H/"],2:["SC:L/SI:L/SA:L/"]},eq5:{0:["E:A/"],1:["E:P/"],2:["E:U/"]}},Ze={eq1:{0:1,1:4,2:5},eq2:{0:1,1:2},eq3eq6:{0:{0:7,1:6},1:{0:8,1:8},2:{1:10}},eq4:{0:6,1:5,2:4},eq5:{0:1,1:1,2:1}};var gn=["AV","AC","AT","PR","UI","VC","VI","VA","SC","SI","SA"],Qe={N:0,A:.1,L:.2,P:.3},en={N:0,L:.1,H:.2},nn={N:0,P:.1,A:.2},rn={L:0,H:.1},tn={N:0,P:.1},sn={H:0,L:.1,N:.2},on={H:0,L:.1,N:.2},an={H:0,L:.1,N:.2},cn={H:.1,L:.2,N:.3},dn={S:0,H:.1,L:.2,N:.3},ln={S:0,H:.1,L:.2,N:.3},pn={H:0,M:.1,L:.2},fn={H:0,M:.1,L:.2},un={H:0,M:.1,L:.2};function mn(e){let n=xr(e);if(!n)return null;let r=kr(n);return J[r]===void 0?null:wr(n,r)}function xr(e){let n=e.split("/");if(n[0]!=="CVSS:4.0")return null;let r={};for(let s of n.slice(1)){let[t,a]=s.split(":");t&&a&&(r[t]=a)}for(let s of gn)if(!(s in r))return null;for(let s of["E","CR","IR","AR"])s in r||(r[s]="X");return r}function b(e,n){let r=e[n];if(n==="E"&&r==="X")return"A";if((n==="CR"||n==="IR"||n==="AR")&&r==="X")return"H";let s=e[`M${n}`];return s!==void 0&&s!=="X"?s:r??""}function kr(e){let n=b(e,"AV"),r=b(e,"PR"),s=b(e,"UI"),t="2";n==="N"&&r==="N"&&s==="N"?t="0":(n==="N"||r==="N"||s==="N")&&n!=="P"&&(t="1");let a=b(e,"AC")==="L"&&b(e,"AT")==="N"?"0":"1",i=b(e,"VC"),o=b(e,"VI"),d=b(e,"VA"),c="2";i==="H"&&o==="H"?c="0":(i==="H"||o==="H"||d==="H")&&(c="1");let l=b(e,"MSI"),u=b(e,"MSA"),f="2";l==="S"||u==="S"?f="0":(b(e,"SC")==="H"||b(e,"SI")==="H"||b(e,"SA")==="H")&&(f="1");let p=b(e,"E"),m=p==="A"?"0":p==="P"?"1":"2",v=b(e,"CR"),g=b(e,"IR"),x=b(e,"AR");return`${t}${a}${c}${f}${m}${v==="H"&&i==="H"||g==="H"&&o==="H"||x==="H"&&d==="H"?"0":"1"}`}function y(e,n){return e[n]??0}function X(e,n){return ye[`eq${n}`][e[n-1]]??[]}function V(e,n){let r=n.indexOf(e)+e.length+1,s=n.slice(r),t=s.indexOf("/");return t>0?s.substring(0,t):s}function wr(e,n){if(gn.slice(5).every(h=>b(e,h)==="N"))return 0;let r=J[n],s=Number(n[0]),t=Number(n[2]),a=Number(n[5]),i=h=>J[h.join("")]??NaN,o=n.split("").map(Number),d=i([o[0]+1,o[1],o[2],o[3],o[4],o[5]]),c=i([o[0],o[1]+1,o[2],o[3],o[4],o[5]]),l;if(t===1&&a===1)l=i([o[0],o[1],o[2]+1,o[3],o[4],o[5]]);else if(t===0&&a===1)l=i([o[0],o[1],o[2]+1,o[3],o[4],o[5]]);else if(t===1&&a===0)l=i([o[0],o[1],o[2],o[3],o[4],o[5]+1]);else if(t===0&&a===0){let h=i([o[0],o[1],o[2],o[3],o[4],o[5]+1]),q=i([o[0],o[1],o[2]+1,o[3],o[4],o[5]]);l=h>q?h:q}else l=i([o[0],o[1],o[2]+1,o[3],o[4],o[5]+1]);let u=i([o[0],o[1],o[2],o[3]+1,o[4],o[5]]),f=i([o[0],o[1],o[2],o[3],o[4]+1,o[5]]),p=X(n,1),m=X(n,2),v=ye.eq3[n[2]][n[5]],g=X(n,4),x=X(n,5),w=[];for(let h of p)for(let q of m)for(let de of v)for(let Kn of g)for(let Un of x)w.push(h+q+de+Kn+Un);let R=0,O=0,P=0,M=0,N=0,D=0,ne=0,re=0,te=0,se=0,oe=0,ie=0,ae=0,ce=0;for(let h of w)if(R=y(Qe,b(e,"AV"))-y(Qe,V("AV",h)),O=y(en,b(e,"PR"))-y(en,V("PR",h)),P=y(nn,b(e,"UI"))-y(nn,V("UI",h)),M=y(rn,b(e,"AC"))-y(rn,V("AC",h)),N=y(tn,b(e,"AT"))-y(tn,V("AT",h)),D=y(sn,b(e,"VC"))-y(sn,V("VC",h)),ne=y(on,b(e,"VI"))-y(on,V("VI",h)),re=y(an,b(e,"VA"))-y(an,V("VA",h)),te=y(cn,b(e,"SC"))-y(cn,V("SC",h)),se=y(dn,b(e,"SI"))-y(dn,V("SI",h)),oe=y(ln,b(e,"SA"))-y(ln,V("SA",h)),ie=y(pn,b(e,"CR"))-y(pn,V("CR",h)),ae=y(fn,b(e,"IR"))-y(fn,V("IR",h)),ce=y(un,b(e,"AR"))-y(un,V("AR",h)),![R,O,P,M,N,D,ne,re,te,se,oe,ie,ae,ce].some(de=>de<0))break;let Dn=R+O+P,En=M+N,In=D+ne+re+ie+ae+ce,Hn=te+se+oe,B=.1,Me=r-d,Ne=r-c,De=r-l,Ee=r-u,Ln=r-f,z=Ze,jn=z.eq1[String(s)]*B,_n=z.eq2[n[1]]*B,qn=z.eq3eq6[String(t)][String(a)]*B,Fn=z.eq4[n[3]]*B,A=0,Ie=0,He=0,Le=0,je=0;Number.isNaN(Me)||(A++,Ie=Me*(Dn/jn)),Number.isNaN(Ne)||(A++,He=Ne*(En/_n)),Number.isNaN(De)||(A++,Le=De*(In/qn)),Number.isNaN(Ee)||(A++,je=Ee*(Hn/Fn)),Number.isNaN(Ln)||A++;let Tn=A===0?0:(Ie+He+Le+je)/A;return r-=Tn,r<0&&(r=0),r>10&&(r=10),Math.round(r*10)/10}var Rr={N:.85,A:.62,L:.55,P:.2},Sr={L:.77,H:.44},Vr={N:.85,R:.62},Or={N:.85,L:.62,H:.27},Pr={N:.85,L:.68,H:.5},he={H:.56,L:.22,N:0};function xe(e){return e<=0?"none":e<4?"low":e<7?"medium":e<9?"high":"critical"}function bn(e){switch(e.trim().toUpperCase()){case"CRITICAL":return"critical";case"HIGH":return"high";case"MODERATE":case"MEDIUM":return"medium";case"LOW":return"low";case"NONE":return"none";default:return"unknown"}}function vn(e){let n=Math.round(e*1e5);return n%1e4===0?n/1e5:(Math.floor(n/1e4)+1)/10}function yn(e){let n=e.split("/"),r=n[0]??"";if(r==="CVSS:4.0"){let g=mn(e);return g===null?null:{score:g,level:xe(g),version:r}}if(!/^CVSS:3\.[01]$/.test(r))return null;let s={};for(let g of n.slice(1)){let[x,w]=g.split(":");x&&w&&(s[x]=w)}let t=Rr[s.AV??""],a=Sr[s.AC??""],i=Vr[s.UI??""],o=s.S==="C",d=(o?Pr:Or)[s.PR??""],c=he[s.C??""],l=he[s.I??""],u=he[s.A??""];if([t,a,i,d,c,l,u].some(g=>g===void 0))return null;let f=1-(1-c)*(1-l)*(1-u),p=o?7.52*(f-.029)-3.25*Math.pow(f-.02,15):6.42*f,m=8.22*t*a*d*i,v;return p<=0?v=0:o?v=vn(Math.min(1.08*(p+m),10)):v=vn(Math.min(p+m,10)),{score:v,level:xe(v),version:r}}function ke(e){let n=[];for(let t of e.severity??[])t.score&&n.push(t.score);for(let t of e.affected??[])for(let a of t.severity??[])a.score&&n.push(a.score);let r=null;for(let t of n){let a=yn(t);a&&(!r||a.score>r.score)&&(r=a)}if(r)return{level:r.level,score:r.score,vector:Ar(r,n),cvssVersion:r.version,source:"cvss"};let s=$r(e);return s?{level:bn(s),source:"database"}:{level:"unknown",source:"unknown"}}function Ar(e,n){return n.find(r=>r.startsWith(e.version))}function $r(e){let n=s=>{let t=s?.severity;return typeof t=="string"?t:void 0},r=n(e.database_specific);if(r)return r;for(let s of e.affected??[]){let t=n(s.database_specific);if(t)return t}}function we(e,n,r={}){let s=[];for(let t of Object.values(e.nodes)){if(t.unscannable||r.prodOnly&&t.scope==="dev")continue;let a=n(t.name);if(a.length===0)continue;let i=new Map,o=a.filter(c=>{let l=W(t.name,t.version,c);return i.set(c,l),l.affected});if(o.length===0)continue;let d=ge(e,t.key);for(let c of be(o)){if(Cr(c,r.ignore))continue;let{fixedVersions:l}=i.get(c)??W(t.name,t.version,c),u=Mr(l),f=ve(t.version,l);s.push({id:c.id,aliases:c.aliases??[],packageName:t.name,version:t.version,scope:t.scope,direct:t.direct,severity:ke(c),summary:c.summary??c.id,details:c.details,references:(c.references??[]).map(p=>p.url),fixedVersions:u,recommendation:Dr(t.name,t.direct,f,e.manager),dependencyPaths:d?[d]:[],source:"osv"})}}return s}function Cr(e,n){return!n||n.size===0?!1:n.has(e.id)?!0:(e.aliases??[]).some(r=>n.has(r))}function Mr(e){return[...new Set(e)].sort((n,r)=>{let s=hn.parse(n,{loose:!0}),t=hn.parse(r,{loose:!0});return s&&t?s.compare(t):n.localeCompare(r)})}var Nr={npm:"npm install",yarn:"yarn add",pnpm:"pnpm add"};function Dr(e,n,r,s){return r?n?`Upgrade to ${e}@${r} or later (e.g. \`${Nr[s]} ${e}@${r}\`).`:`Update the dependency that pulls in ${e} so it resolves to ${e}@${r} or later. If it cannot, force the resolution via an override.`:n?"No fixed version is available yet. Watch the advisory, or evaluate an alternative package.":"No fixed version is available yet. Pressure the parent dependency to update, or override the version."}var Re=class{byName=new Map;ids=new Set;add(n){if(n.withdrawn||this.ids.has(n.id))return;this.ids.add(n.id);let r=new Set;for(let s of n.affected??[]){let t=s.package?.ecosystem;t&&t!=="npm"||s.package?.name&&r.add(s.package.name)}for(let s of r){let t=this.byName.get(s);t?t.push(n):this.byName.set(s,[n])}}addAll(n){for(let r of n)this.add(r)}vulnerabilitiesFor(n){return this.byName.get(n)??[]}get size(){return this.ids.size}};import{resolve as ct}from"path";import{join as Se}from"path";import Er from"env-paths";function Ve(e){return e||(process.env.LOCKHAWK_CACHE?process.env.LOCKHAWK_CACHE:Er("lockhawk",{suffix:""}).cache)}function T(e){return Se(e,"osv-db","npm")}var xn=4096;function Z(e){let n=2166136261;for(let r=0;r<e.length;r++)n^=e.charCodeAt(r),n=Math.imul(n,16777619);return(n>>>0)%xn}function K(e){return Se(T(e),"meta.json")}function Oe(e,n){return Se(e,"query-cache",`${encodeURIComponent(n)}.json`)}import{mkdir as Ir,readFile as Hr,writeFile as Lr}from"fs/promises";import{dirname as jr}from"path";import _r from"p-limit";import qr,{AbortError as Fr}from"p-retry";var Pe="https://api.osv.dev/v1",kn=1e3,U=class{constructor(n){this.opts=n;this.limit=_r(n.concurrency??16)}opts;limit;async fetchAdvisories(n){let r=[...new Set(n.map(o=>o.name))],s=await this.queryBatch(r),t=new Set;for(let o of s.values())for(let d of o)t.add(d);let a=await this.hydrate([...t]),i=new Map;for(let[o,d]of s){let c=[];for(let l of d){let u=a.get(l);u&&c.push(u)}c.length&&i.set(o,c)}return i}async queryBatch(n){let r=new Map;for(let s=0;s<n.length;s+=kn){let t=n.slice(s,s+kn),a={queries:t.map(o=>({package:{name:o,ecosystem:"npm"}}))},i=await this.request(`${Pe}/querybatch`,a);for(let o=0;o<t.length;o++){let d=t[o],c=i.results[o];if(!c)continue;let l=r.get(d)??new Set;for(let f of c.vulns??[])l.add(f.id);let u=c.next_page_token;for(;u;){let f=await this.request(`${Pe}/query`,{package:{name:d,ecosystem:"npm"},page_token:u});for(let p of f.vulns??[])l.add(p.id);u=f.next_page_token}l.size&&r.set(d,l)}}return r}async hydrate(n){let r=new Map;return await Promise.all(n.map(s=>this.limit(async()=>{let t=await this.getVulnerability(s);t&&r.set(s,t)}))),r}async getVulnerability(n){let r=await this.readCache(n);if(r)return r;let s=await this.request(`${Pe}/vulns/${encodeURIComponent(n)}`);return await this.writeCache(n,s),s}async request(n,r){let s=this.opts.retries??3;return qr(async()=>{let t=await fetch(n,{method:r?"POST":"GET",headers:r?{"content-type":"application/json"}:void 0,body:r?JSON.stringify(r):void 0,signal:AbortSignal.timeout(this.opts.timeoutMs??15e3)});if(t.ok)return await t.json();throw t.status===429||t.status>=500?new Error(`OSV ${t.status} for ${n}`):new Fr(`OSV ${t.status} for ${n}`)},{retries:s})}async readCache(n){if(!this.opts.noCache)try{let r=JSON.parse(await Hr(Oe(this.opts.cacheDir,n),"utf8")),s=(this.opts.cacheTtlHours??24)*36e5;return Date.now()-r.fetchedAt>s?void 0:r.vuln}catch{return}}async writeCache(n,r){if(!this.opts.noCache)try{let s=Oe(this.opts.cacheDir,n);await Ir(jr(s),{recursive:!0}),await Lr(s,JSON.stringify({fetchedAt:Date.now(),vuln:r}))}catch{}}};import{createWriteStream as Tr}from"fs";import{mkdir as wn,mkdtemp as Kr,readFile as Rn,rm as Sn,writeFile as Ae}from"fs/promises";import{tmpdir as Ur}from"os";import{join as Q}from"path";import{Readable as Gr}from"stream";import{pipeline as Br}from"stream/promises";import{gunzipSync as zr,gzipSync as Yr}from"zlib";import Wr from"p-limit";import Jr from"yauzl";var Xr="https://storage.googleapis.com/osv-vulnerabilities/npm/all.zip",Zr=32,Qr=8*1024*1024,H=class extends Error{constructor(){super("No offline OSV database found. Run `lockhawk db update` first."),this.name="OfflineDbMissingError"}},et=/^[A-Za-z]{3}, \d{2} [A-Za-z]{3} \d{4} \d{2}:\d{2}:\d{2} GMT$/,nt=/^(?:W\/)?"[\x21\x23-\x7e]*"$/;function rt(e){return e&&et.test(e)?e:void 0}function tt(e){return e&&nt.test(e)?e:void 0}async function L(e){try{return JSON.parse(await Rn(K(e),"utf8"))}catch{return}}async function st(e){let{cacheDir:n,nowIso:r,force:s,onProgress:t}=e,a=T(n);await wn(a,{recursive:!0});let i=s?void 0:await L(n),o={},d=tt(i?.etag),c=rt(i?.lastModified);d&&(o["If-None-Match"]=d),c&&(o["If-Modified-Since"]=c),t?.("Fetching npm advisory database from OSV.dev\u2026");let l=await fetch(Xr,{headers:o});if(l.status===304&&i){let g={...i,lastUpdated:r};return await Ae(K(n),JSON.stringify(g,null,2)),{status:"unchanged",meta:g}}if(!l.ok||!l.body)throw new Error(`OSV database download failed: HTTP ${l.status}`);let u=await Kr(Q(Ur(),"lockhawk-osv-")),f=Q(u,"all.zip"),p;try{await Br(Gr.fromWeb(l.body),Tr(f)),t?.("Extracting advisories\u2026"),p=await at(f)}finally{await Sn(u,{recursive:!0,force:!0})}t?.("Building package index\u2026");let m=ot(p);await it(n,m);let v={ecosystem:"npm",source:"osv.dev",lastUpdated:r,recordCount:p.length,packageCount:m.size,etag:l.headers.get("etag")??void 0,lastModified:l.headers.get("last-modified")??void 0};return await Ae(K(n),JSON.stringify(v,null,2)),{status:"updated",meta:v}}function Vn(e){return Q(T(e),"by-name")}function On(e,n){return Q(Vn(e),`${n}.json.gz`)}async function $e(e,n){if(!await L(e))throw new H;let r=new Map;for(let a of new Set(n)){let i=Z(a),o=r.get(i);o?o.push(a):r.set(i,[a])}let s=new Map,t=Wr(Zr);return await Promise.all([...r.entries()].map(([a,i])=>t(async()=>{try{let o=JSON.parse(zr(await Rn(On(e,a))).toString("utf8"));for(let d of i){let c=o[d];c?.length&&s.set(d,c)}}catch{}}))),s}function ot(e){let n=new Map;for(let r of e){if(r.withdrawn)continue;let s=new Set;for(let t of r.affected??[]){let a=t.package?.ecosystem;a&&a!=="npm"||t.package?.name&&s.add(t.package.name)}for(let t of s){let a=n.get(t);a?a.push(r):n.set(t,[r])}}return n}async function it(e,n){let r=Vn(e);await Sn(r,{recursive:!0,force:!0}),await wn(r,{recursive:!0});let s=new Map;for(let[i,o]of n){let d=Z(i),c=s.get(d)??{};c[i]=o,s.set(d,c)}let t=[...s.entries()],a=256;for(let i=0;i<t.length;i+=a)await Promise.all(t.slice(i,i+a).map(([o,d])=>Ae(On(e,o),Yr(Buffer.from(JSON.stringify(d))))))}function at(e){return new Promise((n,r)=>{let s=[];Jr.open(e,{lazyEntries:!0},(t,a)=>{if(t||!a)return r(t??new Error("Failed to open OSV zip"));a.on("entry",i=>{if(!i.fileName.endsWith(".json")||i.uncompressedSize>Qr)return a.readEntry();a.openReadStream(i,(o,d)=>{if(o||!d)return a.readEntry();let c=[];d.on("data",l=>c.push(l)),d.on("error",()=>a.readEntry()),d.on("end",()=>{try{s.push(JSON.parse(Buffer.concat(c).toString("utf8")))}catch{}a.readEntry()})})}),a.on("end",()=>n(s)),a.on("error",r),a.readEntry()})})}var Pn=36e5,j=class{constructor(n){this.opts=n}opts;async prepare(n){let r=await L(this.opts.cacheDir);if(!r)throw new H;let s=await $e(this.opts.cacheDir,n.map(o=>o.name)),t=(Date.now()-Date.parse(r.lastUpdated))/Pn,a=t>(this.opts.staleAfterHours??24),i=a?[`Offline database is ${Math.round(t)}h old. Run \`lockhawk db update\` for current results.`]:[];return{candidatesFor:o=>s.get(o)??[],database:{source:"offline",recordCount:r.recordCount,lastUpdated:r.lastUpdated,ageHours:Math.round(t*10)/10,stale:a,warnings:i}}}},G=class{constructor(n){this.opts=n}opts;async prepare(n){let r=new U({cacheDir:this.opts.cacheDir,concurrency:this.opts.concurrency,noCache:this.opts.noCache,cacheTtlHours:this.opts.cacheTtlHours});try{let s=await r.fetchAdvisories(n);return{candidatesFor:t=>s.get(t)??[],database:{source:"online",stale:!1,warnings:[]}}}catch(s){if(this.opts.strictNetwork)throw s;let t=s instanceof Error?s.message:String(s);return{candidatesFor:()=>[],database:{source:"online",stale:!0,warnings:[`OSV.dev was unreachable (${t}). Scan completed without online data \u2014 results may be incomplete.`]}}}}},ee=class{constructor(n){this.opts=n}opts;async prepare(n){let r=await L(this.opts.cacheDir),s=this.opts.staleAfterHours??24;if(r&&(Date.now()-Date.parse(r.lastUpdated))/Pn<=s)return new j(this.opts).prepare(n);try{return await new G({...this.opts,strictNetwork:!0}).prepare(n)}catch(t){if(r){let i=await new j(this.opts).prepare(n);return i.database.warnings.push("Used the stale offline database because OSV.dev was unreachable."),i}if(this.opts.strictNetwork)throw t;let a=t instanceof Error?t.message:String(t);return{candidatesFor:()=>[],database:{source:"online",stale:!0,warnings:[`No offline database and OSV.dev was unreachable (${a}). Run \`lockhawk db update\`.`]}}}}};function Ce(e,n){switch(e){case"offline":return new j(n);case"online":return new G(n);default:return new ee(n)}}var An={name:"lockhawk",version:"0.1.0"};async function dt(e={},n){let r=Date.now(),s=ct(e.path??"."),t=fe(s),a=F(s),i=$n(t,e.prodOnly),o=Ve(e.cacheDir),d=n??Ce(e.mode??"auto",{cacheDir:o,concurrency:e.concurrency,noCache:e.noCache,cacheTtlHours:e.cacheTtlHours,strictNetwork:e.strictNetwork});e.onProgress?.("Checking dependencies against OSV.dev\u2026");let c=await d.prepare(i),l=we(t,c.candidatesFor,{prodOnly:e.prodOnly,ignore:e.ignore&&e.ignore.length?new Set(e.ignore):void 0});e.severityThreshold&&e.severityThreshold!=="none"&&(l=l.filter(f=>_e(f.severity.level,e.severityThreshold))),l.sort(ft);let u=lt(t);return{schemaVersion:1,tool:{...An},scannedAt:new Date(r).toISOString(),target:{path:s,manager:t.manager,lockfile:a?.filename??"",root:t.root},database:c.database,summary:pt(l),stats:{totalPackages:Object.keys(t.nodes).length,uniquePackages:i.length,directDependencies:t.directKeys.length,unscannable:u.length,durationMs:Date.now()-r},findings:l,unscannable:u}}function $n(e,n){let r=new Set,s=[];for(let t of Object.values(e.nodes))t.unscannable||n&&t.scope==="dev"||r.has(t.key)||(r.add(t.key),s.push({name:t.name,version:t.version}));return s}function lt(e){let n=[];for(let r of Object.values(e.nodes))r.unscannable&&n.push({name:r.name,version:r.version,reason:r.unscannable.reason});return n}function pt(e){let n={total:e.length,critical:0,high:0,medium:0,low:0,unknown:0,none:0,vulnerablePackages:0,fixable:0},r=new Set;for(let s of e)n[s.severity.level]+=1,r.add(`${s.packageName}@${s.version}`),s.fixedVersions.length>0&&(n.fixable+=1);return n.vulnerablePackages=r.size,n}function ft(e,n){let r=$[n.severity.level]-$[e.severity.level];return r!==0?r:e.packageName.localeCompare(n.packageName)}function ut(e,n){return["critical","high","medium","low","unknown"].some(s=>$[s]>=$[n]&&e[s]>0)}function gt(e){return JSON.stringify(e,null,2)}var mt="https://github.com/lockhawk/lockhawk";function vt(e){return e.find(n=>{try{let r=new URL(n);return r.protocol==="http:"||r.protocol==="https:"}catch{return!1}})}function bt(e){if(typeof e.severity.score=="number")return e.severity.score;switch(e.severity.level){case"critical":return 9.5;case"high":return 7.5;case"medium":return 5;case"low":return 2;default:return 0}}function yt(e){return e==="critical"||e==="high"?"error":e==="medium"?"warning":"note"}function Cn(e){let n=e.target.lockfile||"package-lock.json",r=new Map,s=[];for(let t of e.findings){r.has(t.id)||r.set(t.id,{id:t.id,name:`${t.packageName} ${t.id}`,shortDescription:{text:t.summary},fullDescription:t.details?{text:t.details}:void 0,helpUri:vt(t.references),properties:{"security-severity":String(bt(t)),tags:["security","dependency",t.severity.level]}});let a=t.dependencyPaths[0]?.join(" \u203A ")??t.packageName,i=t.fixedVersions[0]?` Fixed in ${t.fixedVersions[0]}.`:"";s.push({ruleId:t.id,level:yt(t.severity.level),message:{text:`${t.severity.level.toUpperCase()}: ${t.packageName}@${t.version} \u2014 ${t.summary}.${i} Path: ${a}.`},locations:[{physicalLocation:{artifactLocation:{uri:n},region:{startLine:1}}}],partialFingerprints:{lockhawkFinding:`${t.packageName}@${t.version}@${t.id}`},properties:{package:t.packageName,version:t.version,scope:t.scope,fixedVersions:t.fixedVersions}})}return{$schema:"https://json.schemastore.org/sarif-2.1.0.json",version:"2.1.0",runs:[{tool:{driver:{name:e.tool.name,version:e.tool.version,informationUri:mt,rules:[...r.values()]}},results:s,properties:{scannedAt:e.scannedAt,databaseSource:e.database.source,databaseAgeHours:e.database.ageHours,databaseStale:e.database.stale}}]}}function ht(e){return JSON.stringify(Cn(e),null,2)}function xt(e){return St(e)}var Mn={critical:"#ff5d67",high:"#ff9f43",medium:"#ffd23f",low:"#5b9dff",unknown:"#8b95a5",none:"#34d399"};function k(e){return e.replace(/[&<>"]/g,n=>({"&":"&","<":"<",">":">",'"':"""})[n])}function kt(e){try{let n=new URL(e);return n.protocol==="http:"||n.protocol==="https:"?e:void 0}catch{return}}var wt=`<svg class="mark" viewBox="0 0 48 48" fill="none" aria-hidden="true">
|
|
2172
2
|
<defs>
|
|
2173
3
|
<linearGradient id="lh-g" x1="6" y1="4" x2="40" y2="44" gradientUnits="userSpaceOnUse">
|
|
2174
4
|
<stop stop-color="#ffc266"/><stop offset="1" stop-color="#ff5d67"/>
|
|
@@ -2178,79 +8,44 @@ var MARK = `<svg class="mark" viewBox="0 0 48 48" fill="none" aria-hidden="true"
|
|
|
2178
8
|
<path d="M14 18.5 24 15l10 3.5" stroke="url(#lh-g)" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
2179
9
|
<circle cx="24" cy="23" r="3.2" fill="url(#lh-g)"/>
|
|
2180
10
|
<path d="M24 26.2V32" stroke="url(#lh-g)" stroke-width="2.2" stroke-linecap="round"/>
|
|
2181
|
-
</svg
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
<div class="
|
|
2193
|
-
<
|
|
2194
|
-
|
|
2195
|
-
}
|
|
2196
|
-
function renderHtml(result) {
|
|
2197
|
-
const { summary } = result;
|
|
2198
|
-
const cards = ["critical", "high", "medium", "low", "unknown"].map(
|
|
2199
|
-
(sev, i) => `
|
|
2200
|
-
<div class="card${summary[sev] === 0 ? " zero" : ""}" style="--c:${SEVERITY_COLORS[sev]};--i:${i}">
|
|
2201
|
-
<div class="count">${summary[sev]}</div><div class="label">${sev}</div>
|
|
2202
|
-
</div>`
|
|
2203
|
-
).join("");
|
|
2204
|
-
const rows = result.findings.map((f, i) => {
|
|
2205
|
-
const path = f.dependencyPaths[0]?.join(" \u203A ") ?? f.packageName;
|
|
2206
|
-
const refs = f.references.slice(0, 3).map((u) => httpHref(u)).filter((u) => u !== void 0).map((u) => `<a class="ref" href="${esc(u)}" target="_blank" rel="noopener">link</a>`).join("");
|
|
2207
|
-
const fixed = f.fixedVersions[0] ? `<span class="fixchip" title="Click to select, then \u2318/Ctrl+C to copy">${esc(
|
|
2208
|
-
f.fixedVersions[0]
|
|
2209
|
-
)}</span>` : '<span class="nofix">\u2014</span>';
|
|
2210
|
-
return `
|
|
2211
|
-
<tr class="main" style="--i:${i}">
|
|
2212
|
-
<td><span class="pill" style="--c:${SEVERITY_COLORS[f.severity.level]}">${esc(
|
|
2213
|
-
f.severity.level
|
|
2214
|
-
)}${f.severity.score ? ` ${f.severity.score}` : ""}</span></td>
|
|
2215
|
-
<td><span class="pkg">${esc(f.packageName)}@${esc(f.version)}</span><div class="scope">${esc(
|
|
2216
|
-
f.scope
|
|
2217
|
-
)}${f.direct ? " \xB7 direct" : ""}</div></td>
|
|
2218
|
-
<td><a class="advid" href="https://osv.dev/${encodeURIComponent(
|
|
2219
|
-
f.id
|
|
2220
|
-
)}" target="_blank" rel="noopener">${esc(f.id)}</a></td>
|
|
2221
|
-
<td><div class="summary">${esc(f.summary)}</div><div class="path">${esc(path)}</div></td>
|
|
2222
|
-
<td>${fixed}</td>
|
|
2223
|
-
<td class="refs">${refs}</td>
|
|
11
|
+
</svg>`,Nn='<svg class="chev" width="11" height="11" viewBox="0 0 16 16" fill="none" aria-hidden="true"><path d="M6 4l4 4-4 4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>';function Rt(e){let r=e.match(/`([^`]+)`/)?.[1],s=/^No fixed version/i.test(e),t=r?e.replace(/\s*\(e\.g\.[^)]*\)/,""):e,a=r?`<div class="cmdbar"><code class="cmd" title="Click to select, then \u2318/Ctrl+C to copy">${k(r)}</code><span class="hint">click to select</span></div>`:"";return`<div class="fix${s?" fix-pending":""}">
|
|
12
|
+
<div class="fix-label">${Nn.replace("chev","spark")}${s?"No fix available yet":"Recommended fix"}</div>
|
|
13
|
+
<p class="fix-text">${k(t)}</p>${a}
|
|
14
|
+
</div>`}function St(e){let{summary:n}=e,r=["critical","high","medium","low","unknown"].map((i,o)=>`
|
|
15
|
+
<div class="card${n[i]===0?" zero":""}" style="--c:${Mn[i]};--i:${o}">
|
|
16
|
+
<div class="count">${n[i]}</div><div class="label">${i}</div>
|
|
17
|
+
</div>`).join(""),s=e.findings.map((i,o)=>{let d=i.dependencyPaths[0]?.join(" \u203A ")??i.packageName,c=i.references.slice(0,3).map(u=>kt(u)).filter(u=>u!==void 0).map(u=>`<a class="ref" href="${k(u)}" target="_blank" rel="noopener">link</a>`).join(""),l=i.fixedVersions[0]?`<span class="fixchip" title="Click to select, then \u2318/Ctrl+C to copy">${k(i.fixedVersions[0])}</span>`:'<span class="nofix">\u2014</span>';return`
|
|
18
|
+
<tr class="main" style="--i:${o}">
|
|
19
|
+
<td><span class="pill" style="--c:${Mn[i.severity.level]}">${k(i.severity.level)}${i.severity.score?` ${i.severity.score}`:""}</span></td>
|
|
20
|
+
<td><span class="pkg">${k(i.packageName)}@${k(i.version)}</span><div class="scope">${k(i.scope)}${i.direct?" \xB7 direct":""}</div></td>
|
|
21
|
+
<td><a class="advid" href="https://osv.dev/${encodeURIComponent(i.id)}" target="_blank" rel="noopener">${k(i.id)}</a></td>
|
|
22
|
+
<td><div class="summary">${k(i.summary)}</div><div class="path">${k(d)}</div></td>
|
|
23
|
+
<td>${l}</td>
|
|
24
|
+
<td class="refs">${c}</td>
|
|
2224
25
|
</tr>
|
|
2225
26
|
<tr class="detail-row">
|
|
2226
27
|
<td colspan="6">
|
|
2227
|
-
${
|
|
28
|
+
${i.recommendation?Rt(i.recommendation):""}
|
|
2228
29
|
<details>
|
|
2229
|
-
<summary>${
|
|
30
|
+
<summary>${Nn}<span>Advisory details</span></summary>
|
|
2230
31
|
<div class="detail-body">
|
|
2231
32
|
<div class="advisory-wrap">
|
|
2232
|
-
<div class="advisory-head"><span class="dots"><i></i><i></i><i></i></span><span class="advisory-title">advisory \xB7 ${
|
|
2233
|
-
|
|
2234
|
-
)}</span></div>
|
|
2235
|
-
<pre class="advisory">${esc(f.details ?? f.summary)}</pre>
|
|
33
|
+
<div class="advisory-head"><span class="dots"><i></i><i></i><i></i></span><span class="advisory-title">advisory \xB7 ${k(i.id)}</span></div>
|
|
34
|
+
<pre class="advisory">${k(i.details??i.summary)}</pre>
|
|
2236
35
|
</div>
|
|
2237
36
|
</div>
|
|
2238
37
|
</details>
|
|
2239
38
|
</td>
|
|
2240
|
-
</tr
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
<span><b>${summary.vulnerablePackages}</b> packages</span><span class="sep">\xB7</span>
|
|
2246
|
-
<span><b>${summary.fixable}</b> fixable</span>
|
|
2247
|
-
</div>`;
|
|
2248
|
-
return `<!doctype html>
|
|
39
|
+
</tr>`}).join(""),t=e.database.warnings.map(i=>`<div class="warn"><span class="warn-i">\u26A0</span>${k(i)}</div>`).join(""),a=e.findings.length===0?"":`<div class="results-meta">
|
|
40
|
+
<span><b>${n.total}</b> findings</span><span class="sep">\xB7</span>
|
|
41
|
+
<span><b>${n.vulnerablePackages}</b> packages</span><span class="sep">\xB7</span>
|
|
42
|
+
<span><b>${n.fixable}</b> fixable</span>
|
|
43
|
+
</div>`;return`<!doctype html>
|
|
2249
44
|
<html lang="en">
|
|
2250
45
|
<head>
|
|
2251
46
|
<meta charset="utf-8">
|
|
2252
47
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
2253
|
-
<title>lockhawk report \u2014 ${
|
|
48
|
+
<title>lockhawk report \u2014 ${k(e.target.root.name)}</title>
|
|
2254
49
|
<style>
|
|
2255
50
|
:root {
|
|
2256
51
|
--bg:#070a12; --panel:#11151e; --panel-2:#141926; --elev:#171d2b;
|
|
@@ -2391,116 +186,24 @@ function renderHtml(result) {
|
|
|
2391
186
|
</head>
|
|
2392
187
|
<body>
|
|
2393
188
|
<header>
|
|
2394
|
-
<div class="brand">${
|
|
2395
|
-
<div class="meta"><b>${
|
|
2396
|
-
result.scannedAt
|
|
2397
|
-
)}</div>
|
|
189
|
+
<div class="brand">${wt}<div class="wordmark"><span class="lock">lock</span><span class="hawk">hawk</span><span class="caret"></span></div></div>
|
|
190
|
+
<div class="meta"><b>${k(e.target.root.name)}${e.target.root.version?`@${k(e.target.root.version)}`:""}</b> \xB7 ${k(e.target.manager)} \xB7 ${e.stats.totalPackages} packages \xB7 scanned ${k(e.scannedAt)}</div>
|
|
2398
191
|
</header>
|
|
2399
192
|
<main>
|
|
2400
|
-
<div class="cards">${
|
|
2401
|
-
${
|
|
2402
|
-
${
|
|
193
|
+
<div class="cards">${r}</div>
|
|
194
|
+
${t}
|
|
195
|
+
${e.findings.length===0?'<div class="empty">\u2713 No known vulnerabilities found.</div>':`${a}
|
|
2403
196
|
<div class="table-wrap"><div class="scroll"><table>
|
|
2404
197
|
<thead><tr><th>Severity</th><th>Package</th><th>Advisory</th><th>Summary & path</th><th>Fixed in</th><th>Refs</th></tr></thead>
|
|
2405
|
-
<tbody>${
|
|
198
|
+
<tbody>${s}</tbody>
|
|
2406
199
|
</table></div></div>`}
|
|
2407
200
|
</main>
|
|
2408
|
-
<footer>Generated by lockhawk v${
|
|
2409
|
-
result.database.source
|
|
2410
|
-
)}</footer>
|
|
201
|
+
<footer>Generated by lockhawk v${k(e.tool.version)} \xB7 data from OSV.dev \xB7 database: ${k(e.database.source)}</footer>
|
|
2411
202
|
</body>
|
|
2412
|
-
</html
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
/[&<>"']/g,
|
|
2419
|
-
(c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]
|
|
2420
|
-
);
|
|
2421
|
-
}
|
|
2422
|
-
function failureBody(f) {
|
|
2423
|
-
const lines = [
|
|
2424
|
-
`Package: ${f.packageName}@${f.version} (${f.scope}${f.direct ? ", direct" : ", transitive"})`,
|
|
2425
|
-
`Severity: ${f.severity.level}${f.severity.score !== void 0 ? `, CVSS ${f.severity.score}` : ""}${f.severity.vector ? ` (${f.severity.vector})` : ""}`,
|
|
2426
|
-
f.fixedVersions.length ? `Fixed in: ${f.fixedVersions.join(", ")}` : "Fix: none available yet",
|
|
2427
|
-
f.dependencyPaths[0] ? `Dependency path: ${f.dependencyPaths[0].join(" > ")}` : "",
|
|
2428
|
-
f.recommendation ? `Recommendation: ${f.recommendation}` : "",
|
|
2429
|
-
f.references.length ? `References:
|
|
2430
|
-
${f.references.join("\n")}` : ""
|
|
2431
|
-
];
|
|
2432
|
-
return lines.filter(Boolean).join("\n");
|
|
2433
|
-
}
|
|
2434
|
-
function toJunit(result) {
|
|
2435
|
-
const { findings } = result;
|
|
2436
|
-
const failures = findings.length;
|
|
2437
|
-
const tests = findings.length === 0 ? 1 : findings.length;
|
|
2438
|
-
const time = ((result.stats.durationMs ?? 0) / 1e3).toFixed(3);
|
|
2439
|
-
const suiteName = `${result.target.root.name} npm dependency vulnerabilities`;
|
|
2440
|
-
const cases = findings.length === 0 ? [' <testcase name="No known vulnerabilities" classname="lockhawk" time="0" />'] : findings.map((f) => {
|
|
2441
|
-
const name = `${f.packageName}@${f.version}: ${f.id} (${f.severity.level})`;
|
|
2442
|
-
const message = `${f.severity.level.toUpperCase()}${f.severity.score !== void 0 ? ` ${f.severity.score}` : ""} ${f.id}: ${f.summary}`;
|
|
2443
|
-
return [
|
|
2444
|
-
` <testcase name="${xml(name)}" classname="lockhawk.${xml(f.severity.level)}" time="0">`,
|
|
2445
|
-
` <failure message="${xml(message)}" type="${xml(f.severity.level)}">${xml(failureBody(f))}</failure>`,
|
|
2446
|
-
` </testcase>`
|
|
2447
|
-
].join("\n");
|
|
2448
|
-
});
|
|
2449
|
-
return [
|
|
2450
|
-
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
2451
|
-
`<testsuites name="lockhawk" tests="${tests}" failures="${failures}" errors="0" time="${time}">`,
|
|
2452
|
-
` <testsuite name="${xml(suiteName)}" tests="${tests}" failures="${failures}" errors="0" time="${time}">`,
|
|
2453
|
-
...cases,
|
|
2454
|
-
" </testsuite>",
|
|
2455
|
-
"</testsuites>",
|
|
2456
|
-
""
|
|
2457
|
-
].join("\n");
|
|
2458
|
-
}
|
|
2459
|
-
export {
|
|
2460
|
-
AutoSource,
|
|
2461
|
-
LockfileError,
|
|
2462
|
-
OfflineDbMissingError,
|
|
2463
|
-
OfflineSource,
|
|
2464
|
-
OnlineSource,
|
|
2465
|
-
OsvClient,
|
|
2466
|
-
OsvDatabase,
|
|
2467
|
-
SEVERITY_RANK,
|
|
2468
|
-
SHARD_BUCKETS,
|
|
2469
|
-
TOOL,
|
|
2470
|
-
buildDependencyGraph,
|
|
2471
|
-
buildFindings,
|
|
2472
|
-
canonicalId,
|
|
2473
|
-
createSource,
|
|
2474
|
-
dedupeVulnerabilities,
|
|
2475
|
-
detectLockfile,
|
|
2476
|
-
isVersionAffected,
|
|
2477
|
-
levelFromLabel,
|
|
2478
|
-
levelFromScore,
|
|
2479
|
-
loadAdvisoriesForPackages,
|
|
2480
|
-
loadDependencyGraph,
|
|
2481
|
-
nearestFix,
|
|
2482
|
-
offlineDbDir,
|
|
2483
|
-
offlineMetaPath,
|
|
2484
|
-
parseLockfile,
|
|
2485
|
-
pkgKey,
|
|
2486
|
-
readOfflineMeta,
|
|
2487
|
-
readRootManifest,
|
|
2488
|
-
resolveCacheDir,
|
|
2489
|
-
resolveSeverity,
|
|
2490
|
-
rootLabel,
|
|
2491
|
-
scan,
|
|
2492
|
-
scoreFromVector,
|
|
2493
|
-
severityAtLeast,
|
|
2494
|
-
shardBucket,
|
|
2495
|
-
shortestPath,
|
|
2496
|
-
shouldFail,
|
|
2497
|
-
toHtml,
|
|
2498
|
-
toJson,
|
|
2499
|
-
toJunit,
|
|
2500
|
-
toSarif,
|
|
2501
|
-
toSarifString,
|
|
2502
|
-
uniqueScannablePackages,
|
|
2503
|
-
updateOfflineDatabase,
|
|
2504
|
-
vulnerabilityAffects
|
|
2505
|
-
};
|
|
203
|
+
</html>`}function _(e){return e.replace(/[&<>"']/g,n=>({"&":"&","<":"<",">":">",'"':""","'":"'"})[n])}function Vt(e){return[`Package: ${e.packageName}@${e.version} (${e.scope}${e.direct?", direct":", transitive"})`,`Severity: ${e.severity.level}${e.severity.score!==void 0?`, CVSS ${e.severity.score}`:""}${e.severity.vector?` (${e.severity.vector})`:""}`,e.fixedVersions.length?`Fixed in: ${e.fixedVersions.join(", ")}`:"Fix: none available yet",e.dependencyPaths[0]?`Dependency path: ${e.dependencyPaths[0].join(" > ")}`:"",e.recommendation?`Recommendation: ${e.recommendation}`:"",e.references.length?`References:
|
|
204
|
+
${e.references.join(`
|
|
205
|
+
`)}`:""].filter(Boolean).join(`
|
|
206
|
+
`)}function Ot(e){let{findings:n}=e,r=n.length,s=n.length===0?1:n.length,t=((e.stats.durationMs??0)/1e3).toFixed(3),a=`${e.target.root.name} npm dependency vulnerabilities`,i=n.length===0?[' <testcase name="No known vulnerabilities" classname="lockhawk" time="0" />']:n.map(o=>{let d=`${o.packageName}@${o.version}: ${o.id} (${o.severity.level})`,c=`${o.severity.level.toUpperCase()}${o.severity.score!==void 0?` ${o.severity.score}`:""} ${o.id}: ${o.summary}`;return[` <testcase name="${_(d)}" classname="lockhawk.${_(o.severity.level)}" time="0">`,` <failure message="${_(c)}" type="${_(o.severity.level)}">${_(Vt(o))}</failure>`," </testcase>"].join(`
|
|
207
|
+
`)});return['<?xml version="1.0" encoding="UTF-8"?>',`<testsuites name="lockhawk" tests="${s}" failures="${r}" errors="0" time="${t}">`,` <testsuite name="${_(a)}" tests="${s}" failures="${r}" errors="0" time="${t}">`,...i," </testsuite>","</testsuites>",""].join(`
|
|
208
|
+
`)}export{ee as AutoSource,E as LockfileError,H as OfflineDbMissingError,j as OfflineSource,G as OnlineSource,U as OsvClient,Re as OsvDatabase,$ as SEVERITY_RANK,xn as SHARD_BUCKETS,An as TOOL,pe as buildDependencyGraph,we as buildFindings,Xe as canonicalId,Ce as createSource,be as dedupeVulnerabilities,F as detectLockfile,We as isVersionAffected,bn as levelFromLabel,xe as levelFromScore,$e as loadAdvisoriesForPackages,fe as loadDependencyGraph,ve as nearestFix,T as offlineDbDir,K as offlineMetaPath,Ye as parseLockfile,S as pkgKey,L as readOfflineMeta,C as readRootManifest,Ve as resolveCacheDir,ke as resolveSeverity,ue as rootLabel,dt as scan,yn as scoreFromVector,_e as severityAtLeast,Z as shardBucket,ge as shortestPath,ut as shouldFail,xt as toHtml,gt as toJson,Ot as toJunit,Cn as toSarif,ht as toSarifString,$n as uniqueScannablePackages,st as updateOfflineDatabase,W as vulnerabilityAffects};
|
|
2506
209
|
//# sourceMappingURL=index.js.map
|