@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/cli/hosted.mjs
CHANGED
|
@@ -3,7 +3,8 @@ import { parsePackageSpec } from "../core/acquire.mjs";
|
|
|
3
3
|
import { LocalInputError } from "../core/pipeline.mjs";
|
|
4
4
|
import { validateReport } from "../core/report.mjs";
|
|
5
5
|
|
|
6
|
-
const DEFAULT_SERVER_URL =
|
|
6
|
+
export const DEFAULT_SERVER_URL =
|
|
7
|
+
"https://unmask-386156642190.asia-south1.run.app";
|
|
7
8
|
const defaults = Object.freeze({
|
|
8
9
|
totalTimeoutMs: 600000,
|
|
9
10
|
requestTimeoutMs: 15000,
|
|
@@ -11,7 +12,7 @@ const defaults = Object.freeze({
|
|
|
11
12
|
pollMs: 3000,
|
|
12
13
|
});
|
|
13
14
|
const failure = (code, message) => new LocalInputError(code, message);
|
|
14
|
-
function serverOrigin(value) {
|
|
15
|
+
export function serverOrigin(value) {
|
|
15
16
|
if (!value)
|
|
16
17
|
throw failure(
|
|
17
18
|
"hosted-configuration",
|
|
@@ -88,17 +89,30 @@ async function readJson(response, maxBytes) {
|
|
|
88
89
|
throw failure("hosted-response", "The hosted response is not valid JSON.");
|
|
89
90
|
}
|
|
90
91
|
}
|
|
91
|
-
|
|
92
|
+
async function runHosted(
|
|
92
93
|
spec,
|
|
93
94
|
{
|
|
94
95
|
serverUrl = DEFAULT_SERVER_URL,
|
|
95
96
|
fetchImpl = globalThis.fetch,
|
|
96
97
|
signal,
|
|
97
98
|
limits: overrides = {},
|
|
99
|
+
existingReportId,
|
|
100
|
+
optionalDetails = false,
|
|
101
|
+
onStatus,
|
|
98
102
|
} = {},
|
|
103
|
+
withDetails = false,
|
|
99
104
|
) {
|
|
100
105
|
const claim = parsePackageSpec(spec);
|
|
101
106
|
const origin = serverOrigin(serverUrl);
|
|
107
|
+
if (
|
|
108
|
+
existingReportId !== undefined &&
|
|
109
|
+
(typeof existingReportId !== "string" ||
|
|
110
|
+
!/^[a-f0-9]{32}$/.test(existingReportId))
|
|
111
|
+
)
|
|
112
|
+
throw failure(
|
|
113
|
+
"hosted-configuration",
|
|
114
|
+
"Use a recorded 32-character lowercase hexadecimal report ID.",
|
|
115
|
+
);
|
|
102
116
|
const limits = { ...defaults, ...overrides };
|
|
103
117
|
if (
|
|
104
118
|
Object.keys(limits).some(
|
|
@@ -147,46 +161,54 @@ export async function checkHosted(
|
|
|
147
161
|
if (response) await discard(response);
|
|
148
162
|
}
|
|
149
163
|
}
|
|
164
|
+
const status = (phase) => {
|
|
165
|
+
try {
|
|
166
|
+
onStatus?.(phase);
|
|
167
|
+
} catch {}
|
|
168
|
+
};
|
|
150
169
|
try {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
{
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
170
|
+
if (!existingReportId) status("submitting");
|
|
171
|
+
const reportUrl = existingReportId
|
|
172
|
+
? `${origin}/r/${existingReportId}.json`
|
|
173
|
+
: await request(
|
|
174
|
+
origin + "/check",
|
|
175
|
+
{
|
|
176
|
+
method: "POST",
|
|
177
|
+
headers: {
|
|
178
|
+
"content-type": "application/x-www-form-urlencoded",
|
|
179
|
+
accept: "application/json",
|
|
180
|
+
},
|
|
181
|
+
body: new URLSearchParams({ package: spec }),
|
|
182
|
+
},
|
|
183
|
+
async (response) => {
|
|
184
|
+
if (response.status !== 303)
|
|
185
|
+
throw failure(
|
|
186
|
+
"hosted-submit-failed",
|
|
187
|
+
`The hosted service did not accept the check (HTTP ${response.status}). No local fallback was attempted.`,
|
|
188
|
+
);
|
|
189
|
+
const location = response.headers.get("location");
|
|
190
|
+
let url;
|
|
191
|
+
try {
|
|
192
|
+
url = new URL(location, origin);
|
|
193
|
+
} catch {}
|
|
194
|
+
if (
|
|
195
|
+
!location ||
|
|
196
|
+
location.length > 2048 ||
|
|
197
|
+
!url ||
|
|
198
|
+
url.origin !== origin ||
|
|
199
|
+
url.username ||
|
|
200
|
+
url.password ||
|
|
201
|
+
url.search ||
|
|
202
|
+
url.hash ||
|
|
203
|
+
!/^\/r\/[a-f0-9]{32}$/.test(url.pathname)
|
|
204
|
+
)
|
|
205
|
+
throw failure(
|
|
206
|
+
"hosted-response",
|
|
207
|
+
"The hosted service returned an invalid report location.",
|
|
208
|
+
);
|
|
209
|
+
return url.href + ".json";
|
|
210
|
+
},
|
|
211
|
+
);
|
|
190
212
|
const id = new URL(reportUrl).pathname.slice(3, -5);
|
|
191
213
|
for (;;) {
|
|
192
214
|
const result = await request(
|
|
@@ -208,8 +230,14 @@ export async function checkHosted(
|
|
|
208
230
|
"hosted-response",
|
|
209
231
|
"The hosted service returned an invalid pending report.",
|
|
210
232
|
);
|
|
233
|
+
status(value.status);
|
|
211
234
|
return null;
|
|
212
235
|
}
|
|
236
|
+
if (
|
|
237
|
+
["failed", "incomplete"].includes(value?.status) &&
|
|
238
|
+
value.id === id
|
|
239
|
+
)
|
|
240
|
+
throw advisoryOnly(value.advisoryResult, claim);
|
|
213
241
|
try {
|
|
214
242
|
validateReport(value);
|
|
215
243
|
if (
|
|
@@ -227,7 +255,34 @@ export async function checkHosted(
|
|
|
227
255
|
return value;
|
|
228
256
|
},
|
|
229
257
|
);
|
|
230
|
-
if (result)
|
|
258
|
+
if (result) {
|
|
259
|
+
if (!withDetails) return result;
|
|
260
|
+
status("details");
|
|
261
|
+
const detailsUrl = reportUrl.replace(/\.json$/, ".details.json");
|
|
262
|
+
const fetchDetails = () =>
|
|
263
|
+
request(
|
|
264
|
+
detailsUrl,
|
|
265
|
+
{ headers: { accept: "application/json" } },
|
|
266
|
+
async (response) => {
|
|
267
|
+
if (response.status !== 200)
|
|
268
|
+
throw failure(
|
|
269
|
+
"hosted-details-failed",
|
|
270
|
+
`Hosted package details are unavailable (HTTP ${response.status}).`,
|
|
271
|
+
);
|
|
272
|
+
const value = await readJson(
|
|
273
|
+
response,
|
|
274
|
+
Math.min(limits.maxBodyBytes, 4194304),
|
|
275
|
+
);
|
|
276
|
+
validateDetails(value, result);
|
|
277
|
+
return value;
|
|
278
|
+
},
|
|
279
|
+
);
|
|
280
|
+
// Check summaries keep a completed report when only details fail.
|
|
281
|
+
const details = optionalDetails
|
|
282
|
+
? await fetchDetails().catch(() => null)
|
|
283
|
+
: await fetchDetails();
|
|
284
|
+
return { report: result, details, reportUrl, detailsUrl };
|
|
285
|
+
}
|
|
231
286
|
await delay(limits.pollMs, undefined, { signal: lifetime });
|
|
232
287
|
}
|
|
233
288
|
} catch (error) {
|
|
@@ -249,3 +304,156 @@ export async function checkHosted(
|
|
|
249
304
|
clearTimeout(totalTimer);
|
|
250
305
|
}
|
|
251
306
|
}
|
|
307
|
+
|
|
308
|
+
function validateDetails(value, report) {
|
|
309
|
+
const list = (entry) =>
|
|
310
|
+
entry &&
|
|
311
|
+
Array.isArray(entry.items) &&
|
|
312
|
+
entry.items.length <= 20000 &&
|
|
313
|
+
entry.items.every(
|
|
314
|
+
(item) =>
|
|
315
|
+
item !== null && typeof item === "object" && !Array.isArray(item),
|
|
316
|
+
) &&
|
|
317
|
+
["total", "returned", "omitted"].every(
|
|
318
|
+
(key) => Number.isSafeInteger(entry[key]) && entry[key] >= 0,
|
|
319
|
+
) &&
|
|
320
|
+
entry.returned === entry.items.length &&
|
|
321
|
+
entry.total === entry.returned + entry.omitted;
|
|
322
|
+
const identity = value?.identity?.items?.[0];
|
|
323
|
+
if (
|
|
324
|
+
value?.detailsVersion !== "1" ||
|
|
325
|
+
value?.reportSchemaVersion !== report.schemaVersion ||
|
|
326
|
+
!list(value.identity) ||
|
|
327
|
+
identity?.requested?.name !== report.inputs.claim.name ||
|
|
328
|
+
identity?.requested?.version !== report.inputs.claim.version ||
|
|
329
|
+
identity?.artifactDigests?.sha256 !== report.inputs.artifact.sha256 ||
|
|
330
|
+
!list(value.declarations?.fields) ||
|
|
331
|
+
!list(value.scripts?.entries) ||
|
|
332
|
+
!Array.isArray(value.dependencies?.categories) ||
|
|
333
|
+
value.dependencies.categories.length > 6 ||
|
|
334
|
+
value.dependencies.categories.some(
|
|
335
|
+
(category) => !list(category?.entries),
|
|
336
|
+
) ||
|
|
337
|
+
!list(value.provenance?.facts) ||
|
|
338
|
+
!list(value.inventory?.files)
|
|
339
|
+
)
|
|
340
|
+
throw failure(
|
|
341
|
+
"hosted-details-invalid",
|
|
342
|
+
"Hosted details failed version, identity or inventory validation.",
|
|
343
|
+
);
|
|
344
|
+
const check = value.vulnerabilityCheck;
|
|
345
|
+
if (
|
|
346
|
+
check &&
|
|
347
|
+
(check.source !== "OSV" ||
|
|
348
|
+
![
|
|
349
|
+
"found",
|
|
350
|
+
"none-reported",
|
|
351
|
+
"incomplete",
|
|
352
|
+
"unavailable",
|
|
353
|
+
"not-assessed",
|
|
354
|
+
].includes(check.status) ||
|
|
355
|
+
typeof check.complete !== "boolean" ||
|
|
356
|
+
!Array.isArray(check.advisories) ||
|
|
357
|
+
check.complete !== ["found", "none-reported"].includes(check.status) ||
|
|
358
|
+
(check.status === "found" && check.advisories.length === 0) ||
|
|
359
|
+
(check.status === "none-reported" && check.advisories.length !== 0) ||
|
|
360
|
+
(check.package &&
|
|
361
|
+
(check.package.ecosystem !== "npm" ||
|
|
362
|
+
check.package.name !== report.inputs.claim.name ||
|
|
363
|
+
check.package.version !== report.inputs.claim.version)) ||
|
|
364
|
+
(check.complete &&
|
|
365
|
+
(!check.package || !["found", "none-reported"].includes(check.status))))
|
|
366
|
+
)
|
|
367
|
+
throw failure(
|
|
368
|
+
"hosted-details-invalid",
|
|
369
|
+
"Hosted OSV details failed status or identity validation.",
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
const ADVISORY_ASSESSMENTS = {
|
|
374
|
+
found: "advisories-reported",
|
|
375
|
+
"none-reported": "no-advisory-reported",
|
|
376
|
+
incomplete: "advisory-lookup-incomplete",
|
|
377
|
+
unavailable: "advisory-lookup-unavailable",
|
|
378
|
+
};
|
|
379
|
+
const bounded = (value, max, optional = false) =>
|
|
380
|
+
(optional && value === undefined) ||
|
|
381
|
+
(typeof value === "string" && Buffer.byteLength(value) <= max);
|
|
382
|
+
const strings = (value, count, max) =>
|
|
383
|
+
Array.isArray(value) &&
|
|
384
|
+
value.length <= count &&
|
|
385
|
+
value.every((item) => bounded(item, max));
|
|
386
|
+
function advisoryRecord(item) {
|
|
387
|
+
return (
|
|
388
|
+
item !== null &&
|
|
389
|
+
typeof item === "object" &&
|
|
390
|
+
!Array.isArray(item) &&
|
|
391
|
+
typeof item.id === "string" &&
|
|
392
|
+
/^[a-zA-Z0-9:_.-]{1,256}$/.test(item.id) &&
|
|
393
|
+
bounded(item.summary, 2048, true) &&
|
|
394
|
+
bounded(item.databaseSeverity, 64, true) &&
|
|
395
|
+
(item.aliases === undefined || strings(item.aliases, 16, 256))
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
function advisoryOnly(result, claim) {
|
|
399
|
+
const check = result?.vulnerabilityCheck;
|
|
400
|
+
const exact = (item) =>
|
|
401
|
+
item?.ecosystem === "npm" &&
|
|
402
|
+
item.name === claim.name &&
|
|
403
|
+
item.version === claim.version;
|
|
404
|
+
const complete = ["found", "none-reported"].includes(check?.status);
|
|
405
|
+
if (
|
|
406
|
+
result?.schemaVersion !== "1" ||
|
|
407
|
+
result.kind !== "advisory-only" ||
|
|
408
|
+
result.safetyVerdict !== "not-provided" ||
|
|
409
|
+
result.artifactVerification?.status !== "not-performed" ||
|
|
410
|
+
result.sourceComparison?.status !== "not-performed" ||
|
|
411
|
+
!bounded(result.artifactVerification.reason, 128, true) ||
|
|
412
|
+
!bounded(result.notice, 1024) ||
|
|
413
|
+
!exact(result.package) ||
|
|
414
|
+
!exact(check?.package) ||
|
|
415
|
+
check.schemaVersion !== "1" ||
|
|
416
|
+
check.source !== "OSV" ||
|
|
417
|
+
!bounded(check.queriedAt, 64) ||
|
|
418
|
+
!Number.isFinite(Date.parse(check.queriedAt)) ||
|
|
419
|
+
!Number.isSafeInteger(check.pagesFetched) ||
|
|
420
|
+
check.pagesFetched < 0 ||
|
|
421
|
+
check.pagesFetched > 100 ||
|
|
422
|
+
(complete && (check.pagesFetched === 0 || check.reasons.length !== 0)) ||
|
|
423
|
+
(!complete && check.reasons.length === 0) ||
|
|
424
|
+
!Object.hasOwn(ADVISORY_ASSESSMENTS, check.status) ||
|
|
425
|
+
result.assessment !== ADVISORY_ASSESSMENTS[check.status] ||
|
|
426
|
+
check.complete !== complete ||
|
|
427
|
+
!strings(check.reasons, 16, 128) ||
|
|
428
|
+
!Array.isArray(check.advisories) ||
|
|
429
|
+
check.advisories.length > 100 ||
|
|
430
|
+
!check.advisories.every(advisoryRecord) ||
|
|
431
|
+
result.advisoryCount !== check.advisories.length ||
|
|
432
|
+
(check.status === "found" && check.advisories.length === 0) ||
|
|
433
|
+
(check.status === "none-reported" && check.advisories.length !== 0)
|
|
434
|
+
)
|
|
435
|
+
return failure(
|
|
436
|
+
"hosted-report-failed",
|
|
437
|
+
"The hosted analysis did not finish and returned no valid advisory-only result. No local fallback was attempted.",
|
|
438
|
+
);
|
|
439
|
+
const error = failure(
|
|
440
|
+
"hosted-advisory-only",
|
|
441
|
+
"The registry artifact could not be analyzed; only exact-version advisories are available. No verdict is claimed.",
|
|
442
|
+
);
|
|
443
|
+
error.advisoryResult = result;
|
|
444
|
+
return error;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
export async function checkHosted(spec, options) {
|
|
448
|
+
return runHosted(spec, options);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
export async function getHostedDetails(spec, options) {
|
|
452
|
+
return runHosted(spec, options, true);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
export async function getHostedDetailsForReport(spec, reportId, options = {}) {
|
|
456
|
+
if (reportId === undefined)
|
|
457
|
+
throw failure("hosted-configuration", "An existing report ID is required.");
|
|
458
|
+
return runHosted(spec, { ...options, existingReportId: reportId }, true);
|
|
459
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { parsePackageSpec, AcquisitionError } from "../core/acquire.mjs";
|
|
2
|
+
import { LocalInputError } from "../core/pipeline.mjs";
|
|
3
|
+
import { getHostedDetails } from "./hosted.mjs";
|
|
4
|
+
|
|
5
|
+
export function terminalText(value, max = 2048) {
|
|
6
|
+
const text =
|
|
7
|
+
typeof value === "string" ? value : (JSON.stringify(value) ?? "unknown");
|
|
8
|
+
const escaped = text.replace(
|
|
9
|
+
/[\x00-\x1f\x7f-\x9f\u061c\u200b-\u200f\u2028-\u202e\u2060\u2066-\u2069\ufeff]/g,
|
|
10
|
+
(char) => "\\u" + char.charCodeAt(0).toString(16).padStart(4, "0"),
|
|
11
|
+
);
|
|
12
|
+
return escaped.length > max
|
|
13
|
+
? escaped.slice(0, max) + "… [truncated]"
|
|
14
|
+
: escaped;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function aiLines(model) {
|
|
18
|
+
const lines = [
|
|
19
|
+
`AI: ${terminalText(model.status)}${model.reason ? " (" + terminalText(model.reason) + ")" : ""}`,
|
|
20
|
+
];
|
|
21
|
+
if (
|
|
22
|
+
model.status === "complete" &&
|
|
23
|
+
typeof model.summary === "string" &&
|
|
24
|
+
model.summary.trim()
|
|
25
|
+
)
|
|
26
|
+
lines.push(`AI summary: ${terminalText(model.summary)}`);
|
|
27
|
+
return lines;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function exactPackage(spec) {
|
|
31
|
+
try {
|
|
32
|
+
return parsePackageSpec(spec);
|
|
33
|
+
} catch (error) {
|
|
34
|
+
if (error instanceof AcquisitionError)
|
|
35
|
+
throw new LocalInputError(error.code, error.message);
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function hostedOptions(runtime) {
|
|
41
|
+
return {
|
|
42
|
+
serverUrl: (runtime.env ?? process.env).UNMASK_SERVER_URL,
|
|
43
|
+
fetchImpl: runtime.fetchImpl,
|
|
44
|
+
signal: runtime.signal,
|
|
45
|
+
limits: runtime.hostedLimits,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function rows(list, render, max = 100) {
|
|
50
|
+
const items = list?.items ?? [];
|
|
51
|
+
const lines = items.slice(0, max).map(render);
|
|
52
|
+
const omitted = (list?.omitted ?? 0) + Math.max(0, items.length - max);
|
|
53
|
+
if (omitted)
|
|
54
|
+
lines.push(` ${omitted} entries omitted by hosted/terminal limits.`);
|
|
55
|
+
return lines;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function renderInfo({ details, report, reportUrl }) {
|
|
59
|
+
const claim = report.inputs.claim;
|
|
60
|
+
const text = terminalText;
|
|
61
|
+
const lines = [
|
|
62
|
+
`UNMASK info ${text(claim.name)}@${text(claim.version)}`,
|
|
63
|
+
"Publisher declarations (runtime behavior unverified):",
|
|
64
|
+
...rows(
|
|
65
|
+
details.declarations.fields,
|
|
66
|
+
(item) =>
|
|
67
|
+
` ${text(item.field)}: ${text(item.status === "declared" ? item.value : item.status)}`,
|
|
68
|
+
),
|
|
69
|
+
"Scripts (never executed):",
|
|
70
|
+
` Status: ${text(details.scripts.status)}`,
|
|
71
|
+
...rows(
|
|
72
|
+
details.scripts.entries,
|
|
73
|
+
(item) =>
|
|
74
|
+
` ${text(item.name)}: ${text(item.value)}${item.installHook ? " [install hook]" : ""}`,
|
|
75
|
+
),
|
|
76
|
+
"Dependencies (declared only; transitive dependencies not assessed):",
|
|
77
|
+
];
|
|
78
|
+
for (const category of details.dependencies.categories)
|
|
79
|
+
lines.push(
|
|
80
|
+
` ${text(category.field)}: ${text(category.status)}; declared=${text(category.declaredCount)}`,
|
|
81
|
+
...rows(
|
|
82
|
+
category.entries,
|
|
83
|
+
(item) =>
|
|
84
|
+
` ${text(item.name)}${Object.hasOwn(item, "value") ? ": " + text(item.value) : ""} [${text(item.status)}]`,
|
|
85
|
+
),
|
|
86
|
+
);
|
|
87
|
+
lines.push(
|
|
88
|
+
"Provenance (report-recorded verification status):",
|
|
89
|
+
...rows(
|
|
90
|
+
details.provenance.facts,
|
|
91
|
+
(item) => ` ${text(item.field)}: ${text(item.value)}`,
|
|
92
|
+
),
|
|
93
|
+
);
|
|
94
|
+
const check = details.vulnerabilityCheck;
|
|
95
|
+
lines.push(
|
|
96
|
+
`OSV exact-package query: ${text(check?.status ?? "not-assessed")}; complete=${check?.complete === true}`,
|
|
97
|
+
);
|
|
98
|
+
if (check?.queriedAt) lines.push(` Queried at: ${text(check.queriedAt)}`);
|
|
99
|
+
if (check?.reasons?.length) lines.push(` Reasons: ${text(check.reasons)}`);
|
|
100
|
+
const advisories = check?.advisories ?? [];
|
|
101
|
+
for (const item of advisories.slice(0, 100))
|
|
102
|
+
lines.push(
|
|
103
|
+
` ${text(item?.id)}: ${text(item?.summary ?? "No summary recorded")}`,
|
|
104
|
+
);
|
|
105
|
+
if (advisories.length > 100)
|
|
106
|
+
lines.push(
|
|
107
|
+
` ${advisories.length - 100} advisory records omitted from terminal output.`,
|
|
108
|
+
);
|
|
109
|
+
lines.push(
|
|
110
|
+
...aiLines(report.model),
|
|
111
|
+
`Inventory complete: ${details.inventory.complete === true}; coverage complete: ${report.coverage.complete === true}`,
|
|
112
|
+
"This is not a safety verdict. No findings never means safe; missing or omitted facts remain unassessed.",
|
|
113
|
+
`Report: ${text(reportUrl)}`,
|
|
114
|
+
);
|
|
115
|
+
return lines.join("\n") + "\n";
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export async function infoCommand(args, runtime = {}) {
|
|
119
|
+
if (args.length !== 1)
|
|
120
|
+
throw new LocalInputError("usage", "Use info name@exact-version");
|
|
121
|
+
exactPackage(args[0]);
|
|
122
|
+
const result = await getHostedDetails(args[0], hostedOptions(runtime));
|
|
123
|
+
(runtime.stdout ?? process.stdout).write(renderInfo(result));
|
|
124
|
+
return 0;
|
|
125
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_PROFILE,
|
|
3
|
+
GUARD_CONFIG_SCHEMA,
|
|
4
|
+
GuardConfigError,
|
|
5
|
+
createGuardConfig,
|
|
6
|
+
loadGuardSettings,
|
|
7
|
+
validateSetting,
|
|
8
|
+
} from "../core/guard-config.mjs";
|
|
9
|
+
import { parseCommandOptions } from "./config.mjs";
|
|
10
|
+
|
|
11
|
+
const USAGE =
|
|
12
|
+
"Use init [--profile strict|balanced|audit] [--project DIR]; it only creates a new .unmask.json and never installs or runs anything";
|
|
13
|
+
|
|
14
|
+
export async function runInit(args, runtime = {}) {
|
|
15
|
+
const stdout = runtime.stdout ?? process.stdout;
|
|
16
|
+
const stderr = runtime.stderr ?? process.stderr;
|
|
17
|
+
const now = runtime.now ? runtime.now() : new Date();
|
|
18
|
+
try {
|
|
19
|
+
const { positional, options } = parseCommandOptions(
|
|
20
|
+
args,
|
|
21
|
+
[],
|
|
22
|
+
["--project", "--profile"],
|
|
23
|
+
);
|
|
24
|
+
if (positional.length) throw new GuardConfigError("usage", USAGE);
|
|
25
|
+
const profile = validateSetting(
|
|
26
|
+
"profile",
|
|
27
|
+
options.profile ?? DEFAULT_PROFILE,
|
|
28
|
+
);
|
|
29
|
+
const project = options.project ?? runtime.cwd ?? process.cwd();
|
|
30
|
+
const created = await createGuardConfig(
|
|
31
|
+
project,
|
|
32
|
+
{ schemaVersion: GUARD_CONFIG_SCHEMA, profile },
|
|
33
|
+
{ now },
|
|
34
|
+
);
|
|
35
|
+
const loaded = await loadGuardSettings(project, { now });
|
|
36
|
+
if (loaded.source !== "file" || loaded.settings.profile !== profile)
|
|
37
|
+
throw new GuardConfigError(
|
|
38
|
+
"verify-failed",
|
|
39
|
+
"The created configuration did not read back as written",
|
|
40
|
+
);
|
|
41
|
+
stdout.write(
|
|
42
|
+
`Created ${created.file} with profile ${profile} (enforcement ${loaded.settings.enforcement}).\nNext: unmask config show; add exact expiring exceptions only with unmask exceptions add.\n`,
|
|
43
|
+
);
|
|
44
|
+
for (const warning of loaded.warnings)
|
|
45
|
+
stderr.write(`warning: ${warning}\n`);
|
|
46
|
+
return 0;
|
|
47
|
+
} catch (error) {
|
|
48
|
+
if (error instanceof GuardConfigError) {
|
|
49
|
+
stderr.write(`${error.code}: ${error.message}\n`);
|
|
50
|
+
return 2;
|
|
51
|
+
}
|
|
52
|
+
stderr.write(
|
|
53
|
+
"init-failed: Guard configuration could not be created; no file is claimed.\n",
|
|
54
|
+
);
|
|
55
|
+
return 1;
|
|
56
|
+
}
|
|
57
|
+
}
|