@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
package/dist/server/queue.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
boundedVulnerabilityCheck,
|
|
4
4
|
unavailableVulnerabilityCheck,
|
|
5
5
|
} from "./enrichment.mjs";
|
|
6
|
+
import { advisoryOnlyResult } from "./advisory-result.mjs";
|
|
6
7
|
|
|
7
8
|
export const DEFAULT_LIMITS = Object.freeze({
|
|
8
9
|
maxBodyBytes: 4096,
|
|
@@ -57,7 +58,22 @@ export function createQueue({
|
|
|
57
58
|
})
|
|
58
59
|
)
|
|
59
60
|
return;
|
|
60
|
-
|
|
61
|
+
if (
|
|
62
|
+
job.advisoryResult &&
|
|
63
|
+
store.put({
|
|
64
|
+
...job,
|
|
65
|
+
advisoryResult: advisoryOnlyResult(
|
|
66
|
+
unavailableVulnerabilityCheck(
|
|
67
|
+
job.nameAtExactVersion,
|
|
68
|
+
clock,
|
|
69
|
+
"storage-byte-cap",
|
|
70
|
+
),
|
|
71
|
+
{ artifactReason: job.reason },
|
|
72
|
+
),
|
|
73
|
+
})
|
|
74
|
+
)
|
|
75
|
+
return;
|
|
76
|
+
const { report, vulnerabilityCheck, advisoryResult, ...small } = job;
|
|
61
77
|
store.put({ ...small, status: "incomplete", reason: "report-too-large" });
|
|
62
78
|
}
|
|
63
79
|
}
|
|
@@ -71,6 +87,38 @@ export function createQueue({
|
|
|
71
87
|
delete job.queueTimer;
|
|
72
88
|
persist(job);
|
|
73
89
|
}
|
|
90
|
+
async function enrich(job, controller) {
|
|
91
|
+
let abort;
|
|
92
|
+
const interrupted = new Promise((resolve) => {
|
|
93
|
+
abort = () =>
|
|
94
|
+
resolve(
|
|
95
|
+
unavailableVulnerabilityCheck(
|
|
96
|
+
job.nameAtExactVersion,
|
|
97
|
+
clock,
|
|
98
|
+
"aborted",
|
|
99
|
+
),
|
|
100
|
+
);
|
|
101
|
+
controller.signal.addEventListener("abort", abort, { once: true });
|
|
102
|
+
if (controller.signal.aborted) abort();
|
|
103
|
+
});
|
|
104
|
+
try {
|
|
105
|
+
const value = await Promise.race([
|
|
106
|
+
Promise.resolve().then(() =>
|
|
107
|
+
enrichJob(job.nameAtExactVersion, { signal: controller.signal }),
|
|
108
|
+
),
|
|
109
|
+
interrupted,
|
|
110
|
+
]);
|
|
111
|
+
return boundedVulnerabilityCheck(value, job.nameAtExactVersion, clock);
|
|
112
|
+
} catch {
|
|
113
|
+
return unavailableVulnerabilityCheck(
|
|
114
|
+
job.nameAtExactVersion,
|
|
115
|
+
clock,
|
|
116
|
+
"lookup-failed",
|
|
117
|
+
);
|
|
118
|
+
} finally {
|
|
119
|
+
controller.signal.removeEventListener("abort", abort);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
74
122
|
function begin(job) {
|
|
75
123
|
active = job;
|
|
76
124
|
waiting = undefined;
|
|
@@ -79,7 +127,7 @@ export function createQueue({
|
|
|
79
127
|
job.status = "running";
|
|
80
128
|
job.startedAt = clock.now();
|
|
81
129
|
const controller = new AbortController();
|
|
82
|
-
let coreReport;
|
|
130
|
+
let coreReport, advisorySnapshot;
|
|
83
131
|
const timer = clock.setTimeout(() => {
|
|
84
132
|
job.finishedAt = clock.now();
|
|
85
133
|
if (coreReport) {
|
|
@@ -87,16 +135,33 @@ export function createQueue({
|
|
|
87
135
|
persist({
|
|
88
136
|
...publicJob(job),
|
|
89
137
|
report: coreReport,
|
|
90
|
-
vulnerabilityCheck:
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
138
|
+
vulnerabilityCheck:
|
|
139
|
+
advisorySnapshot ??
|
|
140
|
+
unavailableVulnerabilityCheck(
|
|
141
|
+
job.nameAtExactVersion,
|
|
142
|
+
clock,
|
|
143
|
+
"aborted",
|
|
144
|
+
),
|
|
95
145
|
});
|
|
96
146
|
} else {
|
|
97
147
|
job.status = "incomplete";
|
|
98
148
|
job.reason = "execution-timeout";
|
|
99
|
-
persist(
|
|
149
|
+
persist(
|
|
150
|
+
enrichJob
|
|
151
|
+
? {
|
|
152
|
+
...publicJob(job),
|
|
153
|
+
advisoryResult: advisoryOnlyResult(
|
|
154
|
+
advisorySnapshot ??
|
|
155
|
+
unavailableVulnerabilityCheck(
|
|
156
|
+
job.nameAtExactVersion,
|
|
157
|
+
clock,
|
|
158
|
+
"aborted",
|
|
159
|
+
),
|
|
160
|
+
{ artifactReason: job.reason },
|
|
161
|
+
),
|
|
162
|
+
}
|
|
163
|
+
: job,
|
|
164
|
+
);
|
|
100
165
|
}
|
|
101
166
|
controller.abort();
|
|
102
167
|
}, limits.executionMs);
|
|
@@ -105,10 +170,14 @@ export function createQueue({
|
|
|
105
170
|
clock.clearTimeout(timer);
|
|
106
171
|
controller.abort();
|
|
107
172
|
};
|
|
108
|
-
Promise.resolve()
|
|
109
|
-
.
|
|
110
|
-
|
|
111
|
-
|
|
173
|
+
const analysis = Promise.resolve().then(() =>
|
|
174
|
+
runAnalysis(job.nameAtExactVersion, { signal: controller.signal }),
|
|
175
|
+
);
|
|
176
|
+
// The advisory lookup runs beside acquisition so a hung or failed artifact still keeps it.
|
|
177
|
+
const advisory = enrichJob
|
|
178
|
+
? enrich(job, controller).then((check) => (advisorySnapshot = check))
|
|
179
|
+
: Promise.resolve(undefined);
|
|
180
|
+
analysis
|
|
112
181
|
.then(async (report) => {
|
|
113
182
|
if (job.status !== "running" || closed) return;
|
|
114
183
|
if (
|
|
@@ -119,45 +188,7 @@ export function createQueue({
|
|
|
119
188
|
)
|
|
120
189
|
throw new TypeError("Invalid report");
|
|
121
190
|
coreReport = report;
|
|
122
|
-
|
|
123
|
-
if (enrichJob) {
|
|
124
|
-
let abort;
|
|
125
|
-
const interrupted = new Promise((resolve) => {
|
|
126
|
-
abort = () =>
|
|
127
|
-
resolve(
|
|
128
|
-
unavailableVulnerabilityCheck(
|
|
129
|
-
job.nameAtExactVersion,
|
|
130
|
-
clock,
|
|
131
|
-
"aborted",
|
|
132
|
-
),
|
|
133
|
-
);
|
|
134
|
-
controller.signal.addEventListener("abort", abort, { once: true });
|
|
135
|
-
if (controller.signal.aborted) abort();
|
|
136
|
-
});
|
|
137
|
-
try {
|
|
138
|
-
const value = await Promise.race([
|
|
139
|
-
Promise.resolve().then(() =>
|
|
140
|
-
enrichJob(job.nameAtExactVersion, {
|
|
141
|
-
signal: controller.signal,
|
|
142
|
-
}),
|
|
143
|
-
),
|
|
144
|
-
interrupted,
|
|
145
|
-
]);
|
|
146
|
-
vulnerabilityCheck = boundedVulnerabilityCheck(
|
|
147
|
-
value,
|
|
148
|
-
job.nameAtExactVersion,
|
|
149
|
-
clock,
|
|
150
|
-
);
|
|
151
|
-
} catch {
|
|
152
|
-
vulnerabilityCheck = unavailableVulnerabilityCheck(
|
|
153
|
-
job.nameAtExactVersion,
|
|
154
|
-
clock,
|
|
155
|
-
"lookup-failed",
|
|
156
|
-
);
|
|
157
|
-
} finally {
|
|
158
|
-
controller.signal.removeEventListener("abort", abort);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
191
|
+
const vulnerabilityCheck = await advisory;
|
|
161
192
|
if (job.status !== "running" || closed) return;
|
|
162
193
|
const status = report.policy.exitCode === 2 ? "incomplete" : "done";
|
|
163
194
|
const finishedAt = clock.now();
|
|
@@ -171,13 +202,24 @@ export function createQueue({
|
|
|
171
202
|
job.status = status;
|
|
172
203
|
job.finishedAt = finishedAt;
|
|
173
204
|
})
|
|
174
|
-
.catch(() => {
|
|
205
|
+
.catch(async () => {
|
|
206
|
+
if (job.status !== "running" || closed) return;
|
|
207
|
+
const vulnerabilityCheck = coreReport ? undefined : await advisory;
|
|
175
208
|
if (job.status !== "running" || closed) return;
|
|
176
209
|
job.status = "failed";
|
|
177
210
|
job.reason = "analysis-failed";
|
|
178
211
|
job.finishedAt = clock.now();
|
|
179
212
|
delete job.report;
|
|
180
|
-
persist(
|
|
213
|
+
persist({
|
|
214
|
+
...publicJob(job),
|
|
215
|
+
...(vulnerabilityCheck
|
|
216
|
+
? {
|
|
217
|
+
advisoryResult: advisoryOnlyResult(vulnerabilityCheck, {
|
|
218
|
+
artifactReason: job.reason,
|
|
219
|
+
}),
|
|
220
|
+
}
|
|
221
|
+
: {}),
|
|
222
|
+
});
|
|
181
223
|
})
|
|
182
224
|
.finally(() => {
|
|
183
225
|
clock.clearTimeout(timer);
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getRegistryFacts,
|
|
3
|
+
getRegistryHistory,
|
|
4
|
+
registryIdentity,
|
|
5
|
+
} from "../core/registry-facts.mjs";
|
|
6
|
+
|
|
7
|
+
export function createRegistryFactsHandler(options = {}) {
|
|
8
|
+
const maxConcurrent = options.maxConcurrent ?? 2;
|
|
9
|
+
const maxRequests = options.maxRequests ?? 30;
|
|
10
|
+
if (
|
|
11
|
+
!Number.isSafeInteger(maxConcurrent) ||
|
|
12
|
+
maxConcurrent < 1 ||
|
|
13
|
+
maxConcurrent > 2 ||
|
|
14
|
+
!Number.isSafeInteger(maxRequests) ||
|
|
15
|
+
maxRequests < 1 ||
|
|
16
|
+
maxRequests > 30
|
|
17
|
+
)
|
|
18
|
+
throw new TypeError("Invalid registry lookup admission limits");
|
|
19
|
+
let active = 0,
|
|
20
|
+
used = 0,
|
|
21
|
+
windowStart = Date.now();
|
|
22
|
+
return async function handleRegistryFacts(req, json) {
|
|
23
|
+
const url = new URL(req.url, "http://localhost");
|
|
24
|
+
const history = url.pathname === "/api/registry/history";
|
|
25
|
+
if (url.pathname !== "/api/registry/facts" && !history) return false;
|
|
26
|
+
if (req.method !== "GET") {
|
|
27
|
+
json(
|
|
28
|
+
405,
|
|
29
|
+
{ status: "rejected", reason: "method-not-allowed" },
|
|
30
|
+
{ allow: "GET" },
|
|
31
|
+
);
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
const spec = url.searchParams.get("package");
|
|
35
|
+
let limit = 20;
|
|
36
|
+
try {
|
|
37
|
+
const allowed = history ? ["package", "limit"] : ["package"];
|
|
38
|
+
if (
|
|
39
|
+
!spec ||
|
|
40
|
+
[...url.searchParams.keys()].some((key) => !allowed.includes(key)) ||
|
|
41
|
+
allowed.some((key) => url.searchParams.getAll(key).length > 1)
|
|
42
|
+
)
|
|
43
|
+
throw new Error();
|
|
44
|
+
if (history) {
|
|
45
|
+
registryIdentity(`${spec}@0.0.0`);
|
|
46
|
+
if (url.searchParams.has("limit")) {
|
|
47
|
+
const raw = url.searchParams.get("limit");
|
|
48
|
+
if (!/^[1-9]\d{0,2}$/.test(raw)) throw new Error();
|
|
49
|
+
limit = Number(raw);
|
|
50
|
+
if (limit > 100) throw new Error();
|
|
51
|
+
}
|
|
52
|
+
} else registryIdentity(spec);
|
|
53
|
+
} catch {
|
|
54
|
+
json(400, {
|
|
55
|
+
status: "rejected",
|
|
56
|
+
reason: history
|
|
57
|
+
? "package-name-and-bounded-limit-required"
|
|
58
|
+
: "exact-package-required",
|
|
59
|
+
});
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
const now = Date.now();
|
|
63
|
+
if (now - windowStart >= 60000) {
|
|
64
|
+
used = 0;
|
|
65
|
+
windowStart = now;
|
|
66
|
+
}
|
|
67
|
+
if (active >= maxConcurrent || used >= maxRequests) {
|
|
68
|
+
json(
|
|
69
|
+
429,
|
|
70
|
+
{ status: "unavailable", reason: "registry-lookup-limit" },
|
|
71
|
+
{
|
|
72
|
+
"retry-after": String(
|
|
73
|
+
Math.max(1, Math.ceil((60000 - (now - windowStart)) / 1000)),
|
|
74
|
+
),
|
|
75
|
+
},
|
|
76
|
+
);
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
used++;
|
|
80
|
+
active++;
|
|
81
|
+
try {
|
|
82
|
+
json(
|
|
83
|
+
200,
|
|
84
|
+
history
|
|
85
|
+
? await getRegistryHistory(spec, { ...options, limit })
|
|
86
|
+
: await getRegistryFacts(spec, options),
|
|
87
|
+
);
|
|
88
|
+
} catch (error) {
|
|
89
|
+
const reason = [
|
|
90
|
+
"acquisition-timeout",
|
|
91
|
+
"response-cap",
|
|
92
|
+
"redirect-refused",
|
|
93
|
+
"invalid-registry-document",
|
|
94
|
+
"registry-identity-mismatch",
|
|
95
|
+
"http-status",
|
|
96
|
+
"network-failure",
|
|
97
|
+
"invalid-response",
|
|
98
|
+
"registry-history-cap",
|
|
99
|
+
].includes(error.code)
|
|
100
|
+
? error.code
|
|
101
|
+
: "registry-unavailable";
|
|
102
|
+
const status =
|
|
103
|
+
reason === "acquisition-timeout"
|
|
104
|
+
? 504
|
|
105
|
+
: error.observation?.status === 404
|
|
106
|
+
? 404
|
|
107
|
+
: 502;
|
|
108
|
+
json(status, {
|
|
109
|
+
status: "unavailable",
|
|
110
|
+
reason,
|
|
111
|
+
safetyVerdict: "not-provided",
|
|
112
|
+
});
|
|
113
|
+
} finally {
|
|
114
|
+
active--;
|
|
115
|
+
}
|
|
116
|
+
return true;
|
|
117
|
+
};
|
|
118
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildReportFileInventory,
|
|
3
|
+
buildReportFileEvidence,
|
|
4
|
+
explorationJson,
|
|
5
|
+
} from "../core/report-exploration.mjs";
|
|
6
|
+
|
|
7
|
+
function queryParameters(raw, allowed) {
|
|
8
|
+
if (raw.length > 16384) throw new TypeError("Query too long");
|
|
9
|
+
decodeURIComponent(raw.replace(/\+/g, " "));
|
|
10
|
+
const params = new URLSearchParams(raw);
|
|
11
|
+
for (const key of params.keys())
|
|
12
|
+
if (!allowed.includes(key) || params.getAll(key).length !== 1)
|
|
13
|
+
throw new TypeError("Unknown or duplicate query key");
|
|
14
|
+
return params;
|
|
15
|
+
}
|
|
16
|
+
function integer(params, name, fallback) {
|
|
17
|
+
if (!params.has(name)) return fallback;
|
|
18
|
+
const value = params.get(name);
|
|
19
|
+
if (!/^(0|[1-9]\d*)$/.test(value)) throw new TypeError("Invalid integer");
|
|
20
|
+
return Number(value);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function handleReportExploration({ path, getJob, send }) {
|
|
24
|
+
const match = /^\/r\/([a-f0-9]{32})\/(files|file)\.json(?:\?([^#]*))?$/.exec(
|
|
25
|
+
path,
|
|
26
|
+
);
|
|
27
|
+
if (!match) return false;
|
|
28
|
+
const json = (status, body, headers = {}) =>
|
|
29
|
+
send(status, explorationJson(body), "application/json; charset=utf-8", {
|
|
30
|
+
"content-disposition":
|
|
31
|
+
match[2] === "file"
|
|
32
|
+
? 'attachment; filename="unmask-file-evidence.json"'
|
|
33
|
+
: 'attachment; filename="unmask-files.json"',
|
|
34
|
+
...headers,
|
|
35
|
+
});
|
|
36
|
+
const drilldown = match[2] === "file";
|
|
37
|
+
const context = { reportId: match[1] };
|
|
38
|
+
let options;
|
|
39
|
+
try {
|
|
40
|
+
if (drilldown) {
|
|
41
|
+
const params = queryParameters(match[3] ?? "", [
|
|
42
|
+
"path",
|
|
43
|
+
"originalPath",
|
|
44
|
+
"fileId",
|
|
45
|
+
]);
|
|
46
|
+
options = Object.fromEntries(params);
|
|
47
|
+
buildReportFileEvidence({ files: [] }, options, context);
|
|
48
|
+
} else {
|
|
49
|
+
const params = queryParameters(match[3] ?? "", [
|
|
50
|
+
"state",
|
|
51
|
+
"prefix",
|
|
52
|
+
"offset",
|
|
53
|
+
"limit",
|
|
54
|
+
]);
|
|
55
|
+
options = {
|
|
56
|
+
state: params.get("state") ?? "all",
|
|
57
|
+
prefix: params.get("prefix") ?? "",
|
|
58
|
+
offset: integer(params, "offset", 0),
|
|
59
|
+
limit: integer(params, "limit", 50),
|
|
60
|
+
};
|
|
61
|
+
buildReportFileInventory({ files: [] }, options, context);
|
|
62
|
+
}
|
|
63
|
+
} catch {
|
|
64
|
+
json(400, { error: "invalid-query" });
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
const job = getJob(match[1]);
|
|
68
|
+
if (!job) json(404, { error: "report-unavailable" });
|
|
69
|
+
else if (!job.report) {
|
|
70
|
+
const pending = ["queued", "running"].includes(job.status);
|
|
71
|
+
json(
|
|
72
|
+
pending ? 202 : 409,
|
|
73
|
+
{
|
|
74
|
+
error: pending ? "report-pending" : "report-unavailable",
|
|
75
|
+
status: job.status,
|
|
76
|
+
},
|
|
77
|
+
pending ? { "retry-after": "3" } : {},
|
|
78
|
+
);
|
|
79
|
+
} else {
|
|
80
|
+
const body = drilldown
|
|
81
|
+
? buildReportFileEvidence(job.report, options, context)
|
|
82
|
+
: buildReportFileInventory(job.report, options, context);
|
|
83
|
+
if (!body) json(404, { error: "file-unavailable" });
|
|
84
|
+
else json(200, { reportId: match[1], ...body });
|
|
85
|
+
}
|
|
86
|
+
return true;
|
|
87
|
+
}
|
package/dist/server/store.mjs
CHANGED
|
@@ -8,6 +8,14 @@ const deterministicCaps = new Set([
|
|
|
8
8
|
"path-node-cap",
|
|
9
9
|
"metadata-cap",
|
|
10
10
|
]);
|
|
11
|
+
const deterministicAdvisoryCaps = new Set([
|
|
12
|
+
"result-byte-cap",
|
|
13
|
+
"storage-byte-cap",
|
|
14
|
+
"response-limit",
|
|
15
|
+
"page-limit",
|
|
16
|
+
"advisory-limit",
|
|
17
|
+
"record-limit",
|
|
18
|
+
]);
|
|
11
19
|
const transientModelStatuses = new Set([
|
|
12
20
|
"unavailable",
|
|
13
21
|
"timeout",
|
|
@@ -17,7 +25,19 @@ const transientModelStatuses = new Set([
|
|
|
17
25
|
]);
|
|
18
26
|
|
|
19
27
|
function reusable(record) {
|
|
20
|
-
|
|
28
|
+
// A transiently incomplete advisory snapshot stays readable by id but must not answer a resubmission; repeatable caps may.
|
|
29
|
+
if (
|
|
30
|
+
!record.report ||
|
|
31
|
+
transientModelStatuses.has(record.report.model?.status) ||
|
|
32
|
+
(record.vulnerabilityCheck !== undefined &&
|
|
33
|
+
record.vulnerabilityCheck?.complete !== true &&
|
|
34
|
+
!(
|
|
35
|
+
record.vulnerabilityCheck?.reasons?.length > 0 &&
|
|
36
|
+
record.vulnerabilityCheck.reasons.every((reason) =>
|
|
37
|
+
deterministicAdvisoryCaps.has(reason),
|
|
38
|
+
)
|
|
39
|
+
))
|
|
40
|
+
)
|
|
21
41
|
return false;
|
|
22
42
|
if (record.status === "done") return record.report.policy?.exitCode !== 2;
|
|
23
43
|
return (
|
package/dist/version.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"buildSchema":"1","version":"0.1.
|
|
1
|
+
{"buildSchema":"1","version":"0.1.4","dirty":false,"commit":"99771a0fe5b9df4d0d98f5e7d7f56cad11e2a005","sourceSha256":"48f4fc48bcce841253639ff72bb90557d9284155d4aa8ed5d0d25c03a5904ea4","packageSha256":"d3b26f02f381b527f441b4bd526d93992b618a05f3e59767449613d871a396c9","lockfileSha256":"d7c23757a0a9662a41414f1a7b026f838b883d313765f05b9a1a151ce62f9000","files":{"cli/annotations.mjs":{"sha256":"9dd89e6bfcb44aa4f24e71dc2c85418093ad19e8824a467c21fe2208aceae260","size":5179},"cli/audit.mjs":{"sha256":"bf3d7d0bb77a5b5baaee36d7c8363f3d29c00eda3bb105da29d5be0dd3c28cfd","size":8047},"cli/batch.mjs":{"sha256":"101f7ecb4c3d5890c42f402e5ca00ab97351c4a4d90e07a1fa4367e3c0807650","size":7296},"cli/cache.mjs":{"sha256":"ce7f608249ac61082f408239187a9e87176d82c3a4f288a933eb6a0f7a29fd3c","size":15783},"cli/compare.mjs":{"sha256":"30870240034847064c7d7fdf6e1de833cc73c7b70ebdc56d2e7fa90c62eefb18","size":9569},"cli/config.mjs":{"sha256":"845767f86dbe1e909fbb02684bdec8588dc7e85e30e1131938ee64865c6e0206","size":4436},"cli/discovery.mjs":{"sha256":"182f3c659cb042025d1bf5ca3c05e39895416706c72e3b1537e7052a2fda71a9","size":11982},"cli/doctor.mjs":{"sha256":"a6f6441cefea069f8f453afea7af48a45ce29c28545e233db30839fefeb15237","size":11007},"cli/exceptions.mjs":{"sha256":"246d70c8675182688f971fb29ede6b5511feb8b6462fe136efee8706382786da","size":6133},"cli/file.mjs":{"sha256":"6cdc85178af71ea11b1a82fb20ca4dd5525f1e1959e6ad97fdf3cea1a9074aad","size":9002},"cli/files.mjs":{"sha256":"16ae0daec2157dda4ee7989db0d0fb98f97fee8873e3ca588e456c42f16390eb","size":5046},"cli/history.mjs":{"sha256":"be2dabf364486b1e484c22354ceb10e30cbb983772d8b555ccc498ab305ec5d2","size":8260},"cli/hosted.mjs":{"sha256":"eb30b1ca566c8164a3b1ae3e21eaf45e41445fa209cd8eef273f844065bec510","size":15100},"cli/info.mjs":{"sha256":"15d359aa45b4a8dd3bddc923c42ef1ed25b9a052a317d9da04628944ab3be929","size":4392},"cli/init.mjs":{"sha256":"09d6eb86046b2832687f6eafd85cf2f1472a4ceb399eece9435a166ea525b6bb","size":1955},"cli/install-guard.mjs":{"sha256":"b5e532e3709d9c9d6c990e116bde9d62464f24c44b43ba5da4a5236076a01003","size":29538},"cli/lockfile.mjs":{"sha256":"9165572bd853dcdb30c7dc1efcde61874784218300b0b9f501a6175795fc4324","size":11452},"cli/main.mjs":{"sha256":"e99f78c25bb5a2abb0f989f076fc3f3fa064e87f5ce6b8ec593179a521015192","size":10436},"cli/preflight.mjs":{"sha256":"801bf1aa12b2b32cccda64f01fc8bbc63e977083130c6657849bd1907c0ee137","size":13308},"cli/sarif.mjs":{"sha256":"7fc60dcef01be06ad79393a8c52dca0af9f24bc2969a410bedf731ab9fbbb4e1","size":3147},"cli/scan.mjs":{"sha256":"9bf33e926ededf1beb1e4af73c861ba5bf33bee74ca0544151112284e0afc1b5","size":3906},"cli/shell-integration.mjs":{"sha256":"76a1714cbd128d559c2c15e051220a4e37c4860fe3b0687e615498c8004a0ed8","size":4287},"cli/terminal.mjs":{"sha256":"730ceb8c2e8d2948afb67b0a79fc804849a5eb52d9ddc6ef7a6db65404119471","size":14324},"cli/watch.mjs":{"sha256":"206fdcd262dc6962fd19adfb2bf8cc1a951ec98219cd7901f60f3ee8fd8aa7fa","size":7184},"cli/why.mjs":{"sha256":"9c287b22ddd0e0ae44d84297d3b5c759c25db116370f8a5878e0f5a2eb621ad8","size":8306},"core/acquire.mjs":{"sha256":"aee5ed002292edf1b03385ceec8ebfb03de452dba36bbdced96f888eaafa1d67","size":9549},"core/advisories.mjs":{"sha256":"d0dbf9d9ed4a79655da9613c7f8cbca80e3f2de54c47b128d1dba3b67a2ac750","size":15537},"core/advisory-cache.mjs":{"sha256":"88808779b455e8d28708ec8afc11bb441a85df2783e224726920f20387eede64","size":8720},"core/archive.mjs":{"sha256":"80aa4373c0324458fe322a83af18a4cfd0ffec4b39ebdeffc9084639a8655410","size":16193},"core/behavior-signals.mjs":{"sha256":"9a119e620c9b5868b33a91da72f22b8462fb74313871ce7d99c43a55cae656dd","size":33504},"core/compare.mjs":{"sha256":"540614266e06a2875010793d036d256ddfac72f978157cd3d147ffb99015d8e4","size":7579},"core/dependency-graph.mjs":{"sha256":"25ab2d6180750ff63717e6e3d1ff5284c1c8a5a2aee4ab373ebd6812e3559225","size":10591},"core/evidence-exports.mjs":{"sha256":"0d8c77b7afdee20ba381c77c94745d76edb7b4e8c1c5e6cd12843558ad9334d7","size":15923},"core/guard-config.mjs":{"sha256":"bb1f1f0df2fe374aacb6b90e3c051a89f7e32c61e8f742307dc7a31b6b3ca7ec","size":14884},"core/guard-policy.mjs":{"sha256":"0c5b356da66f30c2b8f2cb53d3141035f50c4631b4ef032464e455aa7c4e821c","size":10435},"core/manifests.mjs":{"sha256":"29c40e01ff1e1dfaed444600d8c78380d23d4bc9be71960251e39ea3594de2f1","size":17891},"core/online.mjs":{"sha256":"368a3f2558e16db0701d2eaf068b48e2661e7e01ab7cf5605cdd22766aeb5cca","size":9741},"core/package-details.mjs":{"sha256":"eac0b19390caaf0c7cc56b1379fd7d64ecd1b3efd6f2d7414065f4c3a80985aa","size":22611},"core/pipeline.mjs":{"sha256":"9c4ddc5c258a99455a1230c978fd132c135057ad7bb6c7baa201ae530ee136fd","size":11060},"core/policy.mjs":{"sha256":"4281c626c00a6424992f5e1eba26876e2b70f4b968450a8ca52fa42f5a121ba5","size":2954},"core/registry-facts.mjs":{"sha256":"726344597044b0c23fb74aff4fbfe7c60cbbfc4c0bee838abf9257134f9c96db","size":11013},"core/report-exploration.mjs":{"sha256":"1dcc9897d3bbc98ee3107ef8f8279d9eed6ee6d104455eb214a5e5644bd197fb","size":10137},"core/report.mjs":{"sha256":"45a83f45307cabf7067496784ad63f2f913075cf27193b4022ac501794d8fe39","size":16119},"core/resolve.mjs":{"sha256":"d5575da49ff78e8fea8df1f2fc10a39540adea285eddfa347746e848f9fdf957","size":5202},"core/sarif.mjs":{"sha256":"dd3d4ebcc3b3700052983573956372db0876422e0813e1ea9c270aca916ad775","size":18424},"core/source.mjs":{"sha256":"798fe74e3fba836b82576cd46e1b91e6f6e0a4e3cf273c533b5e074247c81373","size":3238},"core/syntax.mjs":{"sha256":"e09d4b62101a006876761410e042369fe4d82633da4cb229172afbfa94ab3c64","size":19841},"model/client.mjs":{"sha256":"af3acdfb0dfaf16f1455e025e779e8380cb416a577dc1b04d6d06fdee5df9494","size":47819},"model/config.mjs":{"sha256":"127605979a299616be15f4a47e94dcd82928b00ad2af71744ffaa6e359516486","size":3245},"model/diff-evidence.mjs":{"sha256":"64c19e53e2e2cb72c4ee0807ed3ed2fb929e75d5359dc16fbfd60454872403c9","size":5763},"model/summary.mjs":{"sha256":"0f950a699e5db898482fe6e220955c8dcda68786976d9a68eba2a7209aa22d62","size":7267},"server/advisory-result.mjs":{"sha256":"e91ef844a3fb319631905cd343f77f0a1c087f63cd38e262dd7257c4a410841a","size":4774},"server/app.mjs":{"sha256":"9915d70330106bd4a713bf002ea9b57e368df0ae35dccca76b15dd8765595bc2","size":12900},"server/dependency-screening.mjs":{"sha256":"18c77b1933f79bb287620342ac7c4995351da6ddb3b457bc8fd5fc374e2647a6","size":12430},"server/enrichment.mjs":{"sha256":"3c5cc57f2c8f019f79418457f366a9422e0a03a8ba86a41b0767aba636bde179","size":3825},"server/exports.mjs":{"sha256":"5b4e9b4206d57ac2970dbf3a29d35cad8e0acfd2995fb23fd9561bf347f5a926","size":1847},"server/guard-decision.mjs":{"sha256":"1c886f7bde97bbccad18f1f2c619579c16ca139c112520d2a29dec08bd64a7e5","size":1097},"server/main.mjs":{"sha256":"1823983aff551478c44e505931c9983a85efd6a74876ef17dd2b1d2bd9267354","size":5342},"server/queue.mjs":{"sha256":"09ae8ff8eda7071ae3ba3004d8cefebd2cd84ceef3320fc9b7cf8a75d8e2b5d4","size":8715},"server/registry-facts.mjs":{"sha256":"58bce5b2a40f25e7561f122ea012594c9eb6789f993ca83dadc07f198afca9e3","size":3315},"server/report-exploration.mjs":{"sha256":"862b8d0ea07b627368aaca3a0f66359cfc638b14c54ab4158f8ef313602c3810","size":2774},"server/runner.mjs":{"sha256":"a6ca5bfbd7b239f7110639d854ca9a263f4cf3aaf06701ea54493b29f8244706","size":9071},"server/store.mjs":{"sha256":"51e5ca409acd67945be232f7fe156c17eea79f4f24860c48a9c9b3e08d97dbfc","size":3309},"server/worker.mjs":{"sha256":"1df839608cd1c691fc2801d89e698daeef3e48d7f4b8843ce1252a21fde9730a","size":1902},"viewer/render.mjs":{"sha256":"cf6e3daa572d4b001257c25e6e68dfd5420e8e6f39edfe2fb73ca7219671e7f3","size":20366},"viewer/shell.mjs":{"sha256":"037ebbeec3e73e962252acb2cea6eef0056cd3c6be7a56a78c082086eabe1d9f","size":9629}}}
|
package/dist/viewer/render.mjs
CHANGED
|
@@ -237,7 +237,15 @@ export function renderReport(report) {
|
|
|
237
237
|
const modelText =
|
|
238
238
|
model.status === "disabled"
|
|
239
239
|
? "Model is disabled"
|
|
240
|
-
: `Model status: ${model.status}`;
|
|
240
|
+
: `Model status: ${model.status}${model.reason ? ` (${model.reason})` : ""}`;
|
|
241
|
+
const assessed =
|
|
242
|
+
model.status === "complete" &&
|
|
243
|
+
typeof model.summary === "string" &&
|
|
244
|
+
model.assessment?.source === "model";
|
|
245
|
+
const scope = assessed ? model.assessment.checkedScope || {} : {};
|
|
246
|
+
const assessment = assessed
|
|
247
|
+
? `<h3>AI summary</h3><p>${text(model.summary, 600)}</p><dl><dt>Uncertainty</dt><dd>${text(model.assessment.uncertainty, 300)}</dd><dt>Next action</dt><dd>${text(model.assessment.nextAction, 200)}</dd><dt>Checked scope</dt><dd>${text(scope.evidenceSupplied ?? 0)} evidence items supplied, ${text(scope.evidenceOmitted ?? 0)} omitted. Not checked: ${text((scope.notChecked || []).join(", ") || "unspecified")}.</dd></dl><p class="note">The AI summary is advisory and is not a safety signal.</p>`
|
|
248
|
+
: '<p class="note">No AI summary is available for this check. This notice is not model output and is not a safety signal.</p>';
|
|
241
249
|
const csp = `default-src 'none'; script-src 'sha256-${assetHash(script)}'; style-src 'sha256-${assetHash(css)}'; connect-src 'none'; base-uri 'none'; form-action 'none'`;
|
|
242
250
|
return `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta http-equiv="Content-Security-Policy" content="${escape(csp)}"><meta name="referrer" content="no-referrer"><meta name="robots" content="noindex"><title>UNMASK · ${text(identity)}</title><style>${css}</style></head><body>
|
|
243
251
|
<a class="skip" href="#content">Skip to evidence</a><header><strong>UNMASK</strong><p>Published bytes. Source claims. Evidence to review.</p></header>
|
|
@@ -250,6 +258,6 @@ export function renderReport(report) {
|
|
|
250
258
|
<section><h2>Inert links</h2><p class="note">Artifact links: ${text(report.coverage?.links?.artifact ?? "unknown")}. Source links: ${text(report.coverage?.links?.source ?? "unknown")}.</p>${links.html || '<p class="empty">No inert link entries recorded.</p>'}</section>
|
|
251
259
|
<section><h2>Source-only paths</h2>${sourceOnly.html ? `<ul>${sourceOnly.body}</ul>${sourceOnly.notice}` : '<p class="empty">No source-only paths recorded. Source availability and completeness still apply.</p>'}</section>
|
|
252
260
|
<section><h2>Bound evidence</h2><p class="note">Excerpts show original text data. Excerpt display limit: 512 bytes per evidence item. Byte ranges are half-open: start included, end excluded. Open a citation to inspect its digest-bound evidence.</p>${catalog.html || '<p class="empty">No bound evidence recorded.</p>'}</section>
|
|
253
|
-
<section class="model"><h2>Model explanations</h2><p>${text(modelText)}</p
|
|
261
|
+
<section class="model"><h2>Model explanations</h2><p>${text(modelText)}</p>${assessment}<p class="note">Hypotheses are separate from deterministic evidence. A valid citation proves its location, not the reasoning.</p>${hypotheses.html}</section>
|
|
254
262
|
</div></main><footer>UNMASK presents evidence for review. It never declares a package safe. HTML display limits do not change the full JSON report.</footer><script>${script}</script></body></html>`;
|
|
255
263
|
}
|
package/dist/viewer/shell.mjs
CHANGED
|
@@ -58,7 +58,7 @@ export function renderError({
|
|
|
58
58
|
}
|
|
59
59
|
export function renderCompleted(reportHtml, job) {
|
|
60
60
|
const time = new Date(job.finishedAt).toISOString();
|
|
61
|
-
const navigation = `<nav class="summary" aria-label="Report actions"><a href="/">Check another release</a> | <a href="/r/${escape(job.id)}.json" download="unmask-report.json">Download report JSON</a><p>Cached analysis: <time datetime="${time}">${time}</time>. Eligible results may be reused for up to 30 minutes after completion. Capacity eviction or restart can remove them sooner. Viewing a result does not extend this period. Unavailable model explanations and other retryable failures need a new attempt.</p></nav>`;
|
|
61
|
+
const navigation = `<nav class="summary" aria-label="Report actions"><a href="/">Check another release</a> | <a href="/r/${escape(job.id)}.json" download="unmask-report.json">Download report JSON</a> | <a href="/r/${escape(job.id)}.cdx.json" download="unmask-sbom.cdx.json">Download CycloneDX SBOM</a> | <a href="/r/${escape(job.id)}.evidence.json" download="unmask-evidence-manifest.json">Download evidence manifest</a><p>Cached analysis: <time datetime="${time}">${time}</time>. Eligible results may be reused for up to 30 minutes after completion. Capacity eviction or restart can remove them sooner. Viewing a result does not extend this period. Unavailable model explanations and other retryable failures need a new attempt.</p></nav>`;
|
|
62
62
|
if (/<\/header\s*>/i.test(reportHtml))
|
|
63
63
|
return reportHtml.replace(
|
|
64
64
|
/<\/header\s*>/i,
|