@xagents_in/unmask 0.1.3 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +86 -4
  2. package/dist/cli/annotations.mjs +145 -0
  3. package/dist/cli/audit.mjs +223 -0
  4. package/dist/cli/cache.mjs +488 -0
  5. package/dist/cli/compare.mjs +261 -0
  6. package/dist/cli/config.mjs +141 -0
  7. package/dist/cli/discovery.mjs +372 -0
  8. package/dist/cli/doctor.mjs +354 -0
  9. package/dist/cli/exceptions.mjs +170 -0
  10. package/dist/cli/file.mjs +271 -0
  11. package/dist/cli/files.mjs +141 -0
  12. package/dist/cli/history.mjs +232 -0
  13. package/dist/cli/hosted.mjs +251 -43
  14. package/dist/cli/info.mjs +125 -0
  15. package/dist/cli/init.mjs +57 -0
  16. package/dist/cli/install-guard.mjs +896 -0
  17. package/dist/cli/lockfile.mjs +330 -0
  18. package/dist/cli/main.mjs +97 -14
  19. package/dist/cli/preflight.mjs +388 -0
  20. package/dist/cli/sarif.mjs +100 -0
  21. package/dist/cli/shell-integration.mjs +122 -0
  22. package/dist/cli/terminal.mjs +396 -0
  23. package/dist/cli/watch.mjs +233 -0
  24. package/dist/cli/why.mjs +256 -0
  25. package/dist/core/advisories.mjs +101 -24
  26. package/dist/core/advisory-cache.mjs +295 -0
  27. package/dist/core/behavior-signals.mjs +1088 -0
  28. package/dist/core/dependency-graph.mjs +332 -0
  29. package/dist/core/evidence-exports.mjs +469 -0
  30. package/dist/core/guard-config.mjs +463 -0
  31. package/dist/core/guard-policy.mjs +308 -0
  32. package/dist/core/package-details.mjs +42 -6
  33. package/dist/core/report.mjs +7 -0
  34. package/dist/core/sarif.mjs +556 -0
  35. package/dist/core/syntax.mjs +296 -15
  36. package/dist/model/client.mjs +204 -56
  37. package/dist/model/diff-evidence.mjs +177 -0
  38. package/dist/model/summary.mjs +183 -0
  39. package/dist/server/advisory-result.mjs +137 -0
  40. package/dist/server/app.mjs +60 -2
  41. package/dist/server/dependency-screening.mjs +386 -0
  42. package/dist/server/exports.mjs +63 -0
  43. package/dist/server/guard-decision.mjs +37 -0
  44. package/dist/server/main.mjs +25 -7
  45. package/dist/server/queue.mjs +95 -53
  46. package/dist/server/store.mjs +21 -1
  47. package/dist/version.json +1 -1
  48. package/dist/viewer/render.mjs +10 -2
  49. package/dist/viewer/shell.mjs +1 -1
  50. package/package.json +1 -1
