@xagents_in/unmask 0.1.3 → 0.1.4
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 +86 -4
- package/dist/cli/annotations.mjs +145 -0
- package/dist/cli/audit.mjs +223 -0
- package/dist/cli/cache.mjs +488 -0
- package/dist/cli/compare.mjs +261 -0
- package/dist/cli/config.mjs +141 -0
- package/dist/cli/discovery.mjs +372 -0
- package/dist/cli/doctor.mjs +354 -0
- package/dist/cli/exceptions.mjs +170 -0
- package/dist/cli/file.mjs +271 -0
- package/dist/cli/files.mjs +141 -0
- package/dist/cli/history.mjs +232 -0
- package/dist/cli/hosted.mjs +251 -43
- package/dist/cli/info.mjs +125 -0
- package/dist/cli/init.mjs +57 -0
- package/dist/cli/install-guard.mjs +896 -0
- package/dist/cli/lockfile.mjs +330 -0
- package/dist/cli/main.mjs +97 -14
- package/dist/cli/preflight.mjs +388 -0
- package/dist/cli/sarif.mjs +100 -0
- package/dist/cli/shell-integration.mjs +122 -0
- package/dist/cli/terminal.mjs +396 -0
- package/dist/cli/watch.mjs +233 -0
- package/dist/cli/why.mjs +256 -0
- package/dist/core/advisories.mjs +101 -24
- package/dist/core/advisory-cache.mjs +295 -0
- package/dist/core/behavior-signals.mjs +1088 -0
- package/dist/core/dependency-graph.mjs +332 -0
- package/dist/core/evidence-exports.mjs +469 -0
- package/dist/core/guard-config.mjs +463 -0
- package/dist/core/guard-policy.mjs +308 -0
- package/dist/core/package-details.mjs +42 -6
- package/dist/core/report.mjs +7 -0
- package/dist/core/sarif.mjs +556 -0
- package/dist/core/syntax.mjs +296 -15
- package/dist/model/client.mjs +204 -56
- package/dist/model/diff-evidence.mjs +177 -0
- package/dist/model/summary.mjs +183 -0
- package/dist/server/advisory-result.mjs +137 -0
- package/dist/server/app.mjs +60 -2
- package/dist/server/dependency-screening.mjs +386 -0
- package/dist/server/exports.mjs +63 -0
- package/dist/server/guard-decision.mjs +37 -0
- package/dist/server/main.mjs +25 -7
- package/dist/server/queue.mjs +95 -53
- package/dist/server/store.mjs +21 -1
- package/dist/version.json +1 -1
- package/dist/viewer/render.mjs +10 -2
- package/dist/viewer/shell.mjs +1 -1
- package/package.json +1 -1
package/dist/cli/why.mjs
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { LocalInputError } from "../core/pipeline.mjs";
|
|
2
|
+
import {
|
|
3
|
+
isExactVersion,
|
|
4
|
+
isPackageName,
|
|
5
|
+
nameFromKey,
|
|
6
|
+
readLockfile,
|
|
7
|
+
resolveEdge,
|
|
8
|
+
} from "./lockfile.mjs";
|
|
9
|
+
|
|
10
|
+
export const WHY_LIMITS = Object.freeze({
|
|
11
|
+
defaultPaths: 10,
|
|
12
|
+
maxPaths: 100,
|
|
13
|
+
maxDepth: 64,
|
|
14
|
+
maxSteps: 50000,
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
export function parseQuery(value) {
|
|
18
|
+
const separator = typeof value === "string" ? value.lastIndexOf("@") : -1;
|
|
19
|
+
const name = separator > 0 ? value.slice(0, separator) : value;
|
|
20
|
+
const version = separator > 0 ? value.slice(separator + 1) : null;
|
|
21
|
+
if (!isPackageName(name) || (version !== null && !isExactVersion(version)))
|
|
22
|
+
throw new LocalInputError(
|
|
23
|
+
"invalid-package",
|
|
24
|
+
"Use why name or why name@exact-version",
|
|
25
|
+
);
|
|
26
|
+
return { name, version };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function parseWhyArgs(argv) {
|
|
30
|
+
const options = {
|
|
31
|
+
lockfile: "package-lock.json",
|
|
32
|
+
maxPaths: WHY_LIMITS.defaultPaths,
|
|
33
|
+
json: false,
|
|
34
|
+
};
|
|
35
|
+
if (!argv[0] || argv[0].startsWith("--"))
|
|
36
|
+
throw new LocalInputError(
|
|
37
|
+
"usage",
|
|
38
|
+
"Use why name[@version] [--lockfile PATH] [--max-paths N] [--json]",
|
|
39
|
+
);
|
|
40
|
+
options.query = parseQuery(argv[0]);
|
|
41
|
+
const seen = new Set();
|
|
42
|
+
for (let index = 1; index < argv.length; index++) {
|
|
43
|
+
const key = argv[index];
|
|
44
|
+
if (seen.has(key))
|
|
45
|
+
throw new LocalInputError(
|
|
46
|
+
"duplicate-option",
|
|
47
|
+
"Each option may be supplied only once",
|
|
48
|
+
);
|
|
49
|
+
seen.add(key);
|
|
50
|
+
if (key === "--json") {
|
|
51
|
+
options.json = true;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
const value = argv[++index];
|
|
55
|
+
if (key === "--lockfile" && value && !value.startsWith("--"))
|
|
56
|
+
options.lockfile = value;
|
|
57
|
+
else if (
|
|
58
|
+
key === "--max-paths" &&
|
|
59
|
+
/^[1-9]\d*$/.test(value ?? "") &&
|
|
60
|
+
Number(value) <= WHY_LIMITS.maxPaths
|
|
61
|
+
)
|
|
62
|
+
options.maxPaths = Number(value);
|
|
63
|
+
else
|
|
64
|
+
throw new LocalInputError(
|
|
65
|
+
"invalid-option",
|
|
66
|
+
`Use --lockfile PATH, --max-paths 1..${WHY_LIMITS.maxPaths} or --json`,
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
return options;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function graph(lock) {
|
|
73
|
+
const dependents = new Map();
|
|
74
|
+
const unresolved = [];
|
|
75
|
+
for (const entry of lock.entries.values()) {
|
|
76
|
+
for (const edge of entry.edges) {
|
|
77
|
+
const target = resolveEdge(lock, entry.key, edge.name);
|
|
78
|
+
if (!target) {
|
|
79
|
+
if (edge.type !== "dev" || ["root", "workspace"].includes(entry.kind))
|
|
80
|
+
unresolved.push({ from: entry.key, ...edge });
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
const list = dependents.get(target.key) ?? [];
|
|
84
|
+
list.push({ from: entry.key, type: edge.type, range: edge.range });
|
|
85
|
+
dependents.set(target.key, list);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return { dependents, unresolved };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const isRoot = (entry) => ["root", "workspace"].includes(entry?.kind);
|
|
92
|
+
const label = (entry) =>
|
|
93
|
+
entry.kind === "root"
|
|
94
|
+
? `${entry.name ?? "(root)"} (root)`
|
|
95
|
+
: `${entry.name}@${entry.version ?? "?"}${entry.kind === "workspace" ? " (workspace)" : ""}`;
|
|
96
|
+
|
|
97
|
+
function pathsTo(lock, dependents, key, limits) {
|
|
98
|
+
const paths = [];
|
|
99
|
+
let steps = 0;
|
|
100
|
+
let truncated = false;
|
|
101
|
+
const onPath = new Set([key]);
|
|
102
|
+
// Depth-first walk to roots over dependents; onPath breaks cycles.
|
|
103
|
+
function walk(current, trail) {
|
|
104
|
+
if (paths.length >= limits.maxPaths || ++steps > limits.maxSteps) {
|
|
105
|
+
truncated = true;
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
const entry = lock.entries.get(current);
|
|
109
|
+
if (isRoot(entry) && current !== key) {
|
|
110
|
+
paths.push(trail.slice().reverse());
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
if (trail.length > limits.maxDepth) {
|
|
114
|
+
truncated = true;
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
for (const link of dependents.get(current) ?? []) {
|
|
118
|
+
if (onPath.has(link.from)) continue;
|
|
119
|
+
onPath.add(link.from);
|
|
120
|
+
trail.push({ key: link.from, type: link.type, range: link.range });
|
|
121
|
+
walk(link.from, trail);
|
|
122
|
+
trail.pop();
|
|
123
|
+
onPath.delete(link.from);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
walk(key, [{ key, type: null, range: null }]);
|
|
127
|
+
paths.sort((a, b) => a.length - b.length);
|
|
128
|
+
return {
|
|
129
|
+
paths: paths.map((trail) =>
|
|
130
|
+
trail.map((step) => {
|
|
131
|
+
const entry = lock.entries.get(step.key);
|
|
132
|
+
return {
|
|
133
|
+
key: step.key,
|
|
134
|
+
label: label(entry),
|
|
135
|
+
dependencyType: step.type,
|
|
136
|
+
range: step.range,
|
|
137
|
+
};
|
|
138
|
+
}),
|
|
139
|
+
),
|
|
140
|
+
truncated,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function explainWhy(lock, query, { maxPaths = 10 } = {}) {
|
|
145
|
+
const limits = { ...WHY_LIMITS, maxPaths };
|
|
146
|
+
const { dependents, unresolved } = graph(lock);
|
|
147
|
+
const matches = [];
|
|
148
|
+
for (const entry of lock.entries.values()) {
|
|
149
|
+
if (entry.kind === "root" || entry.kind === "link") continue;
|
|
150
|
+
const alias = nameFromKey(entry.key);
|
|
151
|
+
if (entry.name !== query.name && alias !== query.name) continue;
|
|
152
|
+
if (query.version && entry.version !== query.version) continue;
|
|
153
|
+
const { paths, truncated } = pathsTo(lock, dependents, entry.key, limits);
|
|
154
|
+
matches.push({
|
|
155
|
+
key: entry.key,
|
|
156
|
+
name: entry.name,
|
|
157
|
+
alias: alias !== entry.name ? alias : null,
|
|
158
|
+
version: entry.version,
|
|
159
|
+
kind: entry.kind,
|
|
160
|
+
dev: entry.dev,
|
|
161
|
+
optional: entry.optional,
|
|
162
|
+
bundled: entry.bundled,
|
|
163
|
+
direct: paths.some((trail) => trail.length === 2),
|
|
164
|
+
workspace: entry.kind === "workspace",
|
|
165
|
+
paths,
|
|
166
|
+
truncated,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
const versions = new Set(matches.map((match) => match.version));
|
|
170
|
+
const limitations = [
|
|
171
|
+
"Paths follow lockfile placement and npm node_modules resolution, not the installed tree on disk.",
|
|
172
|
+
];
|
|
173
|
+
if (matches.length > 1)
|
|
174
|
+
limitations.push(
|
|
175
|
+
`${matches.length} locations (${versions.size} versions) are locked; each is explained separately.`,
|
|
176
|
+
);
|
|
177
|
+
if (matches.some((match) => match.truncated))
|
|
178
|
+
limitations.push("Some path lists were truncated by traversal limits.");
|
|
179
|
+
if (matches.some((match) => !match.workspace && match.paths.length === 0))
|
|
180
|
+
limitations.push(
|
|
181
|
+
"Entries with no path are extraneous, bundled or reached only through unresolved links.",
|
|
182
|
+
);
|
|
183
|
+
if (matches.some((match) => match.workspace))
|
|
184
|
+
limitations.push(
|
|
185
|
+
"A workspace is part of the project itself; paths show only other workspaces that depend on it.",
|
|
186
|
+
);
|
|
187
|
+
if ([...lock.entries.values()].some((entry) => entry.kind === "workspace"))
|
|
188
|
+
limitations.push(
|
|
189
|
+
"Workspaces are treated as their own roots; root-to-workspace membership is not shown as an edge.",
|
|
190
|
+
);
|
|
191
|
+
return {
|
|
192
|
+
schemaVersion: "1",
|
|
193
|
+
query,
|
|
194
|
+
found: matches.length > 0,
|
|
195
|
+
matches,
|
|
196
|
+
unresolvedEdges: unresolved
|
|
197
|
+
.filter((edge) => edge.name === query.name)
|
|
198
|
+
.slice(0, limits.maxPaths),
|
|
199
|
+
limitations,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function formatText(result) {
|
|
204
|
+
const lines = [];
|
|
205
|
+
for (const match of result.matches) {
|
|
206
|
+
const flags = [
|
|
207
|
+
match.workspace ? null : match.direct ? "direct" : "transitive",
|
|
208
|
+
match.dev && "dev",
|
|
209
|
+
match.optional && "optional",
|
|
210
|
+
match.bundled && "bundled",
|
|
211
|
+
!["registry", "bundled"].includes(match.kind) && match.kind,
|
|
212
|
+
].filter(Boolean);
|
|
213
|
+
lines.push(
|
|
214
|
+
`${match.name}@${match.version ?? "?"} at ${match.key} (${flags.join(", ")}): ${match.paths.length}${match.truncated ? "+" : ""} path(s)`,
|
|
215
|
+
);
|
|
216
|
+
for (const trail of match.paths)
|
|
217
|
+
lines.push(
|
|
218
|
+
" " +
|
|
219
|
+
trail
|
|
220
|
+
.map((step) =>
|
|
221
|
+
step.dependencyType && step.dependencyType !== "prod"
|
|
222
|
+
? `${step.label} -[${step.dependencyType}]->`
|
|
223
|
+
: step.dependencyType
|
|
224
|
+
? `${step.label} ->`
|
|
225
|
+
: step.label,
|
|
226
|
+
)
|
|
227
|
+
.join(" "),
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
for (const note of result.limitations) lines.push(`Note: ${note}`);
|
|
231
|
+
return lines.join("\n") + "\n";
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export async function runWhy(argv) {
|
|
235
|
+
const options = parseWhyArgs(argv);
|
|
236
|
+
const lock = await readLockfile(options.lockfile);
|
|
237
|
+
const result = explainWhy(lock, options.query, options);
|
|
238
|
+
if (!result.found && result.unresolvedEdges.length > 0) {
|
|
239
|
+
const from = result.unresolvedEdges
|
|
240
|
+
.map((edge) => `${edge.from || "the root"} (${edge.type})`)
|
|
241
|
+
.join(", ");
|
|
242
|
+
throw new LocalInputError(
|
|
243
|
+
"package-not-locked",
|
|
244
|
+
`Declared by ${from} but has no locked entry; npm would resolve it at install time, so it is unscreened.`,
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
if (!result.found)
|
|
248
|
+
throw new LocalInputError(
|
|
249
|
+
"package-not-in-lockfile",
|
|
250
|
+
"No locked entry matches that package; it is not part of this lockfile.",
|
|
251
|
+
);
|
|
252
|
+
process.stdout.write(
|
|
253
|
+
options.json ? JSON.stringify(result, null, 2) + "\n" : formatText(result),
|
|
254
|
+
);
|
|
255
|
+
return 0;
|
|
256
|
+
}
|
package/dist/core/advisories.mjs
CHANGED
|
@@ -6,16 +6,21 @@ export const ADVISORY_LIMITS = Object.freeze({
|
|
|
6
6
|
responseBytes: 1024 * 1024,
|
|
7
7
|
pages: 3,
|
|
8
8
|
advisories: 100,
|
|
9
|
+
retries: 1,
|
|
10
|
+
retryDelayMs: 250,
|
|
9
11
|
});
|
|
12
|
+
const TRANSIENT_STATUSES = [429, 500, 502, 503, 504];
|
|
10
13
|
const systemClock = { now: Date.now, setTimeout, clearTimeout };
|
|
11
14
|
class LookupFailure extends Error {
|
|
12
|
-
constructor(reason) {
|
|
15
|
+
constructor(reason, detail = {}) {
|
|
13
16
|
super(reason);
|
|
14
17
|
this.reason = reason;
|
|
18
|
+
this.httpStatus = detail.httpStatus;
|
|
19
|
+
this.retryable = detail.retryable === true;
|
|
15
20
|
}
|
|
16
21
|
}
|
|
17
|
-
const fail = (reason) => {
|
|
18
|
-
throw new LookupFailure(reason);
|
|
22
|
+
const fail = (reason, detail) => {
|
|
23
|
+
throw new LookupFailure(reason, detail);
|
|
19
24
|
};
|
|
20
25
|
const object = (value) =>
|
|
21
26
|
value !== null && typeof value === "object" && !Array.isArray(value);
|
|
@@ -248,7 +253,41 @@ export async function lookupNpmAdvisories(
|
|
|
248
253
|
const reasons = new Set();
|
|
249
254
|
const records = new Map();
|
|
250
255
|
const mark = (reason) => reasons.add(reason);
|
|
256
|
+
const provider = { requests: 0, retries: 0, failures: [] };
|
|
257
|
+
let timedOut = false,
|
|
258
|
+
active = true;
|
|
259
|
+
const recorded = new WeakSet();
|
|
260
|
+
function note(error) {
|
|
261
|
+
if (!active || recorded.has(error)) return;
|
|
262
|
+
recorded.add(error);
|
|
263
|
+
const reason =
|
|
264
|
+
error instanceof LookupFailure ? error.reason : "request-failed";
|
|
265
|
+
const last = provider.failures.at(-1);
|
|
266
|
+
if (last?.reason === reason && last.attempt === provider.requests) return;
|
|
267
|
+
provider.failures.push({
|
|
268
|
+
attempt: provider.requests,
|
|
269
|
+
reason,
|
|
270
|
+
...(error?.httpStatus ? { httpStatus: error.httpStatus } : {}),
|
|
271
|
+
});
|
|
272
|
+
}
|
|
251
273
|
function finish() {
|
|
274
|
+
result.diagnostics = {
|
|
275
|
+
provider: {
|
|
276
|
+
name: "OSV",
|
|
277
|
+
availability: !provider.requests
|
|
278
|
+
? "not-queried"
|
|
279
|
+
: !result.pagesFetched
|
|
280
|
+
? "unavailable"
|
|
281
|
+
: provider.failures.length
|
|
282
|
+
? "degraded"
|
|
283
|
+
: "available",
|
|
284
|
+
requests: provider.requests,
|
|
285
|
+
retries: provider.retries,
|
|
286
|
+
timeouts: timedOut ? 1 : 0,
|
|
287
|
+
failures: provider.failures.map((failure) => ({ ...failure })),
|
|
288
|
+
durationMs: Math.max(0, clock.now() - started),
|
|
289
|
+
},
|
|
290
|
+
};
|
|
252
291
|
result.advisories = [...records.values()].sort((a, b) =>
|
|
253
292
|
a.id < b.id ? -1 : a.id > b.id ? 1 : 0,
|
|
254
293
|
);
|
|
@@ -277,7 +316,7 @@ export async function lookupNpmAdvisories(
|
|
|
277
316
|
(key) =>
|
|
278
317
|
!Object.hasOwn(ADVISORY_LIMITS, key) ||
|
|
279
318
|
!Number.isSafeInteger(limits[key]) ||
|
|
280
|
-
limits[key] < 1 ||
|
|
319
|
+
limits[key] < (key === "retries" ? 0 : 1) ||
|
|
281
320
|
limits[key] > ADVISORY_LIMITS[key],
|
|
282
321
|
)
|
|
283
322
|
) {
|
|
@@ -293,13 +332,12 @@ export async function lookupNpmAdvisories(
|
|
|
293
332
|
const combined = signal
|
|
294
333
|
? AbortSignal.any([signal, controller.signal])
|
|
295
334
|
: controller.signal;
|
|
296
|
-
let timedOut = false,
|
|
297
|
-
active = true;
|
|
298
335
|
const readers = new Set();
|
|
299
336
|
const timer = clock.setTimeout(() => {
|
|
300
337
|
timedOut = true;
|
|
301
338
|
controller.abort();
|
|
302
339
|
}, limits.timeoutMs);
|
|
340
|
+
const pauses = new Set();
|
|
303
341
|
let interrupt;
|
|
304
342
|
const interrupted = new Promise((_, reject) => {
|
|
305
343
|
interrupt = () => {
|
|
@@ -318,22 +356,29 @@ export async function lookupNpmAdvisories(
|
|
|
318
356
|
}
|
|
319
357
|
async function page(token) {
|
|
320
358
|
alive();
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
359
|
+
provider.requests++;
|
|
360
|
+
let response;
|
|
361
|
+
try {
|
|
362
|
+
response = await fetchImpl(ENDPOINT, {
|
|
363
|
+
method: "POST",
|
|
364
|
+
redirect: "manual",
|
|
365
|
+
credentials: "omit",
|
|
366
|
+
referrerPolicy: "no-referrer",
|
|
367
|
+
signal: combined,
|
|
368
|
+
headers: {
|
|
369
|
+
"content-type": "application/json",
|
|
370
|
+
accept: "application/json",
|
|
371
|
+
},
|
|
372
|
+
body: JSON.stringify({
|
|
373
|
+
package: { name: claim.name, ecosystem: "npm" },
|
|
374
|
+
version: claim.version,
|
|
375
|
+
...(token ? { page_token: token } : {}),
|
|
376
|
+
}),
|
|
377
|
+
});
|
|
378
|
+
} catch {
|
|
379
|
+
alive();
|
|
380
|
+
fail("request-failed", { retryable: true });
|
|
381
|
+
}
|
|
337
382
|
if (combined.aborted || !active) response.body?.cancel().catch(() => {});
|
|
338
383
|
alive();
|
|
339
384
|
if (
|
|
@@ -346,7 +391,16 @@ export async function lookupNpmAdvisories(
|
|
|
346
391
|
}
|
|
347
392
|
if (response.status !== 200) {
|
|
348
393
|
response.body?.cancel().catch(() => {});
|
|
349
|
-
|
|
394
|
+
const status = Number.isSafeInteger(response.status)
|
|
395
|
+
? response.status
|
|
396
|
+
: undefined;
|
|
397
|
+
const wait = response.headers.get("retry-after");
|
|
398
|
+
fail("http-error", {
|
|
399
|
+
httpStatus: status,
|
|
400
|
+
retryable:
|
|
401
|
+
TRANSIENT_STATUSES.includes(status) &&
|
|
402
|
+
(wait === null || /^[01]$/.test(wait.trim())),
|
|
403
|
+
});
|
|
350
404
|
}
|
|
351
405
|
if (
|
|
352
406
|
!/^application\/json(?:\s*;\s*charset=utf-8)?$/i.test(
|
|
@@ -412,11 +466,32 @@ export async function lookupNpmAdvisories(
|
|
|
412
466
|
fail("invalid-response");
|
|
413
467
|
return parsed;
|
|
414
468
|
}
|
|
469
|
+
async function attempt(token) {
|
|
470
|
+
for (;;) {
|
|
471
|
+
try {
|
|
472
|
+
return await page(token);
|
|
473
|
+
} catch (error) {
|
|
474
|
+
note(error);
|
|
475
|
+
if (!error?.retryable || provider.retries >= limits.retries)
|
|
476
|
+
throw error;
|
|
477
|
+
provider.retries++;
|
|
478
|
+
// Back off once for transient provider failures, still inside the lookup deadline.
|
|
479
|
+
await new Promise((resolve) => {
|
|
480
|
+
const id = clock.setTimeout(() => {
|
|
481
|
+
pauses.delete(id);
|
|
482
|
+
resolve();
|
|
483
|
+
}, limits.retryDelayMs);
|
|
484
|
+
pauses.add(id);
|
|
485
|
+
});
|
|
486
|
+
alive();
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
415
490
|
async function query() {
|
|
416
491
|
const tokens = new Set();
|
|
417
492
|
let token;
|
|
418
493
|
for (let i = 0; i < limits.pages; i++) {
|
|
419
|
-
const value = await
|
|
494
|
+
const value = await attempt(token);
|
|
420
495
|
alive();
|
|
421
496
|
result.pagesFetched++;
|
|
422
497
|
for (const raw of value.vulns ?? []) {
|
|
@@ -446,10 +521,12 @@ export async function lookupNpmAdvisories(
|
|
|
446
521
|
try {
|
|
447
522
|
await Promise.race([query(), interrupted]);
|
|
448
523
|
} catch (error) {
|
|
524
|
+
note(error);
|
|
449
525
|
mark(error instanceof LookupFailure ? error.reason : "request-failed");
|
|
450
526
|
} finally {
|
|
451
527
|
active = false;
|
|
452
528
|
clock.clearTimeout(timer);
|
|
529
|
+
for (const id of pauses) clock.clearTimeout(id);
|
|
453
530
|
combined.removeEventListener("abort", interrupt);
|
|
454
531
|
for (const reader of readers) reader.cancel().catch(() => {});
|
|
455
532
|
}
|