@xagents_in/unmask 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -8
- package/dist/core/advisories.mjs +457 -0
- package/dist/core/package-details.mjs +638 -0
- package/dist/model/client.mjs +60 -33
- package/dist/server/app.mjs +31 -6
- package/dist/server/enrichment.mjs +116 -0
- package/dist/server/main.mjs +16 -0
- package/dist/server/queue.mjs +85 -6
- package/dist/version.json +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,638 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { validateReport } from "./report.mjs";
|
|
3
|
+
|
|
4
|
+
export const PACKAGE_DETAILS_LIMITS = Object.freeze({
|
|
5
|
+
maxItems: 20000,
|
|
6
|
+
maxValueBytes: 32768,
|
|
7
|
+
maxSectionBytes: 262144,
|
|
8
|
+
maxTotalBytes: 2097152,
|
|
9
|
+
});
|
|
10
|
+
const order = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
|
|
11
|
+
const object = (value) =>
|
|
12
|
+
value !== null && typeof value === "object" && !Array.isArray(value);
|
|
13
|
+
const pointer = (value) =>
|
|
14
|
+
String(value).replace(/~/g, "~0").replace(/\//g, "~1");
|
|
15
|
+
const sorted = (value) => Object.keys(value).sort(order);
|
|
16
|
+
const type = (value) =>
|
|
17
|
+
value === null ? "null" : Array.isArray(value) ? "array" : typeof value;
|
|
18
|
+
|
|
19
|
+
export function buildPackageDetails(report, options = {}) {
|
|
20
|
+
validateReport(report);
|
|
21
|
+
const limits = { ...PACKAGE_DETAILS_LIMITS, ...options };
|
|
22
|
+
if (
|
|
23
|
+
Object.entries(limits).some(
|
|
24
|
+
([key, value]) =>
|
|
25
|
+
!Object.hasOwn(PACKAGE_DETAILS_LIMITS, key) ||
|
|
26
|
+
!Number.isSafeInteger(value) ||
|
|
27
|
+
value < 1 ||
|
|
28
|
+
value > PACKAGE_DETAILS_LIMITS[key],
|
|
29
|
+
) ||
|
|
30
|
+
limits.maxTotalBytes < 65536
|
|
31
|
+
)
|
|
32
|
+
throw new TypeError("Invalid package details limits");
|
|
33
|
+
let remaining = limits.maxTotalBytes - 32768;
|
|
34
|
+
function take(values) {
|
|
35
|
+
const result = {
|
|
36
|
+
items: [],
|
|
37
|
+
total: values.length,
|
|
38
|
+
returned: 0,
|
|
39
|
+
omitted: 0,
|
|
40
|
+
omissionReasons: {},
|
|
41
|
+
};
|
|
42
|
+
let bytes = 2;
|
|
43
|
+
for (const value of values) {
|
|
44
|
+
const json = JSON.stringify(value);
|
|
45
|
+
const size = Buffer.byteLength(json) + 1;
|
|
46
|
+
const reason =
|
|
47
|
+
size > limits.maxValueBytes
|
|
48
|
+
? "value-byte-cap"
|
|
49
|
+
: result.returned >= limits.maxItems
|
|
50
|
+
? "item-cap"
|
|
51
|
+
: bytes + size > limits.maxSectionBytes
|
|
52
|
+
? "section-byte-cap"
|
|
53
|
+
: size > remaining
|
|
54
|
+
? "response-byte-cap"
|
|
55
|
+
: null;
|
|
56
|
+
if (reason) {
|
|
57
|
+
result.omitted++;
|
|
58
|
+
result.omissionReasons[reason] =
|
|
59
|
+
(result.omissionReasons[reason] ?? 0) + 1;
|
|
60
|
+
} else {
|
|
61
|
+
result.items.push(JSON.parse(json));
|
|
62
|
+
result.returned++;
|
|
63
|
+
bytes += size;
|
|
64
|
+
remaining -= size;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return result;
|
|
68
|
+
}
|
|
69
|
+
const manifest = report.manifests.artifact;
|
|
70
|
+
const declared = manifest.status === "usable" ? manifest.value : null;
|
|
71
|
+
const allManifestEvidenceIds = declared
|
|
72
|
+
? [...new Set(manifest.evidenceIds)].sort(order)
|
|
73
|
+
: [];
|
|
74
|
+
const evidenceIds = allManifestEvidenceIds.slice(0, 16);
|
|
75
|
+
const evidenceIdsOmitted = allManifestEvidenceIds.length - evidenceIds.length;
|
|
76
|
+
function field(name) {
|
|
77
|
+
const present = declared !== null && Object.hasOwn(declared, name);
|
|
78
|
+
return {
|
|
79
|
+
field: name,
|
|
80
|
+
pointer: `/${pointer(name)}`,
|
|
81
|
+
status:
|
|
82
|
+
declared === null
|
|
83
|
+
? manifest.status
|
|
84
|
+
: present
|
|
85
|
+
? "declared"
|
|
86
|
+
: "not-declared-in-manifest",
|
|
87
|
+
...(present
|
|
88
|
+
? { value: declared[name], valueType: type(declared[name]) }
|
|
89
|
+
: {}),
|
|
90
|
+
evidenceIds,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
function entries(name, mode = "map") {
|
|
94
|
+
const entry = field(name);
|
|
95
|
+
const value = entry.value;
|
|
96
|
+
let status = entry.status;
|
|
97
|
+
let values = [];
|
|
98
|
+
let declaredCount = null;
|
|
99
|
+
if (entry.status === "declared") {
|
|
100
|
+
if (mode === "bundled" && typeof value === "boolean") {
|
|
101
|
+
status = value
|
|
102
|
+
? "declared-all-runtime-dependencies"
|
|
103
|
+
: "declared-disabled";
|
|
104
|
+
} else if (mode === "bundled" && Array.isArray(value)) {
|
|
105
|
+
values = value.map((item, index) => ({
|
|
106
|
+
name: item,
|
|
107
|
+
index,
|
|
108
|
+
status: typeof item === "string" ? "declared" : "invalid-type",
|
|
109
|
+
pointer: `/${name}/${index}`,
|
|
110
|
+
evidenceIds,
|
|
111
|
+
}));
|
|
112
|
+
values.sort(
|
|
113
|
+
(a, b) =>
|
|
114
|
+
order(JSON.stringify(a.name), JSON.stringify(b.name)) ||
|
|
115
|
+
a.index - b.index,
|
|
116
|
+
);
|
|
117
|
+
declaredCount = value.length;
|
|
118
|
+
} else if (mode === "map" && object(value)) {
|
|
119
|
+
values = sorted(value).map((key) => ({
|
|
120
|
+
name: key,
|
|
121
|
+
value: value[key],
|
|
122
|
+
status: typeof value[key] === "string" ? "declared" : "invalid-type",
|
|
123
|
+
pointer: `/${name}/${pointer(key)}`,
|
|
124
|
+
...(name === "scripts"
|
|
125
|
+
? {
|
|
126
|
+
installHook: ["preinstall", "install", "postinstall"].includes(
|
|
127
|
+
key,
|
|
128
|
+
),
|
|
129
|
+
}
|
|
130
|
+
: {}),
|
|
131
|
+
evidenceIds,
|
|
132
|
+
}));
|
|
133
|
+
declaredCount = values.length;
|
|
134
|
+
} else status = "invalid-type";
|
|
135
|
+
} else if (entry.status === "not-declared-in-manifest") declaredCount = 0;
|
|
136
|
+
return {
|
|
137
|
+
field: name,
|
|
138
|
+
status,
|
|
139
|
+
declaredCount,
|
|
140
|
+
validEntryCount:
|
|
141
|
+
declaredCount === null
|
|
142
|
+
? null
|
|
143
|
+
: values.filter((item) => item.status === "declared").length,
|
|
144
|
+
entries: take(values),
|
|
145
|
+
evidenceIds,
|
|
146
|
+
evidenceIdsOmitted,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
const fileOrder = (a, b) =>
|
|
150
|
+
order(a.path ?? "", b.path ?? "") || order(a.originalPath, b.originalPath);
|
|
151
|
+
const files = [...report.files].sort(fileOrder);
|
|
152
|
+
const extensions = new Map();
|
|
153
|
+
for (const file of files) {
|
|
154
|
+
const extension =
|
|
155
|
+
path.posix.extname(file.path ?? file.originalPath).toLowerCase() ||
|
|
156
|
+
"(none)";
|
|
157
|
+
const group = extensions.get(extension) ?? {
|
|
158
|
+
extension,
|
|
159
|
+
files: 0,
|
|
160
|
+
bytes: 0,
|
|
161
|
+
};
|
|
162
|
+
group.files++;
|
|
163
|
+
group.bytes += file.size;
|
|
164
|
+
extensions.set(extension, group);
|
|
165
|
+
}
|
|
166
|
+
const observations = [
|
|
167
|
+
...report.cues.map((cue, index) => ({ ...cue, cueRef: `/cues/${index}` })),
|
|
168
|
+
...report.annotations.map((cue, index) => ({
|
|
169
|
+
...cue,
|
|
170
|
+
cueRef: `/annotations/${index}`,
|
|
171
|
+
})),
|
|
172
|
+
].filter(
|
|
173
|
+
(cue) => typeof cue.ruleId === "string" && cue.ruleId.startsWith("syntax-"),
|
|
174
|
+
);
|
|
175
|
+
observations.sort(
|
|
176
|
+
(a, b) => order(a.ruleId, b.ruleId) || order(a.cueRef, b.cueRef),
|
|
177
|
+
);
|
|
178
|
+
const capabilityGroups = new Map();
|
|
179
|
+
for (const cue of observations) {
|
|
180
|
+
const group = capabilityGroups.get(cue.ruleId) ?? {
|
|
181
|
+
ruleId: cue.ruleId,
|
|
182
|
+
observations: 0,
|
|
183
|
+
};
|
|
184
|
+
group.observations++;
|
|
185
|
+
capabilityGroups.set(cue.ruleId, group);
|
|
186
|
+
}
|
|
187
|
+
const byPath = new Map(
|
|
188
|
+
files.filter((file) => file.path !== null).map((file) => [file.path, file]),
|
|
189
|
+
);
|
|
190
|
+
const topDirectories = new Map();
|
|
191
|
+
for (const file of files) {
|
|
192
|
+
const name = (file.path ?? file.originalPath).includes("/")
|
|
193
|
+
? (file.path ?? file.originalPath).split("/")[0]
|
|
194
|
+
: "(root)";
|
|
195
|
+
const item = topDirectories.get(name) ?? {
|
|
196
|
+
directory: name,
|
|
197
|
+
files: 0,
|
|
198
|
+
bytes: 0,
|
|
199
|
+
};
|
|
200
|
+
item.files++;
|
|
201
|
+
item.bytes += file.size;
|
|
202
|
+
topDirectories.set(name, item);
|
|
203
|
+
}
|
|
204
|
+
const evidenceById = new Map(report.evidence.map((item) => [item.id, item]));
|
|
205
|
+
const cueRefsByPath = new Map();
|
|
206
|
+
for (const cue of observations)
|
|
207
|
+
for (const id of cue.evidenceIds ?? []) {
|
|
208
|
+
const evidence = evidenceById.get(id);
|
|
209
|
+
if (evidence?.side !== "artifact") continue;
|
|
210
|
+
const refs = cueRefsByPath.get(evidence.path) ?? new Set();
|
|
211
|
+
refs.add(cue.cueRef);
|
|
212
|
+
cueRefsByPath.set(evidence.path, refs);
|
|
213
|
+
}
|
|
214
|
+
const skippedByPath = new Map();
|
|
215
|
+
for (const annotation of report.annotations)
|
|
216
|
+
if (
|
|
217
|
+
annotation.type === "syntax-not-inspected" &&
|
|
218
|
+
annotation.side === "artifact"
|
|
219
|
+
) {
|
|
220
|
+
const reasons = skippedByPath.get(annotation.path) ?? new Set();
|
|
221
|
+
reasons.add(annotation.reason);
|
|
222
|
+
skippedByPath.set(annotation.path, reasons);
|
|
223
|
+
}
|
|
224
|
+
const targets = [];
|
|
225
|
+
let targetNodes = 0;
|
|
226
|
+
let targetTraversalOmitted = false;
|
|
227
|
+
function visitTarget(value, parts) {
|
|
228
|
+
if (++targetNodes > 4096) {
|
|
229
|
+
targetTraversalOmitted = true;
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
if (typeof value === "string") {
|
|
233
|
+
const candidate = value.startsWith("./") ? value.slice(2) : value;
|
|
234
|
+
const safe =
|
|
235
|
+
candidate.length > 0 &&
|
|
236
|
+
!/[\\\0*?\[\]{}:]/u.test(candidate) &&
|
|
237
|
+
!candidate.startsWith("/") &&
|
|
238
|
+
candidate
|
|
239
|
+
.split("/")
|
|
240
|
+
.every((part) => part && part !== "." && part !== "..");
|
|
241
|
+
const file = safe ? byPath.get(candidate) : undefined;
|
|
242
|
+
const cueRefs = file
|
|
243
|
+
? [...(cueRefsByPath.get(file.path) ?? [])].sort(order)
|
|
244
|
+
: [];
|
|
245
|
+
const skipped = file
|
|
246
|
+
? [...(skippedByPath.get(file.path) ?? [])].sort(order)
|
|
247
|
+
: [];
|
|
248
|
+
targets.push({
|
|
249
|
+
field: parts[0],
|
|
250
|
+
pointer: "/" + parts.map(pointer).join("/"),
|
|
251
|
+
target: value,
|
|
252
|
+
matchStatus: !safe
|
|
253
|
+
? "not-assessed-unsafe-or-pattern-target"
|
|
254
|
+
: file
|
|
255
|
+
? "exact-file-match"
|
|
256
|
+
: "no-exact-match-in-accepted-inventory",
|
|
257
|
+
...(file
|
|
258
|
+
? {
|
|
259
|
+
path: file.path,
|
|
260
|
+
state: file.state,
|
|
261
|
+
bytes: file.size,
|
|
262
|
+
evidenceIds: file.evidenceIds,
|
|
263
|
+
syntaxStatus: cueRefs.length
|
|
264
|
+
? "observed-syntax-cues"
|
|
265
|
+
: skipped.length
|
|
266
|
+
? "recorded-not-inspected"
|
|
267
|
+
: "not-recorded-per-file",
|
|
268
|
+
syntaxCueRefs: cueRefs,
|
|
269
|
+
syntaxSkippedReasons: skipped,
|
|
270
|
+
}
|
|
271
|
+
: { evidenceIds: [] }),
|
|
272
|
+
});
|
|
273
|
+
} else if (object(value) || Array.isArray(value)) {
|
|
274
|
+
for (const key of sorted(value)) {
|
|
275
|
+
if (targetNodes >= 4096) {
|
|
276
|
+
targetTraversalOmitted = true;
|
|
277
|
+
break;
|
|
278
|
+
}
|
|
279
|
+
visitTarget(value[key], [...parts, key]);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
if (declared)
|
|
284
|
+
for (const name of [
|
|
285
|
+
"main",
|
|
286
|
+
"module",
|
|
287
|
+
"exports",
|
|
288
|
+
"bin",
|
|
289
|
+
"types",
|
|
290
|
+
"typings",
|
|
291
|
+
"browser",
|
|
292
|
+
])
|
|
293
|
+
if (Object.hasOwn(declared, name)) visitTarget(declared[name], [name]);
|
|
294
|
+
function dependencyDelta(side) {
|
|
295
|
+
const other = report.manifests[side];
|
|
296
|
+
const proven =
|
|
297
|
+
declared &&
|
|
298
|
+
other.status === "usable" &&
|
|
299
|
+
(side === "source"
|
|
300
|
+
? report.source.complete &&
|
|
301
|
+
report.coverage.sourceComplete &&
|
|
302
|
+
["selected", "resolved"].includes(report.source.status) &&
|
|
303
|
+
!["unresolved", "conflicting"].includes(report.source.method)
|
|
304
|
+
: typeof report.inputs.registryDocument?.sha256 === "string");
|
|
305
|
+
const differences = [];
|
|
306
|
+
const unassessedFields = [];
|
|
307
|
+
if (proven)
|
|
308
|
+
for (const field of [
|
|
309
|
+
"dependencies",
|
|
310
|
+
"optionalDependencies",
|
|
311
|
+
"peerDependencies",
|
|
312
|
+
"devDependencies",
|
|
313
|
+
]) {
|
|
314
|
+
const left = declared[field];
|
|
315
|
+
const right = other.value[field];
|
|
316
|
+
if (
|
|
317
|
+
(left !== undefined && !object(left)) ||
|
|
318
|
+
(right !== undefined && !object(right))
|
|
319
|
+
) {
|
|
320
|
+
unassessedFields.push(field);
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
for (const name of [
|
|
324
|
+
...new Set([...Object.keys(left ?? {}), ...Object.keys(right ?? {})]),
|
|
325
|
+
].sort(order)) {
|
|
326
|
+
const inArtifact = Object.hasOwn(left ?? {}, name);
|
|
327
|
+
const inOther = Object.hasOwn(right ?? {}, name);
|
|
328
|
+
const artifactValue = inArtifact ? left[name] : null;
|
|
329
|
+
const otherValue = inOther ? right[name] : null;
|
|
330
|
+
if (
|
|
331
|
+
(inArtifact && typeof artifactValue !== "string") ||
|
|
332
|
+
(inOther && typeof otherValue !== "string")
|
|
333
|
+
) {
|
|
334
|
+
unassessedFields.push(field + "/" + pointer(name));
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
if (artifactValue !== otherValue)
|
|
338
|
+
differences.push({
|
|
339
|
+
field,
|
|
340
|
+
name,
|
|
341
|
+
change: !inOther
|
|
342
|
+
? "artifact-declared-only"
|
|
343
|
+
: !inArtifact
|
|
344
|
+
? side + "-declared-only"
|
|
345
|
+
: "declared-value-differs",
|
|
346
|
+
artifactValue,
|
|
347
|
+
otherValue,
|
|
348
|
+
pointer: "/" + field + "/" + pointer(name),
|
|
349
|
+
evidenceIds,
|
|
350
|
+
otherEvidenceIds: other.evidenceIds ?? [],
|
|
351
|
+
...(side === "registry"
|
|
352
|
+
? { documentSha256: report.inputs.registryDocument.sha256 }
|
|
353
|
+
: {}),
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
return {
|
|
358
|
+
side,
|
|
359
|
+
status: proven ? "declared-values-compared" : "not-assessed",
|
|
360
|
+
normalization: "not-assessed",
|
|
361
|
+
differences: take(differences),
|
|
362
|
+
unassessedFields: take(unassessedFields),
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
const result = {
|
|
366
|
+
detailsVersion: "1",
|
|
367
|
+
reportSchemaVersion: report.schemaVersion,
|
|
368
|
+
versions: take([
|
|
369
|
+
{
|
|
370
|
+
analyzer: report.analyzerVersion,
|
|
371
|
+
policy: report.policyVersion,
|
|
372
|
+
limits: report.limitsVersion,
|
|
373
|
+
},
|
|
374
|
+
]),
|
|
375
|
+
scope: {
|
|
376
|
+
method: "bounded-static-projection-of-existing-report",
|
|
377
|
+
safetyVerdict: "not-provided",
|
|
378
|
+
allCodeRead: false,
|
|
379
|
+
vulnerabilityScan: "not-performed",
|
|
380
|
+
licenseScan: "not-performed",
|
|
381
|
+
cryptographicProvenance:
|
|
382
|
+
"not-assessed-by-this-projection; consult recorded provenance status",
|
|
383
|
+
additionalFetches: 0,
|
|
384
|
+
additionalModelCalls: 0,
|
|
385
|
+
targetExecution: "not-performed",
|
|
386
|
+
limitation:
|
|
387
|
+
"Accepted inventory and bounded observations only. Declaration omissions are not runtime absence. Partial coverage cannot establish package-wide absence; extension groups do not prove language or behavior. Raw schema-1 report retains the underlying accepted detail.",
|
|
388
|
+
},
|
|
389
|
+
identity: take([
|
|
390
|
+
{
|
|
391
|
+
requested: report.inputs.claim ?? null,
|
|
392
|
+
artifactDigests: {
|
|
393
|
+
sha256: report.inputs.artifact.sha256,
|
|
394
|
+
sha512: report.inputs.artifact.sha512,
|
|
395
|
+
},
|
|
396
|
+
},
|
|
397
|
+
]),
|
|
398
|
+
declarations: {
|
|
399
|
+
side: "artifact",
|
|
400
|
+
evidenceIdsOmitted,
|
|
401
|
+
label: "publisher-declared; runtime behavior unverified",
|
|
402
|
+
status: manifest.status,
|
|
403
|
+
fields: take(
|
|
404
|
+
[
|
|
405
|
+
"name",
|
|
406
|
+
"version",
|
|
407
|
+
"description",
|
|
408
|
+
"license",
|
|
409
|
+
"licenses",
|
|
410
|
+
"engines",
|
|
411
|
+
"os",
|
|
412
|
+
"cpu",
|
|
413
|
+
"type",
|
|
414
|
+
"main",
|
|
415
|
+
"module",
|
|
416
|
+
"exports",
|
|
417
|
+
"bin",
|
|
418
|
+
"types",
|
|
419
|
+
"typings",
|
|
420
|
+
"homepage",
|
|
421
|
+
"keywords",
|
|
422
|
+
"browser",
|
|
423
|
+
]
|
|
424
|
+
.map(field)
|
|
425
|
+
.sort((a, b) => order(a.field, b.field)),
|
|
426
|
+
),
|
|
427
|
+
},
|
|
428
|
+
entrypoints: {
|
|
429
|
+
matching:
|
|
430
|
+
"Safe exact relative declaration target only; no Node conditional selection, wildcard expansion, extension inference or runtime resolution. No exact match is not a package-wide absence claim.",
|
|
431
|
+
traversalNodeLimit: 4096,
|
|
432
|
+
traversalOmitted: targetTraversalOmitted,
|
|
433
|
+
targets: take(targets),
|
|
434
|
+
},
|
|
435
|
+
dependencies: {
|
|
436
|
+
transitive: "not-assessed",
|
|
437
|
+
counting:
|
|
438
|
+
"Exact declared entries per field; overlapping categories are not deduplicated. Invalid and omitted entries remain in declaredCount; boolean bundled declarations have no resolved count.",
|
|
439
|
+
categories: [
|
|
440
|
+
"dependencies",
|
|
441
|
+
"optionalDependencies",
|
|
442
|
+
"peerDependencies",
|
|
443
|
+
"devDependencies",
|
|
444
|
+
"bundledDependencies",
|
|
445
|
+
"bundleDependencies",
|
|
446
|
+
].map((name) =>
|
|
447
|
+
entries(name, name.includes("undle") ? "bundled" : "map"),
|
|
448
|
+
),
|
|
449
|
+
peerMetadata: take([field("peerDependenciesMeta")]),
|
|
450
|
+
comparisons: [dependencyDelta("source"), dependencyDelta("registry")],
|
|
451
|
+
},
|
|
452
|
+
scripts: {
|
|
453
|
+
...entries("scripts"),
|
|
454
|
+
execution: "not-performed",
|
|
455
|
+
bindingGyp: {
|
|
456
|
+
interpretation:
|
|
457
|
+
"Observed binding.gyp file and gypfile declaration only; npm implicit-install behavior is not executed or inferred beyond recorded policy cues.",
|
|
458
|
+
files: take(files.filter((file) => file.path === "binding.gyp")),
|
|
459
|
+
declaration: take([field("gypfile")]),
|
|
460
|
+
recordedCues: take(
|
|
461
|
+
report.policy.findings
|
|
462
|
+
.map((finding, index) => ({
|
|
463
|
+
...finding,
|
|
464
|
+
findingRef: `/policy/findings/${index}`,
|
|
465
|
+
}))
|
|
466
|
+
.filter((finding) => finding.ruleId === "lifecycle-implicit-gyp"),
|
|
467
|
+
),
|
|
468
|
+
},
|
|
469
|
+
},
|
|
470
|
+
inventory: {
|
|
471
|
+
scope: "accepted-artifact-regular-files",
|
|
472
|
+
complete: report.coverage.inventory.complete,
|
|
473
|
+
counts: {
|
|
474
|
+
files: report.coverage.inventory.files,
|
|
475
|
+
bytes: report.coverage.inventory.bytes,
|
|
476
|
+
lowerBound: report.coverage.inventory.lowerBound,
|
|
477
|
+
},
|
|
478
|
+
files: take(files),
|
|
479
|
+
typeGrouping:
|
|
480
|
+
"Lowercase final path extension; (none) includes extensionless and dotfiles. Classification is lexical only.",
|
|
481
|
+
typeGroups: take(
|
|
482
|
+
[...extensions.values()].sort((a, b) =>
|
|
483
|
+
order(a.extension, b.extension),
|
|
484
|
+
),
|
|
485
|
+
),
|
|
486
|
+
topDirectories: take(
|
|
487
|
+
[...topDirectories.values()].sort((a, b) =>
|
|
488
|
+
order(a.directory, b.directory),
|
|
489
|
+
),
|
|
490
|
+
),
|
|
491
|
+
largestFiles: take(
|
|
492
|
+
[...files]
|
|
493
|
+
.sort((a, b) => b.size - a.size || fileOrder(a, b))
|
|
494
|
+
.slice(0, 20),
|
|
495
|
+
),
|
|
496
|
+
sourceOnlyPaths: take([...report.coverage.sourceOnly].sort(order)),
|
|
497
|
+
inertLinks: take(report.coverage.linkEntries),
|
|
498
|
+
linkCounts: {
|
|
499
|
+
artifact: report.coverage.links.artifact,
|
|
500
|
+
source: report.coverage.links.source,
|
|
501
|
+
},
|
|
502
|
+
nonregularCounts: {
|
|
503
|
+
artifact: report.coverage.nonregular.artifact,
|
|
504
|
+
source: report.coverage.nonregular.source,
|
|
505
|
+
},
|
|
506
|
+
},
|
|
507
|
+
capabilities: {
|
|
508
|
+
label: "observed syntax references; execution and bindings unproven",
|
|
509
|
+
referenceFormat:
|
|
510
|
+
"cueRef is a JSON pointer into the raw report; existing cue IDs, when present, are preserved. evidenceIds refer to raw-report evidence IDs.",
|
|
511
|
+
groups: take(
|
|
512
|
+
[...capabilityGroups.values()].sort((a, b) =>
|
|
513
|
+
order(a.ruleId, b.ruleId),
|
|
514
|
+
),
|
|
515
|
+
),
|
|
516
|
+
observations: take(
|
|
517
|
+
observations.map((cue) => ({
|
|
518
|
+
...cue,
|
|
519
|
+
locations: (cue.evidenceIds ?? [])
|
|
520
|
+
.map((id) => evidenceById.get(id))
|
|
521
|
+
.filter(Boolean)
|
|
522
|
+
.map(({ id, side, path, range }) => ({
|
|
523
|
+
evidenceId: id,
|
|
524
|
+
side,
|
|
525
|
+
path,
|
|
526
|
+
range,
|
|
527
|
+
})),
|
|
528
|
+
})),
|
|
529
|
+
),
|
|
530
|
+
},
|
|
531
|
+
provenance: {
|
|
532
|
+
label:
|
|
533
|
+
"report-recorded facts; publisher claims retain their recorded verification status",
|
|
534
|
+
facts: take([
|
|
535
|
+
{ field: "source", value: report.source },
|
|
536
|
+
...["artifact", "sourceArchive", "registryDocument"]
|
|
537
|
+
.filter((name) => report.inputs[name] !== undefined)
|
|
538
|
+
.map((name) => ({ field: name, value: report.inputs[name] })),
|
|
539
|
+
]),
|
|
540
|
+
},
|
|
541
|
+
ai: {
|
|
542
|
+
advisory: true,
|
|
543
|
+
selection: {
|
|
544
|
+
selectedGroups: Array.isArray(report.model.evidenceGroups)
|
|
545
|
+
? report.model.evidenceGroups.length
|
|
546
|
+
: null,
|
|
547
|
+
omittedGroups: Array.isArray(report.model.omittedEvidenceGroups)
|
|
548
|
+
? report.model.omittedEvidenceGroups.length
|
|
549
|
+
: null,
|
|
550
|
+
omittedEvidenceItems: Array.isArray(report.model.omittedEvidenceIds)
|
|
551
|
+
? report.model.omittedEvidenceIds.length
|
|
552
|
+
: null,
|
|
553
|
+
interpretation:
|
|
554
|
+
"Prompt-selected groups as recorded, not full-file review or proof of model attention. Null means no selection count recorded; original modelReviewed counters are unchanged.",
|
|
555
|
+
},
|
|
556
|
+
validation:
|
|
557
|
+
"Upstream model-result validation is preserved; this projection performs no new inference or usefulness assessment.",
|
|
558
|
+
metadata: take([
|
|
559
|
+
Object.fromEntries(
|
|
560
|
+
Object.entries(report.model).filter(([key]) => key !== "hypotheses"),
|
|
561
|
+
),
|
|
562
|
+
]),
|
|
563
|
+
hypotheses: take(
|
|
564
|
+
report.model.status === "complete" &&
|
|
565
|
+
Array.isArray(report.model.hypotheses)
|
|
566
|
+
? report.model.hypotheses
|
|
567
|
+
: [],
|
|
568
|
+
),
|
|
569
|
+
},
|
|
570
|
+
coverage: {
|
|
571
|
+
counters: {
|
|
572
|
+
...Object.fromEntries(
|
|
573
|
+
[
|
|
574
|
+
"inventory",
|
|
575
|
+
"compared",
|
|
576
|
+
"unresolved",
|
|
577
|
+
"syntaxInspected",
|
|
578
|
+
"modelReviewed",
|
|
579
|
+
].map((key) => [
|
|
580
|
+
key,
|
|
581
|
+
{
|
|
582
|
+
files: report.coverage[key].files,
|
|
583
|
+
bytes: report.coverage[key].bytes,
|
|
584
|
+
...(key === "inventory"
|
|
585
|
+
? {
|
|
586
|
+
complete: report.coverage.inventory.complete,
|
|
587
|
+
lowerBound: report.coverage.inventory.lowerBound,
|
|
588
|
+
}
|
|
589
|
+
: {}),
|
|
590
|
+
},
|
|
591
|
+
]),
|
|
592
|
+
),
|
|
593
|
+
complete: report.coverage.complete,
|
|
594
|
+
sourceAvailable: report.coverage.sourceAvailable,
|
|
595
|
+
sourceComplete: report.coverage.sourceComplete,
|
|
596
|
+
states: Object.fromEntries(
|
|
597
|
+
["byte-identical", "changed", "artifact-only", "not-compared"].map(
|
|
598
|
+
(key) => [
|
|
599
|
+
key,
|
|
600
|
+
{
|
|
601
|
+
files: report.coverage.states[key].files,
|
|
602
|
+
bytes: report.coverage.states[key].bytes,
|
|
603
|
+
},
|
|
604
|
+
],
|
|
605
|
+
),
|
|
606
|
+
),
|
|
607
|
+
},
|
|
608
|
+
effectiveLimits: take([
|
|
609
|
+
{
|
|
610
|
+
limits: report.inputs.limits ?? null,
|
|
611
|
+
limitsDigest: report.inputs.limitsDigest ?? null,
|
|
612
|
+
},
|
|
613
|
+
]),
|
|
614
|
+
observations: take(
|
|
615
|
+
report.annotations
|
|
616
|
+
.map((item, index) => ({
|
|
617
|
+
...item,
|
|
618
|
+
annotationRef: `/annotations/${index}`,
|
|
619
|
+
}))
|
|
620
|
+
.filter((item) =>
|
|
621
|
+
["syntax-scope", "syntax-not-inspected", "manifest-scope"].includes(
|
|
622
|
+
item.type,
|
|
623
|
+
),
|
|
624
|
+
),
|
|
625
|
+
),
|
|
626
|
+
failures: take(report.failures),
|
|
627
|
+
policyFindings: take(report.policy.findings),
|
|
628
|
+
},
|
|
629
|
+
projection: {
|
|
630
|
+
limits,
|
|
631
|
+
sorting:
|
|
632
|
+
"UTF-16 code-unit ascending: declaration fields, dependency/script keys, file path then originalPath, extension and ruleId. Dependency categories use the documented fixed field order. Bundled lists sort by JSON name then original index. exports object member order is preserved. Other lists preserve report order.",
|
|
633
|
+
},
|
|
634
|
+
};
|
|
635
|
+
if (Buffer.byteLength(JSON.stringify(result)) > limits.maxTotalBytes)
|
|
636
|
+
throw new TypeError("Package details metadata exceeds response cap");
|
|
637
|
+
return result;
|
|
638
|
+
}
|