@@ -0,0 +1,372 @@
1
+ import { existsSync, readFileSync, statSync } from "node:fs";
2
+ import { LocalInputError } from "../core/pipeline.mjs";
3
+
4
+ const MAX_METADATA_BYTES = 1024 * 1024;
5
+ const MAX_MAIN_BYTES = 256 * 1024;
6
+ const GROUPS = [
7
+ "Package checks",
8
+ "Project and lockfile",
9
+ "Install guard and policy",
10
+ "Automation outputs",
11
+ "Local tools",
12
+ ];
13
+ const COMMANDS = [
14
+ {
15
+ name: "check",
16
+ group: 0,
17
+ module: "main.mjs",
18
+ summary:
19
+ "Check an exact npm version via the hosted service, or a local tarball offline.",
20
+ usage: [
21
+ "unmask check NAME@VERSION [--out DIR]",
22
+ "unmask check --tarball FILE --offline --cache-dir DIR [--out DIR] [--claim NAME@VERSION] [--source-archive FILE --commit SHA --source-root DIR [--source-dir DIR]] [--allow-model]",
23
+ ],
24
+ },
25
+ {
26
+ name: "scan",
27
+ group: 0,
28
+ module: "scan.mjs",
29
+ summary:
30
+ "Resolve a package name or dist-tag to one exact version, then check that pinned version.",
31
+ usage: [
32
+ "unmask scan PACKAGE[@VERSION-OR-TAG] [--json] (semver ranges are unsupported)",
33
+ ],
34
+ },
35
+ {
36
+ name: "batch",
37
+ group: 0,
38
+ module: "batch.mjs",
39
+ summary:
40
+ "Check several explicit exact versions with per-item outcomes and visible partial failures.",
41
+ usage: ["unmask batch NAME@VERSION... [--stdin] [--json]"],
42
+ },
43
+ {
44
+ name: "info",
45
+ group: 0,
46
+ module: "info.mjs",
47
+ summary:
48
+ "Show hosted package details: metadata, scripts, dependencies, provenance and advisories.",
49
+ usage: ["unmask info NAME@VERSION"],
50
+ },
51
+ {
52
+ name: "files",
53
+ group: 0,
54
+ module: "files.mjs",
55
+ summary: "List a checked version's file inventory with hashes and states.",
56
+ usage: [
57
+ "unmask files NAME@VERSION [--report ID] [--path TEXT] [--state STATE] [--limit 1..200] [--offset N]",
58
+ ],
59
+ },
60
+ {
61
+ name: "file",
62
+ group: 0,
63
+ module: "file.mjs",
64
+ summary:
65
+ "Show one recorded file's evidence, bound to a hosted report ID and file ID.",
66
+ usage: ["unmask file NAME@VERSION --report REPORT_ID --file-id FILE_ID"],
67
+ },
68
+ {
69
+ name: "compare",
70
+ group: 0,
71
+ module: "compare.mjs",
72
+ summary:
73
+ "Compare scripts, dependencies and files between two exact versions of one package.",
74
+ usage: ["unmask compare NAME@OLD NAME@NEW [--json]"],
75
+ },
76
+ {
77
+ name: "history",
78
+ group: 0,
79
+ module: "history.mjs",
80
+ summary:
81
+ "Show a bounded npm release timeline with deprecations and exact-version guidance.",
82
+ usage: ["unmask history NAME [--limit 1-100] [--json]"],
83
+ },
84
+ {
85
+ name: "audit",
86
+ group: 1,
87
+ module: "audit.mjs",
88
+ summary: "Audit the exact dependency versions in an npm v2/v3 lockfile.",
89
+ usage: [
90
+ "unmask audit [--lockfile PATH] [--max N] [--concurrency N] [--omit-dev] [--json]",
91
+ ],
92
+ },
93
+ {
94
+ name: "why",
95
+ group: 1,
96
+ module: "why.mjs",
97
+ summary: "Explain the lockfile dependency paths that bring in a package.",
98
+ usage: [
99
+ "unmask why NAME[@VERSION] [--lockfile PATH] [--max-paths N] [--json]",
100
+ ],
101
+ },
102
+ {
103
+ name: "preflight",
104
+ group: 1,
105
+ module: "preflight.mjs",
106
+ summary:
107
+ "Check exact lockfile changes against a baseline for CI pipelines.",
108
+ usage: [
109
+ "unmask preflight --baseline FILE [--lockfile PATH] [--max-checks N] [--no-check] [--json]",
110
+ ],
111
+ },
112
+ {
113
+ name: "watch",
114
+ group: 1,
115
+ module: "watch.mjs",
116
+ summary:
117
+ "Watch the project lockfile and run bounded, non-overlapping checks on changes.",
118
+ usage: [
119
+ "unmask watch [--lockfile PATH] [--debounce-ms 50-60000] [--max-checks N] [--no-check] [--json]",
120
+ ],
121
+ },
122
+ {
123
+ name: "ci",
124
+ group: 2,
125
+ module: "install-guard.mjs",
126
+ summary:
127
+ "Screen the exact lockfile plan, then run npm ci only if policy allows it.",
128
+ usage: [
129
+ "unmask ci [--omit|--include dev|optional|peer] [--loglevel LEVEL] [--ignore-scripts] [--prefer-offline] [--offline] [other allowlisted npm ci flags]",
130
+ ],
131
+ },
132
+ {
133
+ name: "install",
134
+ group: 2,
135
+ module: "install-guard.mjs",
136
+ summary:
137
+ "Refuses to run npm install (it can re-resolve versions) and explains the lockfile-first path.",
138
+ usage: ["unmask install [ARGS]"],
139
+ },
140
+ {
141
+ name: "enable",
142
+ group: 2,
143
+ module: "shell-integration.mjs",
144
+ summary:
145
+ "Print shell code that routes npm ci through the guard; no startup file is written.",
146
+ usage: ["unmask enable bash|zsh|fish"],
147
+ },
148
+ {
149
+ name: "disable",
150
+ group: 2,
151
+ module: "shell-integration.mjs",
152
+ summary: "Print shell code that removes the guard routing.",
153
+ usage: ["unmask disable bash|zsh|fish"],
154
+ },
155
+ {
156
+ name: "init",
157
+ group: 2,
158
+ module: "init.mjs",
159
+ summary:
160
+ "Create a new project .unmask.json guard configuration; never overwrites an existing one.",
161
+ usage: ["unmask init [--profile strict|balanced|audit] [--project DIR]"],
162
+ },
163
+ {
164
+ name: "config",
165
+ group: 2,
166
+ module: "config.mjs",
167
+ summary:
168
+ "Show or set the project guard policy profile (strict, balanced, audit).",
169
+ usage: [
170
+ "unmask config show|profiles [--json] [--project DIR]",
171
+ "unmask config set profile strict|balanced|audit [--project DIR]",
172
+ ],
173
+ },
174
+ {
175
+ name: "exceptions",
176
+ group: 2,
177
+ module: "exceptions.mjs",
178
+ summary:
179
+ "Manage explicit, expiring exact-version exceptions with recorded reasons.",
180
+ usage: [
181
+ "unmask exceptions list [--json] [--project DIR]",
182
+ "unmask exceptions check NAME@VERSION [--json] [--project DIR]",
183
+ "unmask exceptions add NAME@VERSION --reason TEXT --expires YYYY-MM-DD [--project DIR]",
184
+ "unmask exceptions remove NAME@VERSION [--project DIR]",
185
+ "unmask exceptions prune [--project DIR]",
186
+ ],
187
+ },
188
+ {
189
+ name: "sarif",
190
+ group: 3,
191
+ module: "sarif.mjs",
192
+ summary: "Export an existing UNMASK report as SARIF.",
193
+ usage: ["unmask sarif --report FILE [--details FILE] [--out FILE]"],
194
+ },
195
+ {
196
+ name: "annotations",
197
+ group: 3,
198
+ module: "annotations.mjs",
199
+ summary:
200
+ "Print CI annotations from an existing report, optionally appending a summary file.",
201
+ usage: [
202
+ "unmask annotations --report FILE [--details FILE] [--summary FILE]",
203
+ ],
204
+ },
205
+ {
206
+ name: "doctor",
207
+ group: 4,
208
+ module: "doctor.mjs",
209
+ summary:
210
+ "Diagnose Node, hosted service version and project guard configuration.",
211
+ usage: [
212
+ "unmask doctor [--offline] [--server-url ORIGIN] [--project DIR] [--json]",
213
+ ],
214
+ },
215
+ {
216
+ name: "cache",
217
+ group: 4,
218
+ module: "cache.mjs",
219
+ summary: "Import, inspect and explicitly prune local report snapshots.",
220
+ usage: [
221
+ "unmask cache import --report FILE [--cache-dir DIR] [--json]",
222
+ "unmask cache inspect [--cache-dir DIR] [--json]",
223
+ "unmask cache prune --older-than-days N --limit N [--apply] [--cache-dir DIR] [--json]",
224
+ ],
225
+ },
226
+ ];
227
+ export function dispatchedCommands(mainSource) {
228
+ return new Set(
229
+ [...String(mainSource).matchAll(/argv\[0\] === "([a-z][a-z-]*)"/g)].map(
230
+ (match) => match[1],
231
+ ),
232
+ );
233
+ }
234
+ function shippedDispatch() {
235
+ try {
236
+ const url = new URL("./main.mjs", import.meta.url);
237
+ if (statSync(url).size > MAX_MAIN_BYTES) return new Set();
238
+ return dispatchedCommands(readFileSync(url, "utf8"));
239
+ } catch {
240
+ return new Set();
241
+ }
242
+ }
243
+ function usageError(message) {
244
+ return new LocalInputError("usage", message);
245
+ }
246
+ export function commandCatalog() {
247
+ const dispatched = shippedDispatch();
248
+ return COMMANDS.map((command) => ({
249
+ name: command.name,
250
+ module: command.module,
251
+ group: GROUPS[command.group],
252
+ available:
253
+ command.name === "check" ||
254
+ (dispatched.has(command.name) &&
255
+ existsSync(new URL(`./${command.module}`, import.meta.url))),
256
+ summary: command.summary,
257
+ usage: command.usage,
258
+ }));
259
+ }
260
+ function boundedJson(url) {
261
+ const stat = statSync(url);
262
+ if (!stat.isFile() || stat.size > MAX_METADATA_BYTES) throw new TypeError();
263
+ return JSON.parse(readFileSync(url, "utf8"));
264
+ }
265
+ export function buildIdentity() {
266
+ const metadataUrl = new URL("../version.json", import.meta.url);
267
+ if (!existsSync(metadataUrl)) {
268
+ const pkg = boundedJson(new URL("../../package.json", import.meta.url));
269
+ if (typeof pkg?.version !== "string") throw new TypeError();
270
+ return { version: pkg.version, build: "source", commit: null, dirty: null };
271
+ }
272
+ const metadata = boundedJson(metadataUrl);
273
+ if (
274
+ metadata?.buildSchema !== "1" ||
275
+ typeof metadata.version !== "string" ||
276
+ !/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(metadata.version) ||
277
+ typeof metadata.dirty !== "boolean" ||
278
+ !(metadata.commit === "unknown" || /^[a-f0-9]{40}$/.test(metadata.commit))
279
+ )
280
+ throw new TypeError();
281
+ return {
282
+ version: metadata.version,
283
+ build: "built",
284
+ commit: metadata.commit === "unknown" ? null : metadata.commit,
285
+ dirty: metadata.dirty,
286
+ sourceSha256: /^[a-f0-9]{64}$/.test(metadata.sourceSha256)
287
+ ? metadata.sourceSha256
288
+ : null,
289
+ };
290
+ }
291
+ function renderOverview(catalog) {
292
+ const width = Math.max(...catalog.map((command) => command.name.length));
293
+ const row = (command) =>
294
+ ` ${command.name.padEnd(width)} ${command.summary}`;
295
+ const lines = ["Usage: unmask <command> [options]"];
296
+ for (const group of GROUPS) {
297
+ const rows = catalog.filter(
298
+ (command) => command.available && command.group === group,
299
+ );
300
+ if (rows.length) lines.push("", `${group}:`, ...rows.map(row));
301
+ }
302
+ const planned = catalog.filter((command) => !command.available);
303
+ if (planned.length)
304
+ lines.push("", "Planned, not included in this build:", ...planned.map(row));
305
+ lines.push(
306
+ "",
307
+ "Help and identity:",
308
+ " unmask --help | unmask help COMMAND | unmask COMMAND --help",
309
+ " unmask --version [--json] build version and commit, read locally without network",
310
+ "",
311
+ "Results report evidence and coverage; no command guarantees a package is safe.",
312
+ );
313
+ return lines.join("\n") + "\n";
314
+ }
315
+ function renderCommand(command) {
316
+ const lines = [`unmask ${command.name}: ${command.summary}`];
317
+ if (!command.available)
318
+ lines.push("Not included in this build; this command cannot be run here.");
319
+ else if (command.usage.length)
320
+ lines.push("Usage:", ...command.usage.map((usage) => ` ${usage}`));
321
+ return lines.join("\n") + "\n";
322
+ }
323
+ export function discoveryCommand(argv, runtime = {}) {
324
+ const stdout = runtime.stdout ?? process.stdout;
325
+ if (argv[0] === "--version" || argv[0] === "version") {
326
+ const json = argv[1] === "--json";
327
+ if (argv.length > (json ? 2 : 1))
328
+ throw usageError("Use unmask --version [--json]");
329
+ let identity;
330
+ try {
331
+ identity = buildIdentity();
332
+ } catch {
333
+ throw new LocalInputError(
334
+ "build-metadata-invalid",
335
+ "Build version metadata is missing or invalid; reinstall the package.",
336
+ );
337
+ }
338
+ stdout.write(
339
+ json
340
+ ? JSON.stringify(identity, null, 2) + "\n"
341
+ : `unmask ${identity.version} (${
342
+ identity.build === "source"
343
+ ? "unbuilt source checkout, no build metadata"
344
+ : identity.commit
345
+ ? `commit ${identity.commit}`
346
+ : "built from uncommitted changes, commit unknown"
347
+ })\n`,
348
+ );
349
+ return 0;
350
+ }
351
+ const catalog = commandCatalog();
352
+ const name =
353
+ argv[0] === "help" ? argv[1] : argv[1] === "--help" ? argv[0] : undefined;
354
+ const extra = argv[0] === "help" ? argv.slice(2) : argv.slice(1);
355
+ if (name === undefined && argv.length <= 1) {
356
+ stdout.write(renderOverview(catalog));
357
+ return 0;
358
+ }
359
+ const command = catalog.find((item) => item.name === name);
360
+ if (!command || (argv[0] === "help" ? extra.length : extra.length !== 1))
361
+ throw usageError(
362
+ "Unknown command or help request; run unmask --help for the command list",
363
+ );
364
+ stdout.write(renderCommand(command));
365
+ return 0;
366
+ }
367
+ export function isDiscoveryRequest(argv) {
368
+ return (
369
+ ["--help", "-h", "help", "--version", "version"].includes(argv[0]) ||
370
+ (argv.length === 2 && argv[1] === "--help")
371
+ );
372
+ }