@xagents_in/unmask 0.1.2 → 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 +301 -85
- package/dist/cli/annotations.mjs +145 -0
- package/dist/cli/audit.mjs +223 -0
- package/dist/cli/batch.mjs +218 -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 +101 -14
- package/dist/cli/preflight.mjs +388 -0
- package/dist/cli/sarif.mjs +100 -0
- package/dist/cli/scan.mjs +128 -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/registry-facts.mjs +342 -0
- package/dist/core/report-exploration.mjs +328 -0
- 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 +66 -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/registry-facts.mjs +118 -0
- package/dist/server/report-exploration.mjs +87 -0
- 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
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import { parsePackageSpec } from "./acquire.mjs";
|
|
2
|
+
import { ADVISORY_LIMITS, lookupNpmAdvisories } from "./advisories.mjs";
|
|
3
|
+
|
|
4
|
+
export const ADVISORY_CACHE_LIMITS = Object.freeze({
|
|
5
|
+
ttlMs: 15 * 60 * 1000,
|
|
6
|
+
staleMs: 6 * 60 * 60 * 1000,
|
|
7
|
+
entries: 256,
|
|
8
|
+
entryBytes: 2 * 1024 * 1024,
|
|
9
|
+
totalBytes: 32 * 1024 * 1024,
|
|
10
|
+
});
|
|
11
|
+
const systemClock = { now: Date.now };
|
|
12
|
+
const COMPLETE = ["found", "none-reported"];
|
|
13
|
+
|
|
14
|
+
function identity(spec) {
|
|
15
|
+
try {
|
|
16
|
+
const { name, version } = parsePackageSpec(spec);
|
|
17
|
+
return { name, version, key: `${name}@${version}` };
|
|
18
|
+
} catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function unavailable(claim, clock, reasons) {
|
|
24
|
+
return {
|
|
25
|
+
schemaVersion: "1",
|
|
26
|
+
source: "OSV",
|
|
27
|
+
package: claim
|
|
28
|
+
? { name: claim.name, version: claim.version, ecosystem: "npm" }
|
|
29
|
+
: null,
|
|
30
|
+
queriedAt: new Date(clock.now()).toISOString(),
|
|
31
|
+
status: "unavailable",
|
|
32
|
+
complete: false,
|
|
33
|
+
reasons: [...new Set(reasons)].sort(),
|
|
34
|
+
advisories: [],
|
|
35
|
+
pagesFetched: 0,
|
|
36
|
+
limits: { ...ADVISORY_LIMITS },
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const NOT_CONTACTED = Object.freeze({
|
|
41
|
+
name: "OSV",
|
|
42
|
+
availability: "not-contacted",
|
|
43
|
+
requests: 0,
|
|
44
|
+
retries: 0,
|
|
45
|
+
timeouts: 0,
|
|
46
|
+
failures: [],
|
|
47
|
+
durationMs: 0,
|
|
48
|
+
});
|
|
49
|
+
const FRESHNESS = {
|
|
50
|
+
hit: "cached",
|
|
51
|
+
miss: "live",
|
|
52
|
+
coalesced: "live",
|
|
53
|
+
stale: "stale",
|
|
54
|
+
bypass: "none",
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export function createAdvisoryCache({
|
|
58
|
+
lookup = lookupNpmAdvisories,
|
|
59
|
+
lookupOptions = {},
|
|
60
|
+
exposeStats = false,
|
|
61
|
+
clock = systemClock,
|
|
62
|
+
limits: overrides = {},
|
|
63
|
+
} = {}) {
|
|
64
|
+
if (typeof lookup !== "function")
|
|
65
|
+
throw new TypeError("lookup must be a function");
|
|
66
|
+
const limits = { ...ADVISORY_CACHE_LIMITS, ...overrides };
|
|
67
|
+
if (
|
|
68
|
+
Object.keys(limits).some(
|
|
69
|
+
(key) =>
|
|
70
|
+
!Object.hasOwn(ADVISORY_CACHE_LIMITS, key) ||
|
|
71
|
+
!Number.isSafeInteger(limits[key]) ||
|
|
72
|
+
limits[key] < 1 ||
|
|
73
|
+
limits[key] > ADVISORY_CACHE_LIMITS[key],
|
|
74
|
+
) ||
|
|
75
|
+
limits.staleMs < limits.ttlMs ||
|
|
76
|
+
limits.totalBytes < limits.entryBytes
|
|
77
|
+
)
|
|
78
|
+
throw new TypeError("Invalid advisory cache limits");
|
|
79
|
+
const entries = new Map();
|
|
80
|
+
const flights = new Map();
|
|
81
|
+
let bytes = 0;
|
|
82
|
+
const counts = {
|
|
83
|
+
hit: 0,
|
|
84
|
+
miss: 0,
|
|
85
|
+
coalesced: 0,
|
|
86
|
+
stale: 0,
|
|
87
|
+
uncached: 0,
|
|
88
|
+
bypass: 0,
|
|
89
|
+
evicted: 0,
|
|
90
|
+
expired: 0,
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
function drop(key) {
|
|
94
|
+
const entry = entries.get(key);
|
|
95
|
+
if (!entry) return;
|
|
96
|
+
entries.delete(key);
|
|
97
|
+
bytes -= entry.bytes;
|
|
98
|
+
}
|
|
99
|
+
function prune(now) {
|
|
100
|
+
for (const [key, entry] of entries)
|
|
101
|
+
if (now < entry.storedAt || now - entry.storedAt >= limits.staleMs) {
|
|
102
|
+
drop(key);
|
|
103
|
+
counts.expired++;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function cacheable(result, claim) {
|
|
107
|
+
return (
|
|
108
|
+
result !== null &&
|
|
109
|
+
typeof result === "object" &&
|
|
110
|
+
result.complete === true &&
|
|
111
|
+
COMPLETE.includes(result.status) &&
|
|
112
|
+
Array.isArray(result.reasons) &&
|
|
113
|
+
result.reasons.length === 0 &&
|
|
114
|
+
result.package?.name === claim.name &&
|
|
115
|
+
result.package?.version === claim.version
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
function store(claim, result, now) {
|
|
119
|
+
const json = JSON.stringify(result);
|
|
120
|
+
const size = Buffer.byteLength(json);
|
|
121
|
+
if (size > limits.entryBytes) return false;
|
|
122
|
+
drop(claim.key);
|
|
123
|
+
entries.set(claim.key, { json, bytes: size, storedAt: now });
|
|
124
|
+
bytes += size;
|
|
125
|
+
for (const key of entries.keys()) {
|
|
126
|
+
if (entries.size <= limits.entries && bytes <= limits.totalBytes) break;
|
|
127
|
+
drop(key);
|
|
128
|
+
counts.evicted++;
|
|
129
|
+
}
|
|
130
|
+
return entries.has(claim.key);
|
|
131
|
+
}
|
|
132
|
+
function provenance(status, entry, now) {
|
|
133
|
+
return {
|
|
134
|
+
status,
|
|
135
|
+
ttlMs: limits.ttlMs,
|
|
136
|
+
...(entry
|
|
137
|
+
? {
|
|
138
|
+
storedAt: new Date(entry.storedAt).toISOString(),
|
|
139
|
+
ageMs: now - entry.storedAt,
|
|
140
|
+
}
|
|
141
|
+
: {}),
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
function fly(claim, spec) {
|
|
145
|
+
const controller = new AbortController();
|
|
146
|
+
const flight = { controller, waiters: 0 };
|
|
147
|
+
flight.promise = Promise.resolve()
|
|
148
|
+
.then(() => lookup(spec, { ...lookupOptions, signal: controller.signal }))
|
|
149
|
+
.catch(() => null)
|
|
150
|
+
.then((result) => {
|
|
151
|
+
if (flights.get(claim.key) === flight) flights.delete(claim.key);
|
|
152
|
+
const now = clock.now();
|
|
153
|
+
if (cacheable(result, claim) && store(claim, result, now))
|
|
154
|
+
return { stored: entries.get(claim.key), now };
|
|
155
|
+
return {
|
|
156
|
+
result: result ?? unavailable(claim, clock, ["lookup-failed"]),
|
|
157
|
+
now,
|
|
158
|
+
};
|
|
159
|
+
});
|
|
160
|
+
flights.set(claim.key, flight);
|
|
161
|
+
return flight;
|
|
162
|
+
}
|
|
163
|
+
function settle(claim, outcome, coalesced) {
|
|
164
|
+
const { now } = outcome;
|
|
165
|
+
if (outcome.stored)
|
|
166
|
+
return {
|
|
167
|
+
...JSON.parse(outcome.stored.json),
|
|
168
|
+
cache: provenance(
|
|
169
|
+
coalesced ? "coalesced" : "miss",
|
|
170
|
+
outcome.stored,
|
|
171
|
+
now,
|
|
172
|
+
),
|
|
173
|
+
};
|
|
174
|
+
const result = structuredClone(outcome.result);
|
|
175
|
+
const prior = entries.get(claim.key);
|
|
176
|
+
// Only a refresh that produced no advisory data may fall back to an older complete snapshot.
|
|
177
|
+
if (
|
|
178
|
+
result.status === "unavailable" &&
|
|
179
|
+
prior &&
|
|
180
|
+
now >= prior.storedAt &&
|
|
181
|
+
now - prior.storedAt < limits.staleMs
|
|
182
|
+
) {
|
|
183
|
+
const snapshot = JSON.parse(prior.json);
|
|
184
|
+
return {
|
|
185
|
+
...snapshot,
|
|
186
|
+
status: "incomplete",
|
|
187
|
+
complete: false,
|
|
188
|
+
reasons: [...new Set(["stale-cache", ...result.reasons])].sort(),
|
|
189
|
+
diagnostics: result.diagnostics,
|
|
190
|
+
cache: {
|
|
191
|
+
...provenance("stale", prior, now),
|
|
192
|
+
snapshotStatus: snapshot.status,
|
|
193
|
+
refreshStatus: result.status,
|
|
194
|
+
},
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
return { ...result, cache: provenance("uncached", null, now) };
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Aggregate counters only: never package identities, keys or timestamps of individual entries.
|
|
201
|
+
function stats() {
|
|
202
|
+
return {
|
|
203
|
+
entries: entries.size,
|
|
204
|
+
bytes,
|
|
205
|
+
inflight: flights.size,
|
|
206
|
+
hits: counts.hit,
|
|
207
|
+
misses: counts.miss,
|
|
208
|
+
coalesced: counts.coalesced,
|
|
209
|
+
staleServed: counts.stale,
|
|
210
|
+
uncached: counts.uncached,
|
|
211
|
+
bypassed: counts.bypass,
|
|
212
|
+
evicted: counts.evicted,
|
|
213
|
+
expired: counts.expired,
|
|
214
|
+
limits: { ...limits },
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
async function cachedLookup(spec, options) {
|
|
218
|
+
const result = await resolve(spec, options);
|
|
219
|
+
const now = clock.now();
|
|
220
|
+
const { status, storedAt } = result.cache;
|
|
221
|
+
counts[status]++;
|
|
222
|
+
const state =
|
|
223
|
+
FRESHNESS[status] ?? (result.status === "unavailable" ? "none" : "live");
|
|
224
|
+
const queried = Date.parse(result.queriedAt);
|
|
225
|
+
const provider =
|
|
226
|
+
status === "hit" ? NOT_CONTACTED : result.diagnostics?.provider;
|
|
227
|
+
return {
|
|
228
|
+
...result,
|
|
229
|
+
diagnostics: {
|
|
230
|
+
...(provider ? { provider: structuredClone(provider) } : {}),
|
|
231
|
+
freshness: {
|
|
232
|
+
state,
|
|
233
|
+
...(state !== "none" && Number.isFinite(queried)
|
|
234
|
+
? { queriedAt: result.queriedAt, ageMs: Math.max(0, now - queried) }
|
|
235
|
+
: {}),
|
|
236
|
+
...(storedAt && state !== "stale"
|
|
237
|
+
? {
|
|
238
|
+
freshUntil: new Date(
|
|
239
|
+
Date.parse(storedAt) + limits.ttlMs,
|
|
240
|
+
).toISOString(),
|
|
241
|
+
}
|
|
242
|
+
: {}),
|
|
243
|
+
},
|
|
244
|
+
...(exposeStats ? { cacheStats: stats() } : {}),
|
|
245
|
+
},
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
async function resolve(spec, { signal } = {}) {
|
|
249
|
+
const claim = identity(spec);
|
|
250
|
+
if (!claim) {
|
|
251
|
+
const result = await lookup(spec, { ...lookupOptions, signal });
|
|
252
|
+
return { ...result, cache: provenance("bypass", null, clock.now()) };
|
|
253
|
+
}
|
|
254
|
+
const now = clock.now();
|
|
255
|
+
prune(now);
|
|
256
|
+
const entry = entries.get(claim.key);
|
|
257
|
+
if (entry && now - entry.storedAt < limits.ttlMs) {
|
|
258
|
+
entries.delete(claim.key);
|
|
259
|
+
entries.set(claim.key, entry);
|
|
260
|
+
return {
|
|
261
|
+
...JSON.parse(entry.json),
|
|
262
|
+
cache: provenance("hit", entry, now),
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
if (signal?.aborted)
|
|
266
|
+
return {
|
|
267
|
+
...unavailable(claim, clock, ["aborted"]),
|
|
268
|
+
cache: provenance("uncached", null, now),
|
|
269
|
+
};
|
|
270
|
+
const existing = flights.get(claim.key);
|
|
271
|
+
const flight = existing ?? fly(claim, spec);
|
|
272
|
+
flight.waiters++;
|
|
273
|
+
let release;
|
|
274
|
+
const aborted = new Promise((resolve) => {
|
|
275
|
+
release = () => resolve(null);
|
|
276
|
+
signal?.addEventListener("abort", release, { once: true });
|
|
277
|
+
});
|
|
278
|
+
try {
|
|
279
|
+
const outcome = await Promise.race([flight.promise, aborted]);
|
|
280
|
+
if (outcome) return settle(claim, outcome, Boolean(existing));
|
|
281
|
+
return {
|
|
282
|
+
...unavailable(claim, clock, ["aborted"]),
|
|
283
|
+
cache: provenance("uncached", null, clock.now()),
|
|
284
|
+
};
|
|
285
|
+
} finally {
|
|
286
|
+
signal?.removeEventListener("abort", release);
|
|
287
|
+
// The shared upstream request is cancelled only once every waiter has gone.
|
|
288
|
+
if (--flight.waiters === 0 && flights.get(claim.key) === flight) {
|
|
289
|
+
flights.delete(claim.key);
|
|
290
|
+
flight.controller.abort();
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return { lookup: cachedLookup, stats };
|
|
295
|
+
}
|