@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.
- package/README.md +86 -4
- package/dist/cli/annotations.mjs +145 -0
- package/dist/cli/audit.mjs +223 -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 +97 -14
- package/dist/cli/preflight.mjs +388 -0
- package/dist/cli/sarif.mjs +100 -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/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 +60 -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/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/core/syntax.mjs
CHANGED
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
import { parse } from "acorn";
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
3
|
import { validateReport } from "./report.mjs";
|
|
4
|
+
import {
|
|
5
|
+
BEHAVIOR_LIMITS,
|
|
6
|
+
javascriptSignal,
|
|
7
|
+
manifestScriptSpans,
|
|
8
|
+
destructiveSignals,
|
|
9
|
+
DESTRUCTIVE_LIMITS,
|
|
10
|
+
moduleAliases,
|
|
11
|
+
NETWORK_MODULES,
|
|
12
|
+
obfuscationSignals,
|
|
13
|
+
OBFUSCATION_LIMITS,
|
|
14
|
+
scriptSignals,
|
|
15
|
+
summaryFor,
|
|
16
|
+
} from "./behavior-signals.mjs";
|
|
4
17
|
|
|
5
18
|
export const SYNTAX_LIMITS = Object.freeze({
|
|
6
19
|
fileBytes: 2097152,
|
|
@@ -79,8 +92,26 @@ function nameOf(node) {
|
|
|
79
92
|
}
|
|
80
93
|
return "";
|
|
81
94
|
}
|
|
82
|
-
function
|
|
95
|
+
function aliasedName(callee, aliases) {
|
|
96
|
+
if (callee?.type === "Identifier") return aliases.get(callee.name) ?? "";
|
|
97
|
+
if (
|
|
98
|
+
callee?.type === "MemberExpression" &&
|
|
99
|
+
!callee.computed &&
|
|
100
|
+
callee.object.type === "Identifier" &&
|
|
101
|
+
aliases.has(callee.object.name)
|
|
102
|
+
)
|
|
103
|
+
return `${aliases.get(callee.object.name)}.${callee.property.name}`;
|
|
104
|
+
return "";
|
|
105
|
+
}
|
|
106
|
+
function capability(node, aliases) {
|
|
83
107
|
const name = nameOf(node.callee);
|
|
108
|
+
const aliased = aliases?.size ? aliasedName(node.callee, aliases) : "";
|
|
109
|
+
if (
|
|
110
|
+
node.type === "CallExpression" &&
|
|
111
|
+
aliased &&
|
|
112
|
+
/^(https?\.(request|get)|net\.connect)$/.test(aliased)
|
|
113
|
+
)
|
|
114
|
+
return "network-call";
|
|
84
115
|
if (
|
|
85
116
|
node.type === "CallExpression" &&
|
|
86
117
|
/^(fetch|globalThis.fetch|https?\.(request|get)|net\.connect)$/.test(name)
|
|
@@ -144,7 +175,92 @@ export function inspectJavaScript(artifact, source, overrides = {}) {
|
|
|
144
175
|
const literals = new Map(),
|
|
145
176
|
candidates = [],
|
|
146
177
|
evidence = new Map();
|
|
147
|
-
let sourceLiteralTruncated = false
|
|
178
|
+
let sourceLiteralTruncated = false,
|
|
179
|
+
syntaxCount = 0;
|
|
180
|
+
const hookTargets = new Map();
|
|
181
|
+
const linked = new Map();
|
|
182
|
+
const link = (path, hooks, from) => {
|
|
183
|
+
const item = linked.get(path) ?? { hooks: new Set(), from: new Set() };
|
|
184
|
+
for (const hook of hooks) item.hooks.add(hook);
|
|
185
|
+
item.from.add(from);
|
|
186
|
+
if (linked.has(path) || linked.size < BEHAVIOR_LIMITS.linkedScripts)
|
|
187
|
+
linked.set(path, item);
|
|
188
|
+
};
|
|
189
|
+
const behaviorUse = { review: 0, HIGH: 0, perFileRule: new Map() };
|
|
190
|
+
const admitBehavior = (priority, path, ruleId) => {
|
|
191
|
+
const key = `${path}\0${ruleId}`;
|
|
192
|
+
const used = behaviorUse.perFileRule.get(key) || 0;
|
|
193
|
+
const tier = priority === "HIGH" ? "HIGH" : "review";
|
|
194
|
+
const cap =
|
|
195
|
+
tier === "HIGH" ? BEHAVIOR_LIMITS.highCues : BEHAVIOR_LIMITS.reviewCues;
|
|
196
|
+
if (
|
|
197
|
+
used >= 0 &&
|
|
198
|
+
behaviorUse[tier] < cap &&
|
|
199
|
+
(tier === "HIGH" || used < BEHAVIOR_LIMITS.reviewCuesPerFileRule)
|
|
200
|
+
) {
|
|
201
|
+
behaviorUse[tier]++;
|
|
202
|
+
behaviorUse.perFileRule.set(key, used + 1);
|
|
203
|
+
return true;
|
|
204
|
+
}
|
|
205
|
+
if (used !== -1) {
|
|
206
|
+
behaviorUse.perFileRule.set(key, -1);
|
|
207
|
+
result.annotations.push({
|
|
208
|
+
type: "behavior-not-inspected",
|
|
209
|
+
side: "artifact",
|
|
210
|
+
path,
|
|
211
|
+
ruleId,
|
|
212
|
+
reason: "behavior-cue-cap",
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
return false;
|
|
216
|
+
};
|
|
217
|
+
const manifest = (artifact?.entries || []).find(
|
|
218
|
+
(entry) => entry.type === "file" && entry.path === "package.json",
|
|
219
|
+
);
|
|
220
|
+
let behaviorManifest = { status: "not-assessed", reason: "not-retained" };
|
|
221
|
+
if (Buffer.isBuffer(manifest?.content))
|
|
222
|
+
try {
|
|
223
|
+
const text = new TextDecoder("utf-8", {
|
|
224
|
+
fatal: true,
|
|
225
|
+
ignoreBOM: true,
|
|
226
|
+
}).decode(manifest.content);
|
|
227
|
+
const positions = offsets(text);
|
|
228
|
+
const parsed = manifestScriptSpans(text);
|
|
229
|
+
behaviorManifest = parsed.reason
|
|
230
|
+
? { status: parsed.status, reason: parsed.reason }
|
|
231
|
+
: { status: parsed.status };
|
|
232
|
+
for (const item of parsed.hooks) {
|
|
233
|
+
const assessed = scriptSignals(item.script);
|
|
234
|
+
if (assessed.status !== "assessed")
|
|
235
|
+
result.annotations.push({
|
|
236
|
+
type: "behavior-not-inspected",
|
|
237
|
+
side: "artifact",
|
|
238
|
+
path: manifest.path,
|
|
239
|
+
hook: item.hook,
|
|
240
|
+
reason: assessed.reason,
|
|
241
|
+
});
|
|
242
|
+
for (const script of assessed.scripts || [])
|
|
243
|
+
link(script, [item.hook], `package.json#scripts.${item.hook}`);
|
|
244
|
+
for (const target of assessed.targets || [])
|
|
245
|
+
hookTargets.set(target, [
|
|
246
|
+
...new Set([...(hookTargets.get(target) || []), item.hook]),
|
|
247
|
+
]);
|
|
248
|
+
for (const { ruleId, priority, ...signal } of assessed.signals)
|
|
249
|
+
if (admitBehavior(priority, manifest.path, ruleId))
|
|
250
|
+
candidates.push({
|
|
251
|
+
behavior: { ruleId, priority, hook: item.hook, ...signal },
|
|
252
|
+
evidence: evidenceFor(
|
|
253
|
+
"artifact",
|
|
254
|
+
artifact,
|
|
255
|
+
manifest,
|
|
256
|
+
item,
|
|
257
|
+
positions,
|
|
258
|
+
),
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
} catch {
|
|
262
|
+
behaviorManifest = { status: "not-assessed", reason: "invalid-utf8" };
|
|
263
|
+
}
|
|
148
264
|
for (const [side, inventory] of [
|
|
149
265
|
["artifact", artifact],
|
|
150
266
|
["source", source],
|
|
@@ -219,7 +335,35 @@ export function inspectJavaScript(artifact, source, overrides = {}) {
|
|
|
219
335
|
coverage.files++;
|
|
220
336
|
coverage.bytes += entry.size;
|
|
221
337
|
const positions = offsets(text);
|
|
222
|
-
|
|
338
|
+
const all = nodes(ast);
|
|
339
|
+
const networkAliases =
|
|
340
|
+
side === "artifact" ? moduleAliases(all, NETWORK_MODULES) : null;
|
|
341
|
+
if (side === "artifact") {
|
|
342
|
+
const hooks = hookTargets.get(entry.path);
|
|
343
|
+
const found = obfuscationSignals(all);
|
|
344
|
+
if (found.capped) skip("behavior-obfuscation-cap");
|
|
345
|
+
const destructive = destructiveSignals(all);
|
|
346
|
+
if (destructive.capped) skip("behavior-destructive-cap");
|
|
347
|
+
found.signals.push(...destructive.signals);
|
|
348
|
+
for (const {
|
|
349
|
+
ruleId,
|
|
350
|
+
priority,
|
|
351
|
+
node,
|
|
352
|
+
related,
|
|
353
|
+
detail,
|
|
354
|
+
} of found.signals) {
|
|
355
|
+
if (admitBehavior(priority, entry.path, ruleId))
|
|
356
|
+
candidates.push({
|
|
357
|
+
behavior: { ruleId, priority, ...detail },
|
|
358
|
+
hooks,
|
|
359
|
+
evidence: evidenceFor(side, inventory, entry, node, positions),
|
|
360
|
+
related: related.map((item) =>
|
|
361
|
+
evidenceFor(side, inventory, entry, item, positions),
|
|
362
|
+
),
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
for (const node of all) {
|
|
223
367
|
if (side === "source") {
|
|
224
368
|
if (
|
|
225
369
|
node.type === "Literal" &&
|
|
@@ -235,24 +379,128 @@ export function inspectJavaScript(artifact, source, overrides = {}) {
|
|
|
235
379
|
}
|
|
236
380
|
continue;
|
|
237
381
|
}
|
|
238
|
-
const kind = capability(node);
|
|
239
|
-
|
|
240
|
-
if (
|
|
382
|
+
const kind = capability(node, networkAliases);
|
|
383
|
+
const signal = javascriptSignal(node);
|
|
384
|
+
if (!kind && !signal) continue;
|
|
385
|
+
if (kind && syntaxCount >= limits.cues) {
|
|
241
386
|
skip("syntax-cue-cap");
|
|
242
387
|
break;
|
|
243
388
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
389
|
+
const hooks = hookTargets.get(entry.path);
|
|
390
|
+
syntaxCount += kind ? 1 : 0;
|
|
391
|
+
if (kind)
|
|
392
|
+
candidates.push({
|
|
393
|
+
kind,
|
|
394
|
+
hooks,
|
|
395
|
+
evidence: evidenceFor(side, inventory, entry, node, positions),
|
|
396
|
+
literal:
|
|
397
|
+
node.arguments?.[0]?.type === "Literal" &&
|
|
398
|
+
typeof node.arguments[0].value === "string"
|
|
399
|
+
? node.arguments[0].value
|
|
400
|
+
: null,
|
|
401
|
+
});
|
|
402
|
+
if (signal && admitBehavior("review", entry.path, signal.ruleId)) {
|
|
403
|
+
const { ruleId, at, ...detail } = signal;
|
|
404
|
+
candidates.push({
|
|
405
|
+
behavior: { ruleId, priority: "review", ...detail },
|
|
406
|
+
hooks,
|
|
407
|
+
evidence: evidenceFor(
|
|
408
|
+
side,
|
|
409
|
+
inventory,
|
|
410
|
+
entry,
|
|
411
|
+
at || node,
|
|
412
|
+
positions,
|
|
413
|
+
),
|
|
414
|
+
});
|
|
415
|
+
}
|
|
253
416
|
}
|
|
254
417
|
}
|
|
255
418
|
}
|
|
419
|
+
for (const candidate of candidates)
|
|
420
|
+
if (
|
|
421
|
+
candidate.kind === "process-execution" &&
|
|
422
|
+
candidate.hooks &&
|
|
423
|
+
candidate.literal !== null
|
|
424
|
+
)
|
|
425
|
+
for (const script of scriptSignals(candidate.literal).scripts || [])
|
|
426
|
+
link(script, candidate.hooks, candidate.evidence.path);
|
|
427
|
+
for (const [path, { hooks, from }] of linked) {
|
|
428
|
+
const entry = artifact.entries.find(
|
|
429
|
+
(item) => item.type === "file" && item.path === path,
|
|
430
|
+
);
|
|
431
|
+
const unscanned = (reason) =>
|
|
432
|
+
result.annotations.push({
|
|
433
|
+
type: "behavior-not-inspected",
|
|
434
|
+
side: "artifact",
|
|
435
|
+
path,
|
|
436
|
+
reason,
|
|
437
|
+
});
|
|
438
|
+
if (!entry) {
|
|
439
|
+
unscanned("linked-script-missing");
|
|
440
|
+
continue;
|
|
441
|
+
}
|
|
442
|
+
if (!Buffer.isBuffer(entry.content)) {
|
|
443
|
+
unscanned("not-retained");
|
|
444
|
+
continue;
|
|
445
|
+
}
|
|
446
|
+
if (entry.size > BEHAVIOR_LIMITS.linkedScriptBytes) {
|
|
447
|
+
unscanned("linked-script-byte-cap");
|
|
448
|
+
continue;
|
|
449
|
+
}
|
|
450
|
+
let text;
|
|
451
|
+
try {
|
|
452
|
+
text = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(
|
|
453
|
+
entry.content,
|
|
454
|
+
);
|
|
455
|
+
} catch {
|
|
456
|
+
unscanned("invalid-utf8");
|
|
457
|
+
continue;
|
|
458
|
+
}
|
|
459
|
+
const assessed = scriptSignals(text, {
|
|
460
|
+
maxBytes: BEHAVIOR_LIMITS.linkedScriptBytes,
|
|
461
|
+
maxTokens: BEHAVIOR_LIMITS.linkedScriptTokens,
|
|
462
|
+
});
|
|
463
|
+
if (assessed.status !== "assessed") {
|
|
464
|
+
unscanned(assessed.reason);
|
|
465
|
+
continue;
|
|
466
|
+
}
|
|
467
|
+
const positions = offsets(text);
|
|
468
|
+
const lines = [];
|
|
469
|
+
for (let start = 0; start < text.length;) {
|
|
470
|
+
const stop = text.indexOf("\n", start);
|
|
471
|
+
const end = stop === -1 ? text.length : stop;
|
|
472
|
+
lines.push({ start, end, lower: text.slice(start, end).toLowerCase() });
|
|
473
|
+
start = end + 1;
|
|
474
|
+
}
|
|
475
|
+
for (const { ruleId, priority, ...signal } of assessed.signals) {
|
|
476
|
+
const words = [
|
|
477
|
+
...(signal.tools || []),
|
|
478
|
+
...(signal.file ? [signal.file.toLowerCase()] : []),
|
|
479
|
+
...(signal.paths || []),
|
|
480
|
+
...(signal.variables || []).map((name) => name.toLowerCase()),
|
|
481
|
+
...(signal.commands || []).map((command) => command.split(" ")[0]),
|
|
482
|
+
];
|
|
483
|
+
const cited = lines
|
|
484
|
+
.filter((line) => words.some((word) => line.lower.includes(word)))
|
|
485
|
+
.slice(0, 3);
|
|
486
|
+
if (!cited.length || !admitBehavior(priority, path, ruleId)) continue;
|
|
487
|
+
const [first, ...rest] = cited.map((line) =>
|
|
488
|
+
evidenceFor("artifact", artifact, entry, line, positions),
|
|
489
|
+
);
|
|
490
|
+
candidates.push({
|
|
491
|
+
behavior: {
|
|
492
|
+
ruleId,
|
|
493
|
+
priority,
|
|
494
|
+
script: path,
|
|
495
|
+
linkedFrom: [...from].sort(),
|
|
496
|
+
...signal,
|
|
497
|
+
},
|
|
498
|
+
hooks: [...hooks].sort(),
|
|
499
|
+
evidence: first,
|
|
500
|
+
related: rest,
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
}
|
|
256
504
|
const stem = (value) => value.replace(/\.[cm]?[jt]sx?$/, "");
|
|
257
505
|
const unknownSource = new Set(
|
|
258
506
|
result.annotations
|
|
@@ -265,6 +513,27 @@ export function inspectJavaScript(artifact, source, overrides = {}) {
|
|
|
265
513
|
for (const candidate of candidates) {
|
|
266
514
|
const ids = [candidate.evidence.id];
|
|
267
515
|
evidence.set(candidate.evidence.id, candidate.evidence);
|
|
516
|
+
const hookTarget = candidate.hooks
|
|
517
|
+
? { installHookTarget: candidate.hooks }
|
|
518
|
+
: {};
|
|
519
|
+
for (const item of candidate.related || []) {
|
|
520
|
+
evidence.set(item.id, item);
|
|
521
|
+
if (!ids.includes(item.id)) ids.push(item.id);
|
|
522
|
+
}
|
|
523
|
+
if (candidate.behavior) {
|
|
524
|
+
const { ruleId, priority, ...signal } = candidate.behavior;
|
|
525
|
+
result.cues.push({
|
|
526
|
+
module: "behavior",
|
|
527
|
+
ruleId,
|
|
528
|
+
priority,
|
|
529
|
+
summary: summaryFor(ruleId),
|
|
530
|
+
confidence: "suspicion-not-proof",
|
|
531
|
+
signal,
|
|
532
|
+
...hookTarget,
|
|
533
|
+
evidenceIds: ids,
|
|
534
|
+
});
|
|
535
|
+
continue;
|
|
536
|
+
}
|
|
268
537
|
const match = candidate.literal !== null && literals.get(candidate.literal);
|
|
269
538
|
if (match) {
|
|
270
539
|
evidence.set(match.id, match);
|
|
@@ -291,10 +560,22 @@ export function inspectJavaScript(artifact, source, overrides = {}) {
|
|
|
291
560
|
? "not-assessed"
|
|
292
561
|
: "not-found-in-inspected-source"
|
|
293
562
|
: "source-unavailable",
|
|
563
|
+
...hookTarget,
|
|
294
564
|
evidenceIds: ids,
|
|
295
565
|
});
|
|
296
566
|
}
|
|
297
567
|
result.evidence = [...evidence.values()];
|
|
568
|
+
result.annotations.push({
|
|
569
|
+
type: "behavior-scope",
|
|
570
|
+
manifest: behaviorManifest,
|
|
571
|
+
limits: {
|
|
572
|
+
...BEHAVIOR_LIMITS,
|
|
573
|
+
obfuscation: { ...OBFUSCATION_LIMITS },
|
|
574
|
+
destructive: { ...DESTRUCTIVE_LIMITS },
|
|
575
|
+
},
|
|
576
|
+
limitation:
|
|
577
|
+
"Behavior cues are static text and syntax suspicions, not proof of execution or intent. Install hooks and package code were not run; aliases, computed names and runtime-constructed paths are not resolved.",
|
|
578
|
+
});
|
|
298
579
|
result.annotations.push({
|
|
299
580
|
type: "syntax-scope",
|
|
300
581
|
parser: "acorn@8.19.0",
|