@clear-capabilities/agentic-security-scanner 0.195.28 → 0.195.30

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 (48) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/bin/agentic-security.js +13 -4
  3. package/dist/1340.index.js +11 -6
  4. package/dist/3500.index.js +943 -0
  5. package/dist/5616.index.js +146 -19
  6. package/dist/{3eabd90d6d9c8c8dbbd9.json → 7e58a58b8e6ccdde1659.json} +7 -6
  7. package/dist/9560.index.js +29 -5
  8. package/dist/agentic-security.mjs +2 -2
  9. package/dist/agentic-security.mjs.sha256 +1 -1
  10. package/package.json +7 -6
  11. package/src/egress/policy.js +79 -1
  12. package/src/engine.js +1 -1
  13. package/src/language/CLAUDE.md +1 -1
  14. package/src/language/haskell-guards.js +26 -5
  15. package/src/lineage/CLAUDE.md +3 -1
  16. package/src/lineage/deployment/adapter-cloud.js +5 -2
  17. package/src/lineage/deployment/attack-paths.js +107 -30
  18. package/src/lineage/deployment/boundaries-run.js +2 -2
  19. package/src/lineage/deployment/iam-eval.js +105 -0
  20. package/src/lineage/deployment/projection.js +10 -4
  21. package/src/lineage/deployment/proof-binding.js +79 -0
  22. package/src/llm-validator/bounded-context.js +115 -8
  23. package/src/llm-validator/index.js +1 -1
  24. package/src/mcp/CLAUDE.md +6 -0
  25. package/src/mcp/tools.js +27 -3
  26. package/src/pipeline/evidence-record.js +1 -1
  27. package/src/posture/CLAUDE.md +2 -0
  28. package/src/posture/assurance/candidate-qualification.js +155 -0
  29. package/src/posture/assurance/release-package-cli.js +44 -2
  30. package/src/posture/auditor-walkthrough.js +2 -1
  31. package/src/posture/dnr/detection-ir.js +2 -2
  32. package/src/posture/dnr/hunt.js +22 -6
  33. package/src/posture/dnr/log-reader.js +47 -3
  34. package/src/posture/dnr/matchers.js +43 -3
  35. package/src/posture/dnr/outputs.js +16 -2
  36. package/src/posture/dnr/payload-classes.js +12 -2
  37. package/src/posture/dnr/validate.js +17 -4
  38. package/src/posture/evaluation/report.js +2 -1
  39. package/src/posture/evaluation/score.js +24 -7
  40. package/src/posture/logic-claims.js +15 -14
  41. package/src/posture/sibling-queue.js +69 -0
  42. package/src/posture/triage-memory.js +106 -26
  43. package/src/posture/verification-separation.js +55 -7
  44. package/src/report/index.js +4 -0
  45. package/src/runScan.js +5 -0
  46. package/src/scope/flags.js +1 -0
  47. package/src/scope/inventory.js +17 -1
  48. package/src/scope/release-scope.js +80 -0
@@ -105,14 +105,14 @@ function buildDetectionIR({ finding, proof = null, allowUnproven = false } = {})
105
105
 
106
106
  let parameter = null;
107
107
  if (attack?.parameter) { parameter = { name: attack.parameter, location: attack.location }; facts.parameter = { source: 'reproducer', ref: 'attack.parameter' }; }
108
- else if (payloadClass !== 'auth-bypass') limitations.push('no vulnerable parameter is known, so the payload test is applied to every query and body value of the endpoint');
108
+ else if (def.matcher !== 'behavior') limitations.push('no vulnerable parameter is known, so the payload test is applied to every query and body value of the endpoint');
109
109
 
110
110
  let sink = null;
111
111
  if (attack?.sink) { sink = attack.sink; facts.sink = { source: 'reproducer', ref: 'attack.sink' }; }
112
112
  const affectedOperation = { sink, file: finding.file ? String(finding.file) : null, line: Number.isInteger(finding.line) ? finding.line : null };
113
113
 
114
114
  const confidence = Math.max(0.1, Math.min(0.95,
115
- (grounding === 'exploit-confirmed' ? 0.9 : grounding === 'reproduced' ? 0.7 : 0.4) - (endpoint ? 0 : 0.2) - (parameter || payloadClass === 'auth-bypass' ? 0 : 0.1)));
115
+ (grounding === 'exploit-confirmed' ? 0.9 : grounding === 'reproduced' ? 0.7 : 0.4) - (endpoint ? 0 : 0.2) - (parameter || def.matcher === 'behavior' ? 0 : 0.1)));
116
116
  if (grounding !== 'exploit-confirmed') limitations.push(grounding === 'reproduced' ? 'the exploit was reproduced but not independently confirmed' : 'the exploit was not executed; this detection rests on static analysis only');
117
117
 
118
118
  const ir = {
@@ -188,6 +188,7 @@ const unavailable = (reason) => ({ applicable: false, reason });
188
188
  const fpNotes = (ir) => [
189
189
  'A legitimate client that sends this payload shape to the same endpoint (a security scanner, a penetration test).',
190
190
  ...(ir.payloadClass === 'auth-bypass' ? ['Public endpoints that are intentionally reachable without a credential.'] : []),
191
+ ...(ir.payloadClass === 'object-authz' ? ['A cross-tenant read that an operator or support role is allowed to make but whose grant is not recorded in the event.'] : []),
191
192
  ];
192
193
 
193
194
  /** Sigma rule as YAML text. JSON string syntax is used for every scalar; it is valid YAML and cannot be broken out of. */
@@ -228,7 +229,8 @@ function http(ir) {
228
229
  schema: 'agentic-security/http-detection', version: 1, id: ir.id, findingId: ir.findingId, level: levelFor(ir), mode: 'detect',
229
230
  request: { method: ir.endpoint.method, pathTemplate: ir.endpoint.path },
230
231
  parameter: ir.parameter,
231
- payload: ir.payloadClass === 'auth-bypass' ? { class: 'auth-bypass', behavior: 'status 2xx with no credential' }
232
+ payload: ir.payloadClass === 'auth-bypass' ? { class: 'auth-bypass', behavior: 'status 2xx for a request that is not authenticated (no credential, or a rejected one)' }
233
+ : ir.payloadClass === 'object-authz' ? { class: 'object-authz', behavior: 'status 2xx for an authenticated actor whose tenant differs from the object\'s, with no cross-tenant grant', requiredFields: ['auth.outcome', 'actor.tenant', 'object.tenant', 'authz.cross_tenant_grant'] }
232
234
  : { class: ir.payloadClass, matcher: def.matcher, minLength: def.minLength ?? null, regex: classRegex(ir.payloadClass), flags: 'i' },
233
235
  limitations: ir.limitations,
234
236
  };
@@ -260,6 +262,7 @@ function logQuery(ir) {
260
262
  if (!ir.endpoint) return unavailable('no endpoint is known for this finding');
261
263
  const where = [{ field: 'http.request.method', op: 'eq', value: ir.endpoint.method }, hasTemplate(ir.endpoint.path) ? { field: 'url.path', op: 'regex', value: pathRegex(ir.endpoint.path) } : { field: 'url.path', op: 'eq', value: ir.endpoint.path }];
262
264
  if (ir.payloadClass === 'auth-bypass') where.push({ field: 'http.response.status_code', op: 'range', value: [200, 299] }, { field: 'http.request.headers.authorization', op: 'absent' });
265
+ else if (ir.payloadClass === 'object-authz') where.push({ field: 'http.response.status_code', op: 'range', value: [200, 299] }, { field: 'auth.outcome', op: 'eq', value: 'authenticated' }, { field: 'actor.tenant', op: 'ne-field', value: 'object.tenant' }, { field: 'authz.cross_tenant_grant', op: 'ne', value: true });
263
266
  else where.push({ field: ir.parameter?.location === 'body' ? 'http.request.body.content' : ir.parameter?.location === 'query' ? `url.query.${ir.parameter.name}` : 'url.query', op: 'regex', value: classRegex(ir.payloadClass), flags: 'i' });
264
267
  return { applicable: true, format: 'log-query', content: `${JSON.stringify({ schema: 'agentic-security/structured-log-query', version: 1, id: ir.id, findingId: ir.findingId, where }, null, 2)}\n` };
265
268
  }
@@ -308,8 +311,19 @@ const RENDERERS = { sigma, http, waf, 'log-query': logQuery, yara, spl, kql, clo
308
311
  function renderDetection(ir, format) {
309
312
  const fn = RENDERERS[format];
310
313
  if (!fn) return { applicable: false, reason: `unknown format: ${format}` };
311
- return { format, ...fn(ir) };
314
+ // Threat-model FR-07.4: a rule is published to a destination only when its field mapping carries what the rule needs. The
315
+ // object-authorization rule needs the actor's tenant, the object's tenant and any cross-tenant grant, which only this tool's own
316
+ // structured formats name; elsewhere it is reported as unavailable rather than rendered as a lossy predicate.
317
+ if (ir.payloadClass === 'object-authz' && !OBJECT_AUTHZ_FORMATS.has(format)) {
318
+ return { format, ...unavailable(`the ${format} destination's default fields carry no actor tenant, object tenant or cross-tenant grant, so the object-authorization rule cannot be expressed; use the http or log-query output with mapped fields`) };
319
+ }
320
+ const out = { format, ...fn(ir) };
321
+ // The rule languages see whether an Authorization header was sent, not whether it was accepted: the rendered rule covers the
322
+ // no-credential case only, and says so.
323
+ if (ir.payloadClass === 'auth-bypass' && out.applicable && format !== 'http') out.coverage = { covers: 'requests that sent no credential', notCovered: 'a credential that was sent and rejected: this destination has no authentication-outcome field' };
324
+ return out;
312
325
  }
326
+ const OBJECT_AUTHZ_FORMATS = new Set(['http', 'log-query']);
313
327
 
314
328
  function renderAll(ir) {
315
329
  return Object.fromEntries(FORMATS.map((f) => [f, renderDetection(ir, f)]));
@@ -423,7 +437,13 @@ RENDERERS.elastic = elastic; RENDERERS.yaral = yaral; RENDERERS.datadog = datado
423
437
  // searches the logs. An event is the NORMALIZED request record produced by log-reader.js or by the validator.
424
438
  //
425
439
  // event = { time, method, path, query: {name: [values]}, bodyParams: {name: [values]} | null, headers: {name: value} | null,
426
- // status, authPresent: boolean | null }
440
+ // status, authPresent: boolean | null,
441
+ // credentialPresent: boolean | null, authOutcome: 'authenticated'|'unauthenticated'|'unknown'|'conflict',
442
+ // actorTenant, objectTenant, crossTenantGrant: boolean | null, authzDecision: 'allow'|'deny'|null }
443
+ //
444
+ // Authentication (threat-model FR-07.1): the outcome decides, not the header. A request whose credential was REJECTED is
445
+ // unauthenticated even though a header was sent; a header with no recorded outcome is undecidable, not "authenticated". An event
446
+ // that carries only the legacy `authPresent` (an older caller) is judged as before.
427
447
  //
428
448
  // A missing field is `undefined`/`null`, never an empty string: "the log did not record this" and "the value was empty" are
429
449
  // different facts, and only the first makes an event undecidable.
@@ -440,11 +460,25 @@ function pathMatches(template, actual) {
440
460
  return t.every((seg, i) => (/^(?::[A-Za-z_]\w*|\{[A-Za-z_]\w*\})$/.test(seg) ? a[i].length > 0 : seg === a[i]));
441
461
  }
442
462
 
463
+ /**
464
+ * The authentication fact a detection may rely on: 'authenticated', 'unauthenticated' or 'unknown'. An explicit outcome wins; with
465
+ * no outcome, a request that carried NO credential is unauthenticated, and one that carried a credential is unknown (it may have
466
+ * been accepted or rejected). An event with only the legacy `authPresent` keeps its legacy reading.
467
+ */
468
+ function authenticationOf(event) {
469
+ const legacyOnly = event.authOutcome === undefined && event.credentialPresent === undefined;
470
+ if (legacyOnly) return event.authPresent === false ? 'unauthenticated' : event.authPresent === true ? 'authenticated' : 'unknown';
471
+ if (event.authOutcome === 'authenticated' || event.authOutcome === 'unauthenticated') return event.authOutcome;
472
+ if (event.authOutcome === 'conflict') return 'unknown';
473
+ return event.credentialPresent === false ? 'unauthenticated' : 'unknown';
474
+ }
475
+
443
476
  /** Fields an event must have for a detection to be able to say "no" about it. */
444
477
  function requiredFields(ir) {
445
478
  const need = new Set();
446
479
  if (ir.endpoint) { need.add('method'); need.add('path'); }
447
480
  if (ir.payloadClass === 'auth-bypass') { need.add('status'); need.add('auth'); return [...need]; }
481
+ if (ir.payloadClass === 'object-authz') { need.add('status'); need.add('auth'); need.add('tenant'); return [...need]; }
448
482
  const loc = ir.parameter?.location;
449
483
  if (loc === 'body') need.add('body');
450
484
  else if (loc === 'header' || loc === 'cookie') need.add('headers');
@@ -493,10 +527,30 @@ function evaluateEvent(ir, event) {
493
527
  } else if (typeof event.path !== 'string') { res.missing.push('path'); res.undecidable = true; return res; }
494
528
 
495
529
  if (ir.payloadClass === 'auth-bypass') {
530
+ const auth = authenticationOf(event);
496
531
  if (event.status == null) res.missing.push('status');
497
- if (event.authPresent == null) res.missing.push('auth');
532
+ if (auth === 'unknown') res.missing.push('auth');
498
533
  if (res.missing.length) { res.undecidable = true; return res; }
499
- if (event.status >= 200 && event.status < 300 && event.authPresent === false) { res.match = true; res.matchedOn = 'unauthenticated-success'; res.value = `${event.status}`; }
534
+ // a matched line is a SIGNAL that the response indicated success; delivery needs a separately bound observer
535
+ if (event.status >= 200 && event.status < 300 && auth === 'unauthenticated') { res.match = true; res.matchedOn = 'unauthenticated-success'; res.value = `${event.status}`; res.signal = 'response-indication'; }
536
+ return res;
537
+ }
538
+
539
+ if (ir.payloadClass === 'object-authz') {
540
+ // FR-07.2: an AUTHENTICATED actor whose tenant differs from the object's, with no explicit cross-tenant grant, and a success
541
+ // response. A denial (401/403 or a recorded deny) is an attempt, never a delivery. An unauthenticated success is the auth-bypass
542
+ // rule's case, not this one.
543
+ const auth = authenticationOf(event);
544
+ if (event.status == null) res.missing.push('status');
545
+ if (auth === 'unknown') res.missing.push('auth');
546
+ // an unauthenticated request has no actor tenant to compare: it is decided (not this rule's case), not undecidable
547
+ if (auth !== 'unauthenticated' && (event.actorTenant == null || event.objectTenant == null)) res.missing.push('tenant');
548
+ if (res.missing.length) { res.undecidable = true; return res; }
549
+ if (auth !== 'authenticated' || event.actorTenant === event.objectTenant) return res;
550
+ const denied = event.status === 401 || event.status === 403 || event.authzDecision === 'deny';
551
+ if (denied) { res.attempt = 'denied'; return res; }
552
+ if (event.crossTenantGrant === true) { res.attempt = 'authorized-delegation'; return res; }
553
+ if (event.status >= 200 && event.status < 300) { res.match = true; res.matchedOn = 'cross-tenant-success'; res.value = `${event.status}`; res.signal = 'response-indication'; }
500
554
  return res;
501
555
  }
502
556
 
@@ -522,10 +576,10 @@ function evaluateEvent(ir, event) {
522
576
 
523
577
  const PROBE = 'probe';
524
578
 
525
- function eventFor(ir, { method, path, values, extraParam = null, status = 200, authPresent = null }) {
579
+ function eventFor(ir, { method, path, values, extraParam = null, status = 200, authPresent = null, facts = null }) {
526
580
  const p = ir.parameter;
527
581
  const bag = {};
528
- const ev = { time: '2024-01-01T00:00:00.000Z', method, path, query: {}, bodyParams: {}, headers: {}, status, authPresent };
582
+ const ev = { time: '2024-01-01T00:00:00.000Z', method, path, query: {}, bodyParams: {}, headers: {}, status, authPresent, ...(facts || {}) };
529
583
  const put = (loc, name, vals) => {
530
584
  if (loc === 'query') ev.query[name] = vals;
531
585
  else if (loc === 'body') ev.bodyParams[name] = vals;
@@ -551,14 +605,27 @@ function validateDetection(ir) {
551
605
  if (!ir.endpoint) return { schema: 'agentic-security/detection-validation', version: 1, detectionId: ir.id, status: 'NOT_VALIDATED', reason: 'no endpoint is known, so there is no request to synthesize', cases, render, counts: { positives: 0, negatives: 0, passed: 0, failed: 0 } };
552
606
  const concretePath = ir.endpoint.path.replace(/(:[A-Za-z_]\w*|\{[A-Za-z_]\w*\})/g, '42');
553
607
  const { method } = ir.endpoint;
554
- const add = (kind, name, ev) => { const r = evaluateEvent(ir, ev); cases.push({ kind, name, expected: kind === 'positive', fired: r.match, undecidable: r.undecidable, ok: kind === 'positive' ? r.match : !r.match }); };
608
+ // `undecidable` cases (threat-model FR-07.4): the rule must refuse to answer, neither fire nor clear, when a required fact is missing
609
+ const add = (kind, name, ev) => { const r = evaluateEvent(ir, ev); cases.push({ kind, name, expected: kind === 'positive' ? true : kind === 'undecidable' ? 'undecidable' : false, fired: r.match, undecidable: r.undecidable, ok: kind === 'positive' ? r.match : kind === 'undecidable' ? r.undecidable && !r.match : !r.match }); };
610
+ const auth = (credentialPresent, authOutcome, extra = {}) => ({ credentialPresent, authOutcome, actorTenant: null, objectTenant: null, crossTenantGrant: null, authzDecision: null, ...extra });
555
611
 
556
612
  if (ir.payloadClass === 'auth-bypass') {
557
613
  add('positive', 'success with no credential', eventFor(ir, { method, path: concretePath, status: 200, authPresent: false }));
614
+ add('positive', 'success after a credential was sent and rejected', eventFor(ir, { method, path: concretePath, status: 200, facts: auth(true, 'unauthenticated') }));
558
615
  add('negative', 'success with a credential', eventFor(ir, { method, path: concretePath, status: 200, authPresent: true }));
616
+ add('negative', 'success with an accepted credential', eventFor(ir, { method, path: concretePath, status: 200, facts: auth(true, 'authenticated') }));
559
617
  add('negative', 'refusal without a credential (401)', eventFor(ir, { method, path: concretePath, status: 401, authPresent: false }));
560
618
  add('negative', 'refusal without a credential (403)', eventFor(ir, { method, path: concretePath, status: 403, authPresent: false }));
561
619
  add('negative', 'same request on another path', eventFor(ir, { method, path: '/other/unrelated', status: 200, authPresent: false }));
620
+ add('undecidable', 'a credential header with no recorded outcome', eventFor(ir, { method, path: concretePath, status: 200, facts: auth(true, 'unknown') }));
621
+ } else if (ir.payloadClass === 'object-authz') {
622
+ const t = (actorTenant, objectTenant, extra = {}) => auth(true, 'authenticated', { actorTenant, objectTenant, ...extra });
623
+ add('positive', 'an authenticated actor receives another tenant\'s object', eventFor(ir, { method, path: concretePath, status: 200, facts: t('tenant-a', 'tenant-b') }));
624
+ add('negative', 'the rightful tenant reads its own object', eventFor(ir, { method, path: concretePath, status: 200, facts: t('tenant-b', 'tenant-b') }));
625
+ add('negative', 'a cross-tenant read under an explicit grant', eventFor(ir, { method, path: concretePath, status: 200, facts: t('tenant-a', 'tenant-b', { crossTenantGrant: true }) }));
626
+ add('negative', 'a cross-tenant attempt that was denied (403)', eventFor(ir, { method, path: concretePath, status: 403, facts: t('tenant-a', 'tenant-b', { authzDecision: 'deny' }) }));
627
+ add('negative', 'the same cross-tenant read on another path', eventFor(ir, { method, path: '/other/unrelated', status: 200, facts: t('tenant-a', 'tenant-b') }));
628
+ add('undecidable', 'no object tenant recorded', eventFor(ir, { method, path: concretePath, status: 200, facts: t('tenant-a', null) }));
562
629
  } else {
563
630
  for (const [i, v] of def.exemplars.positive.entries()) add('positive', `exploit payload #${i + 1}`, eventFor(ir, { method, path: concretePath, values: [v] }));
564
631
  for (const [i, v] of def.exemplars.benign.entries()) add('negative', `benign value #${i + 1} on the same endpoint`, eventFor(ir, { method, path: concretePath, values: [v] }));
@@ -569,7 +636,7 @@ function validateDetection(ir) {
569
636
  add('negative', 'same endpoint, no parameter at all', eventFor(ir, { method, path: concretePath }));
570
637
  if (ir.parameter && ir.parameter.location !== 'path') add('negative', 'exploit payload in an unrelated parameter', eventFor(ir, { method, path: concretePath, values: exploit, extraParam: 'unrelated_field' }));
571
638
  }
572
- const positives = cases.filter((c) => c.kind === 'positive'); const negatives = cases.filter((c) => c.kind === 'negative');
639
+ const positives = cases.filter((c) => c.kind === 'positive'); const negatives = cases.filter((c) => c.kind !== 'positive');
573
640
  const failed = cases.filter((c) => !c.ok);
574
641
  const status = failed.length === 0 && positives.length && negatives.length && render.regexCompiles ? 'VALIDATED' : 'FAILED';
575
642
  return { schema: 'agentic-security/detection-validation', version: 1, detectionId: ir.id, status, cases, render, counts: { positives: positives.length, negatives: negatives.length, passed: cases.length - failed.length, failed: failed.length }, failures: failed.map((c) => c.name) };
@@ -680,19 +747,61 @@ function parseLine(line) {
680
747
  }
681
748
  const authHeader = headers.authorization ?? pick(f, ['authorization']);
682
749
  const authFlag = pick(f, ['authenticated', 'is_authenticated', 'auth.present']);
750
+ // Legacy: one boolean where a header wins over everything. Kept for existing callers; detections read the two facts below.
683
751
  const authPresent = authHeader != null ? true : typeof authFlag === 'boolean' ? authFlag : (sawHeaders ? false : null);
684
752
  const statusRaw = pick(f, ['status', 'status_code', 'response_code', 'http.response.status_code', 'sc-status', 'response.status', 'res.statuscode']);
685
753
  const status = Number.isInteger(Number(statusRaw)) && statusRaw !== undefined ? Number(statusRaw) : null;
686
- return { time: parseTime(pick(f, ['@timestamp', 'timestamp', 'time', 'ts', 'start_time', 'datetime', 'date'])), method: typeof m === 'string' ? m.toUpperCase() : null, path: tgt.path ?? null, query: query ?? null, bodyParams, headers: sawHeaders ? headers : null, status, authPresent };
754
+ return { time: parseTime(pick(f, ['@timestamp', 'timestamp', 'time', 'ts', 'start_time', 'datetime', 'date'])), method: typeof m === 'string' ? m.toUpperCase() : null, path: tgt.path ?? null, query: query ?? null, bodyParams, headers: sawHeaders ? headers : null, status, authPresent, ...authFacts(f, authHeader, sawHeaders) };
687
755
  }
688
756
  const c = CLF.exec(text);
689
757
  if (c) {
690
758
  const { path: p, query } = fromRequestTarget(c[5]);
691
- return { time: parseTime(c[3]), method: c[4], path: p, query, bodyParams: null, headers: null, status: c[6] === '-' ? null : Number(c[6]), authPresent: null };
759
+ return { time: parseTime(c[3]), method: c[4], path: p, query, bodyParams: null, headers: null, status: c[6] === '-' ? null : Number(c[6]), authPresent: null, ...UNKNOWN_AUTH };
692
760
  }
693
761
  return null;
694
762
  }
695
763
 
764
+ // ── authentication and authorization facts (PRD threat-model FR-07.1, FR-07.5) ──────────────────────────────────────────────────────
765
+ // Credential PRESENCE and the verified authentication OUTCOME are separate facts. A header only says a credential was sent; an
766
+ // authoritative `authenticated: false` (or an auth result of "rejected") says it was refused, and survives a redacted header. Missing or
767
+ // conflicting outcome fields leave the outcome `unknown`. Identity and policy fields (actor tenant, object tenant, a cross-tenant grant,
768
+ // the authorization decision) are read only from these mapped field names, never from request headers, query or body, which the client
769
+ // controls.
770
+ const OUTCOME_FIELDS = ['authenticated', 'is_authenticated', 'auth.authenticated', 'auth.outcome', 'auth.result', 'authn.outcome', 'authn.result'];
771
+ const ACTOR_TENANT_FIELDS = ['identity.tenant', 'actor.tenant', 'auth.tenant', 'principal.tenant'];
772
+ const OBJECT_TENANT_FIELDS = ['object.tenant', 'resource.tenant', 'record.tenant'];
773
+ const GRANT_FIELDS = ['authz.cross_tenant_grant', 'authz.delegated', 'authz.crossTenantGrant'];
774
+ const DECISION_FIELDS = ['authz.decision', 'authz.result'];
775
+ const UNKNOWN_AUTH = Object.freeze({ credentialPresent: null, authOutcome: 'unknown', actorTenant: null, objectTenant: null, crossTenantGrant: null, authzDecision: null });
776
+
777
+ function outcomeOf(v) {
778
+ if (v === true) return 'authenticated';
779
+ if (v === false) return 'unauthenticated';
780
+ const s = String(v ?? '').trim().toLowerCase();
781
+ if (/^(?:true|success|succeeded|ok|authenticated|allow|allowed|valid)$/.test(s)) return 'authenticated';
782
+ if (/^(?:false|failure|failed|rejected|denied|invalid|unauthenticated|anonymous|none|expired)$/.test(s)) return 'unauthenticated';
783
+ return null;
784
+ }
785
+ const ident = (v) => (v === undefined || v === null || v === '' ? null : String(v).slice(0, 128));
786
+
787
+ function authFacts(f, authHeader, sawHeaders) {
788
+ const outcomes = new Set(OUTCOME_FIELDS.map((k) => f[k]).filter((v) => v !== undefined).map(outcomeOf).filter(Boolean));
789
+ const authOutcome = outcomes.size === 1 ? [...outcomes][0] : 'unknown';
790
+ const presentFlag = f['auth.present'];
791
+ const credentialPresent = authHeader != null ? true : typeof presentFlag === 'boolean' ? presentFlag : (sawHeaders ? false : null);
792
+ const grant = GRANT_FIELDS.map((k) => f[k]).find((v) => v !== undefined);
793
+ const decision = DECISION_FIELDS.map((k) => f[k]).find((v) => v !== undefined);
794
+ const d = String(decision ?? '').toLowerCase();
795
+ return {
796
+ credentialPresent,
797
+ authOutcome: outcomes.size > 1 ? 'conflict' : authOutcome,
798
+ actorTenant: ident(pick(f, ACTOR_TENANT_FIELDS)),
799
+ objectTenant: ident(pick(f, OBJECT_TENANT_FIELDS)),
800
+ crossTenantGrant: grant === true || grant === 'true' ? true : grant === false || grant === 'false' ? false : null,
801
+ authzDecision: /^(?:allow|allowed|permit|granted)$/.test(d) ? 'allow' : /^(?:deny|denied|forbidden|refused)$/.test(d) ? 'deny' : null,
802
+ };
803
+ }
804
+
696
805
  function collectFiles(root, limits, skipped) {
697
806
  const out = [];
698
807
  const walk = (p, depth) => {
@@ -722,7 +831,7 @@ function collectFiles(root, limits, skipped) {
722
831
  */
723
832
  async function readLogs(target, { onEvent, signal, limits: over = {} } = {}) {
724
833
  const limits = { ...LIMITS, ...over };
725
- const stats = { files: [], filesSkipped: [], lines: 0, parsed: 0, failed: 0, oversizeLines: 0, windowStart: null, windowEnd: null, eventsWithTime: 0, fieldCounts: { method: 0, path: 0, query: 0, body: 0, headers: 0, status: 0, auth: 0 }, truncated: [], budgetExhausted: false, aborted: false, unreadable: false };
834
+ const stats = { files: [], filesSkipped: [], lines: 0, parsed: 0, failed: 0, oversizeLines: 0, windowStart: null, windowEnd: null, eventsWithTime: 0, fieldCounts: { method: 0, path: 0, query: 0, body: 0, headers: 0, status: 0, auth: 0, authOutcome: 0, tenant: 0 }, truncated: [], budgetExhausted: false, aborted: false, unreadable: false };
726
835
  let st;
727
836
  try { st = external_node_fs_.lstatSync(target); } catch { stats.unreadable = true; return stats; }
728
837
  if (st.isSymbolicLink()) { stats.unreadable = true; stats.filesSkipped.push({ file: external_node_path_.basename(target), reason: 'symlink' }); return stats; }
@@ -758,6 +867,8 @@ async function readLogs(target, { onEvent, signal, limits: over = {} } = {}) {
758
867
  if (ev.headers != null) stats.fieldCounts.headers++;
759
868
  if (ev.status != null) stats.fieldCounts.status++;
760
869
  if (ev.authPresent != null) stats.fieldCounts.auth++;
870
+ if (ev.authOutcome === 'authenticated' || ev.authOutcome === 'unauthenticated') stats.fieldCounts.authOutcome++;
871
+ if (ev.actorTenant != null && ev.objectTenant != null) stats.fieldCounts.tenant++;
761
872
  if (ev.time) { stats.eventsWithTime++; if (!stats.windowStart || ev.time < stats.windowStart) stats.windowStart = ev.time; if (!stats.windowEnd || ev.time > stats.windowEnd) stats.windowEnd = ev.time; }
762
873
  onEvent?.(ev);
763
874
  }
@@ -825,7 +936,10 @@ const STATEMENTS = {
825
936
  TELEMETRY_UNAVAILABLE: () => 'There were no readable request records to search, so nothing can be said about past exploitation.',
826
937
  };
827
938
 
828
- const FIELD_COUNT_KEY = { method: 'method', path: 'path', query: 'query', body: 'body', headers: 'headers', status: 'status', auth: 'auth' };
939
+ const FIELD_COUNT_KEY = { method: 'method', path: 'path', query: 'query', body: 'body', headers: 'headers', status: 'status', auth: 'auth', authOutcome: 'authOutcome', tenant: 'tenant' };
940
+
941
+ // A matched line shows the RESPONSE indicated success; delivery of the data is an effect that needs a separately bound observer.
942
+ const EFFECT = Object.freeze({ corroborated: false, basis: 'log-signal', reason: 'a matched log line is a detection signal; delivery or mutation needs a separately bound observer (runtime corroboration), which a log hunt does not have' });
829
943
 
830
944
  async function huntLogs({ ir, logsPath, since = null, until = null, signal, limits } = {}) {
831
945
  const base = { schema: 'agentic-security/hunt-result', version: 1, findingId: ir.findingId, detectionId: ir.id, detectionDigest: ir.digest, notExploitedClaim: false, egress: 'none' };
@@ -838,6 +952,10 @@ async function huntLogs({ ir, logsPath, since = null, until = null, signal, limi
838
952
  let matchCount = 0; let firstSeen = null; let lastSeen = null;
839
953
  let undecidable = 0; const undecidableFields = new Set();
840
954
  let endpointEvents = 0; let outOfWindow = 0; let undated = 0;
955
+ // Threat-model FR-07.3: the target counter uses the SAME method and route template as the matcher (a different route with the same
956
+ // method used to count), and separates the relevant events from those that could be judged and those missing a required field.
957
+ const target = { relevant: 0, adjudicable: 0, missingRequired: 0, deniedAttempts: 0, authorizedDelegations: 0 };
958
+ const atEndpoint = (ev) => !!ir.endpoint && typeof ev.method === 'string' && ev.method.toUpperCase() === ir.endpoint.method && pathMatches(ir.endpoint.path, ev.path);
841
959
 
842
960
  const stats = await readLogs(logsPath, {
843
961
  signal, limits,
@@ -848,19 +966,27 @@ async function huntLogs({ ir, logsPath, since = null, until = null, signal, limi
848
966
  if ((sinceMs != null && t < sinceMs) || (untilMs != null && t > untilMs)) { outOfWindow++; return; }
849
967
  }
850
968
  const r = evaluateEvent(ir, ev);
851
- if (r.undecidable) { undecidable++; r.missing.forEach((m) => undecidableFields.add(m)); return; }
852
- if (ir.endpoint && ev.method === ir.endpoint.method) endpointEvents++;
969
+ const here = atEndpoint(ev);
970
+ if (here) target.relevant++;
971
+ if (r.undecidable) { undecidable++; if (here) target.missingRequired++; r.missing.forEach((m) => undecidableFields.add(m)); return; }
972
+ if (here) { endpointEvents++; target.adjudicable++; }
973
+ if (r.attempt === 'denied') target.deniedAttempts++;
974
+ if (r.attempt === 'authorized-delegation') target.authorizedDelegations++;
853
975
  if (!r.match) return;
854
976
  matchCount++;
855
977
  if (ev.time) { if (!firstSeen || ev.time < firstSeen) firstSeen = ev.time; if (!lastSeen || ev.time > lastSeen) lastSeen = ev.time; }
856
- if (sampleList.length < MAX_SAMPLES) sampleList.push({ time: ev.time, method: ev.method, path: redactValue(ev.path, 160), matchedOn: r.matchedOn, valueFingerprint: fingerprint(r.value), valuePreview: redactValue(r.value, 80), status: ev.status });
978
+ if (sampleList.length < MAX_SAMPLES) sampleList.push({ time: ev.time, method: ev.method, path: redactValue(ev.path, 160), matchedOn: r.matchedOn, valueFingerprint: fingerprint(r.value), valuePreview: redactValue(r.value, 80), status: ev.status, ...(r.signal ? { signal: r.signal } : {}) });
857
979
  },
858
980
  });
859
981
 
860
982
  const logs = { source: external_node_path_.basename(String(logsPath)), files: stats.files.length, filesSkipped: stats.filesSkipped, lines: stats.lines, parsed: stats.parsed, failed: stats.failed, truncated: stats.truncated };
861
983
  const window = { start: stats.windowStart, end: stats.windowEnd, datedEvents: stats.eventsWithTime };
862
984
  const caveats = [];
863
- const result = (verdict) => ({ ...base, verdict, logs, window, coverage: { requestedWindow: { since, until }, outOfWindow, undated, endpointEvents, requiredFields: requiredFields(ir), fieldAvailability: Object.fromEntries(Object.entries(FIELD_COUNT_KEY).map(([k, key]) => [k, stats.parsed ? Number((stats.fieldCounts[key] / stats.parsed).toFixed(3)) : 0])) }, matches: { count: matchCount, firstSeen, lastSeen, samples: sampleList }, caveats, statement: STATEMENTS[verdict](matchCount) });
985
+ // A requested window the logs do not reach: the time before the first event or after the last cannot be searched, so it is a gap.
986
+ const windowGaps = [];
987
+ if (sinceMs != null && stats.windowStart && Date.parse(stats.windowStart) > sinceMs) windowGaps.push({ edge: 'start', from: new Date(sinceMs).toISOString(), to: stats.windowStart });
988
+ if (untilMs != null && stats.windowEnd && Date.parse(stats.windowEnd) < untilMs) windowGaps.push({ edge: 'end', from: stats.windowEnd, to: new Date(untilMs).toISOString() });
989
+ const result = (verdict) => ({ ...base, verdict, logs, window, effect: EFFECT, coverage: { requestedWindow: { since, until }, outOfWindow, undated, endpointEvents, target: { ...target, samples: sampleList.length, samplesCapped: matchCount > sampleList.length }, windowGaps, requiredFields: requiredFields(ir), fieldAvailability: Object.fromEntries(Object.entries(FIELD_COUNT_KEY).map(([k, key]) => [k, stats.parsed ? Number((stats.fieldCounts[key] / stats.parsed).toFixed(3)) : 0])) }, matches: { count: matchCount, firstSeen, lastSeen, samples: sampleList }, caveats, statement: STATEMENTS[verdict](matchCount) });
864
990
 
865
991
  if (stats.unreadable || stats.parsed === 0) {
866
992
  caveats.push(stats.unreadable ? 'the log path could not be read' : stats.lines === 0 ? 'the logs contain no lines' : 'no line was recognised as a request record (supported: common/combined access logs, JSON lines)');
@@ -878,6 +1004,7 @@ async function huntLogs({ ir, logsPath, since = null, until = null, signal, limi
878
1004
  if (!stats.eventsWithTime) caveats.push('no event carries a timestamp, so the searched time window is unknown');
879
1005
  if (stats.filesSkipped.some((f) => f.reason !== 'not-a-log-extension')) caveats.push(`${stats.filesSkipped.filter((f) => f.reason !== 'not-a-log-extension').length} file(s) were skipped (${[...new Set(stats.filesSkipped.map((f) => f.reason))].join(', ')})`);
880
1006
  if (undated > 0) caveats.push(`${undated} event(s) had no timestamp and were excluded by the requested window`);
1007
+ for (const g of windowGaps) caveats.push(`the logs do not reach the ${g.edge} of the requested window (${g.from} to ${g.to} is not covered), so nothing is known about that time`);
881
1008
  if (!caveats.length && ir.endpoint && endpointEvents === 0) caveats.push('no request to the endpoint appears in the logs at all (informational: nothing to exploit was seen either)');
882
1009
  const blocking = caveats.some((c) => !c.startsWith('no request to the endpoint'));
883
1010
  return result(blocking ? 'INCONCLUSIVE' : 'NOT_OBSERVED');
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clear-capabilities/agentic-security-scanner",
3
- "version": "0.195.28",
3
+ "version": "0.195.30",
4
4
  "description": "Scanner engine for the agentic-security Claude Code plugin: SAST, SCA with reachability and CISA KEV, secrets, IaC, Haskell and Nix/NixOS, prompt-injection, MCP/agent-tool audit, authZ analysis, SBOM, compliance attestation, and more.",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -87,14 +87,14 @@
87
87
  "test:ollama-e2e": "AGENTIC_SECURITY_OLLAMA_E2E=1 node --test test/ollama-e2e.test.js",
88
88
  "test:glob": "node --test test/glob-compat.test.js",
89
89
  "test:sast": "node --test test/dag-precision.test.js test/cpp-hardcoded-secret.test.js test/sibling-guard.test.js test/comment-blindness.test.js test/todo-marker-words.test.js test/crypto-specialist.test.js test/llm.test.js test/llm-cost-advisor.test.js test/llm-owasp.test.js test/logic.test.js test/authz.test.js test/model-load.test.js test/prompt-template.test.js test/business-logic.test.js test/python-sinks.test.js test/phase1-detectors.test.js test/phase2-detectors.test.js test/phase3-v3.test.js test/phase7-extensions.test.js test/phase8-extensions.test.js test/new-cwe-detectors.test.js test/file-upload.test.js test/llmsecops-detectors.test.js test/db-taint.test.js test/dart-swift.test.js test/zip-slip-containment-guard.test.js test/zip-slip-go.test.js test/redos-anchoring.test.js test/path-guard-validator-call.test.js test/redos-nfa.test.js test/weak-randomness.test.js test/csharp-pipeline.test.js test/csharp-cross-file.test.js test/post-quantum-crypto.test.js test/web3-advanced.test.js test/cloud-iam-k8s.test.js test/crypto-protocol.test.js test/ml-supply-chain.test.js test/mcp-audit-scan.test.js test/wrong-context-sanitizer.test.js test/sanitizer-context.test.js test/frontend-hygiene.test.js test/csv-injection.test.js test/stored-taint.test.js test/tree-sitter-sinks.test.js test/kotlin-structural.test.js test/ruby-php-structural.test.js test/java-csharp-structural.test.js test/java-structural-cross-file.test.js test/guard-recognition.test.js test/guard-window-identifier-correlation.test.js test/js-python-framework-structural.test.js test/go-structural.test.js test/secret-concat.test.js test/secret-redaction.test.js test/xss-reflected-multilang.test.js test/code-injection-multilang.test.js test/xxe-multilang.test.js test/xpath-injection-multilang.test.js test/gapfill-batch13.test.js test/agent-untrusted-flow.test.js test/api-authz.test.js test/event-entrypoint.test.js test/iac-terraform.test.js test/cross-service.test.js test/rbac-consistency.test.js test/adversarial-robustness.test.js test/secret-entropy.test.js test/comment-strip.test.js test/juliet-path-filter-gate.test.js test/java-bench-shape-opt-in.test.js test/vendor-java-parser-current.test.js test/java-ast-folding.test.js test/java-bench-extras.test.js test/reachability-missing-callgraph-data.test.js test/rate-limit.test.js test/auth-signals.test.js test/convention-deviation.test.js test/resource-exhaustion.test.js test/resource-exhaustion-realworld.test.js test/redirect-toctou.test.js test/redirect-toctou-realworld.test.js test/codegen-sink.test.js test/ownership-authz.test.js test/ownership-authz-realworld.test.js test/t21-precision-audit.test.js test/secrets-coverage.test.js test/prompt-injection-payloads.test.js test/iac-cloud-templates.test.js test/ruby-path-join.test.js test/sast/manifest-line-tracking.test.js test/rust-sast.test.js test/java-deserialization-precision.test.js test/pipeline-env-secret.test.js test/mark-used-vuln-functions.test.js",
90
- "test:posture": "node --test test/contracts/finding-provenance.contract.test.js test/contracts/root-cause-sweep.contract.test.js test/contracts/model-validation.contract.test.js test/contracts/agentic-discovery.contract.test.js test/contracts/local-models.contract.test.js test/contracts/dependency-finding.contract.test.js test/contracts/taint-flow.contract.test.js test/contracts/runtime-corroboration.contract.test.js test/compliance-coverage.test.js test/contracts/blast-radius.contract.test.js test/contracts/federation.contract.test.js test/contracts/eval-lab-report.contract.test.js test/contracts/fleet.contract.test.js test/contracts/rule-compiler.contract.test.js test/contracts/release-assurance.contract.test.js test/contracts/dnr.contract.test.js test/contracts/strategic-metrics.contract.test.js test/contracts/exploit-proof.contract.test.js test/contracts/artifact-provenance.contract.test.js test/dnr/artifact-join.test.js test/rule-pack-signing.test.js test/threat-model.test.js test/git-hardening.test.js test/privacy-framework.test.js test/material-change.test.js test/drift.test.js test/scorecard.test.js test/accuracy-scorecard.test.js test/scorecard-gate.test.js test/predeploy-gate.test.js test/release-check.test.js test/pre-push-gate.test.js test/dependency-currency.test.js test/package-contents-check.test.js test/gate-verdict-cache.test.js test/run-unit-tests.test.js test/temp-leak-guard.test.js test/release-workflow.test.js test/release-workflow-structure.test.js test/independent-population.test.js test/why-missed.test.js test/independent-scoring.test.js test/evidence-bundle.test.js test/obligation-evidence-pack.test.js test/policy-bundle.test.js test/discovery-memory.test.js test/mttr.test.js test/license-policy.test.js test/aibom.test.js test/sbom.test.js test/api-inventory.test.js test/iam-policy.test.js test/container.test.js test/container-runtime.test.js test/image-packages.test.js test/kev.test.js test/dep-confusion.test.js test/sca-deprecated.test.js test/sca-batch.test.js test/composite-risk.test.js test/epss-enrich.test.js test/sca-coverage.test.js test/gradle-deps.test.js test/sca-route-reachable.test.js test/sca-policy.test.js test/sca-verdict.test.js test/install-script.test.js test/sca-linked-findings.test.js test/packs.test.js test/flow-narration.test.js test/regression-test-gen.test.js test/deterministic-fix.test.js test/falsification.test.js test/verification-separation.test.js test/logic-claims.test.js test/attestation.test.js test/verify-attestation-cli.test.js test/comparison.test.js test/determinism-cross-machine.test.js test/fix-honesty-gate.test.js test/model-routing.test.js test/root-cause-sweep.test.js test/entrypoint-inventory.test.js test/relevance.test.js test/untrusted.test.js test/agent-hardening.test.js test/rule-synthesis.test.js test/policy-gate.test.js test/agents-memory.test.js test/cve-lookup.test.js test/cve-alert-daemon.test.js test/fix-verify-loop.test.js test/fix-verify-tests.test.js test/fix-acceptance.test.js test/exploitability-probability.test.js test/history-scan.test.js test/viral-features.test.js test/viral-v074.test.js test/state-dir.test.js test/ignore-pragma.test.js test/license-graph.test.js test/secret-live-check.test.js test/attack-taxonomy.test.js test/triage-memory.test.js test/pr-augment.test.js test/chat-batch2.test.js test/chat-batch3.test.js test/chat-batch4.test.js test/chat-batch5.test.js test/chat-batch6.test.js test/risk-scenario-disclosure.test.js test/egress-policy.test.js test/egress-transport.test.js test/local-preset-callers.test.js test/egress-policy-completeness.test.js test/egress-policy-integration.test.js test/router.test.js test/legacy-alias-redirect.test.js test/cache-economics.test.js test/coverage-report.test.js test/corpus-status.test.js test/provenance.test.js test/secret-history.test.js test/execution-proof.test.js test/fix-metrics.test.js test/corpus-enroll.test.js test/prove-findings.test.js test/corpus-match.test.js test/corpus-provenance.test.js test/learning-quorum.test.js test/llm-cache-integrity.test.js test/scope/inventory.test.js test/scope/readtree-scope.test.js test/scope/cli-scope.test.js test/scope/watch-scope.test.js test/workflow-flow.test.js test/oidc-trust.test.js test/haskell-coverage/coverage.test.js test/validation-disposition.test.js test/claim-groups.test.js test/validation-bounded-context.test.js test/evidence-record.test.js test/report-disposition-view.test.js test/sca-basis.test.js test/proof-artifact.test.js test/providers.test.js test/fleet.test.js test/autopilot.test.js test/autopilot-cli.test.js test/verifier-independence.test.js test/integrity-legacy-key.test.js test/suppression-visibility.test.js test/cost-ceiling.test.js test/local-endpoint.test.js test/ollama-provider.test.js test/ollama-capabilities.test.js test/ollama-offline-egress.test.js test/ollama-fix-proposal.test.js test/ollama-explain-proposal.test.js test/ollama-poc-proposal.test.js test/ollama-model-probe.test.js test/ollama-agent-tools.test.js test/ollama-agent-loop.test.js test/ollama-e2e.test.js test/ollama-oom-feedback.test.js test/ollama-doc-drift.test.js test/model-trust.test.js test/vuln-archaeology.test.js test/scan-checkpoint.test.js test/llm-redact.test.js test/world-class-batch2.test.js test/api-contract-scope.test.js test/world-class-modules.test.js test/privacy-ir-adapter.test.js test/privacy-taxonomy.test.js test/privacy-sink-policy.test.js test/privacy-governance.test.js test/privacy-inventory.test.js test/integration-end-to-end.test.js test/streak-cli-wiring.test.js test/stack-playbook.test.js test/machine-output-flag.test.js test/stdout-flush.test.js test/format-determinism.test.js test/only-flag-channels.test.js test/confidence.test.js test/security-trend.test.js test/calibration-drift.test.js test/validator-metrics.test.js test/clustering-ir-taint-line.test.js test/concurrency-cwe.test.js test/compliance-mapping-liveness.test.js test/module-artifact-liveness.test.js test/supply-chain-provenance-completeness.test.js test/compliance-severity-threshold.test.js test/compliance-severity-policy.test.js test/compliance-evidence-binding.test.js test/compliance-evidence-signing.test.js test/compliance-file-contains.test.js test/label-isolation.test.js test/evidence-grade-wording.test.js test/llm-model-status.test.js test/egress-policy-constraints.test.js test/egress-redact.test.js test/egress-audit.test.js test/egress-approved-providers.test.js test/family-registry.test.js test/coverage-strength.test.js test/framework-provenance.test.js test/framework-provenance-controlrefs.test.js test/third-party-verification.test.js test/proof-coverage.test.js test/calibration-holdout.test.js test/independent-population-gate.test.js test/catalog-freshness.test.js test/incremental-parity.test.js test/surface-smoke.test.js test/fix-coverage.test.js test/raw-source-carveout.test.js test/mlbom-conformance.test.js test/deploy-gate-replay.test.js test/sbom-conformance.test.js test/unscored-claims.test.js test/mcp-rug-pull.test.js test/agent-boundary-taint.test.js test/confinement-adversarial.test.js test/comment-strip-cost.test.js test/reachability-claim.test.js test/entrypoint-breadth.test.js test/dep-file-admission.test.js test/custom-rules-freshness.test.js test/posture/provenance-schema.test.js test/posture/provenance-cache.test.js test/helpers/build-git-fixture.test.js test/posture/provenance-git-evidence.test.js test/posture/provenance-dag-walk.test.js test/posture/provenance-confidence.test.js test/posture/provenance-predicate-replay.test.js test/posture/provenance-origin-resolver.test.js test/posture/provenance-ai-authorship.test.js test/posture/provenance-branch-entry.test.js test/posture/provenance-evidence-attribution.test.js test/posture/provenance-lifecycle.test.js test/posture/provenance-missing-control-resolver.test.js test/posture/provenance-missing-control-wiring.test.js test/posture/provenance-coordinator.test.js test/posture/provenance-sca-origin.test.js test/posture/provenance-transitive-sca.test.js test/posture/provenance-coordinator-sca.test.js test/posture/provenance-providers.test.js test/posture/provenance-evidence-bundle.test.js test/posture/provenance-repo-lineage.test.js test/posture/provenance-secrets-logic.test.js test/cli/provenance-flags.test.js test/cli/attest-provenance.test.js test/cli/attest-obligations.test.js test/cli/models.test.js test/cli/setup-llm.test.js test/cli/fix-retry.test.js test/cli/compliance-walkthrough-lineage.test.js test/cli/dataflow-export-privacy.test.js test/cli/dataflow-export-briefing.test.js test/cli/lineage-snapshot-persist.test.js test/cli/dataflow-diff.test.js test/cli/dataflow-watch.test.js test/cli/dataflow-recipients.test.js test/cli/dataflow-coverage.test.js test/security/provenance-safety.test.js test/security/provenance-injection.test.js test/posture/remediation-ledger.test.js test/posture/assurance-baseline.test.js test/posture/assurance-contracts.test.js test/posture/assurance-config.test.js test/evidence-issuer.test.js test/load-factor.test.js test/fleet/job-store.test.js test/fleet/enterprise-graph.test.js test/fleet/discover-edges.test.js test/fleet/code-boundaries.test.js test/fleet/job-runner.test.js test/fleet/accounting.test.js test/fleet/fleet-org-cli.test.js test/eval-lab/dataset-neutralize.test.js test/eval-lab/arms-score-stats.test.js test/eval-lab/run-report.test.js test/eval-lab/router.test.js test/eval-lab/arm-ollama.test.js test/eval-lab/import-independent.test.js test/cli/eval-lab-cli.test.js test/dnr/ir-and-outputs.test.js test/dnr/logs-and-hunt.test.js test/dnr/window-store-view.test.js test/dnr/exploit-correlation.test.js test/cli/dnr-cli.test.js test/proof-fabric/core.test.js test/proof-fabric/container.test.js test/proof-fabric/generate.test.js test/proof-fabric/siblings.test.js test/proof-fabric/generate-python.test.js test/proof-fabric/generate-haskell.test.js test/proof-fabric/generate-haskell-widened.test.js test/cli/proof-lifecycle-demo.test.js test/mcp-proof.test.js test/proof-fabric/fix-store-runner.test.js test/proof-fabric/fixtures.test.js test/cli/proof-fabric-cli.test.js test/rule-compiler/core.test.js test/rule-compiler/gates.test.js test/rule-compiler/ci-regression.test.js test/rule-compiler/synthesis.test.js test/rule-compiler/structure.test.js test/cli/rule-compiler-cli.test.js test/fleet/fleet-graph.test.js test/fleet/fleet-manifest.test.js test/fleet/fleet-cli.test.js test/artifact/safe-tar.test.js test/artifact/image-archive.test.js test/artifact/filesystem.test.js test/artifact/layer-secrets.test.js test/artifact/inventory.test.js test/artifact/vuln-correlate.test.js test/artifact/signature.test.js test/artifact/provenance-graph.test.js test/artifact/mismatch.test.js test/artifact/vex.test.js test/artifact/registry.test.js test/artifact/sources-cosign-evidence.test.js test/cli/artifact-sources-cli.test.js test/cli/artifact-provenance-cli.test.js test/witness-bundle.test.js",
90
+ "test:posture": "node --test test/contracts/finding-provenance.contract.test.js test/contracts/root-cause-sweep.contract.test.js test/contracts/model-validation.contract.test.js test/contracts/agentic-discovery.contract.test.js test/contracts/local-models.contract.test.js test/contracts/dependency-finding.contract.test.js test/contracts/taint-flow.contract.test.js test/contracts/runtime-corroboration.contract.test.js test/compliance-coverage.test.js test/contracts/blast-radius.contract.test.js test/contracts/federation.contract.test.js test/contracts/eval-lab-report.contract.test.js test/contracts/fleet.contract.test.js test/contracts/rule-compiler.contract.test.js test/contracts/release-assurance.contract.test.js test/contracts/dnr.contract.test.js test/contracts/strategic-metrics.contract.test.js test/contracts/exploit-proof.contract.test.js test/contracts/artifact-provenance.contract.test.js test/dnr/artifact-join.test.js test/rule-pack-signing.test.js test/threat-model.test.js test/git-hardening.test.js test/privacy-framework.test.js test/material-change.test.js test/drift.test.js test/scorecard.test.js test/accuracy-scorecard.test.js test/scorecard-gate.test.js test/predeploy-gate.test.js test/release-check.test.js test/pre-push-gate.test.js test/dependency-currency.test.js test/package-contents-check.test.js test/gate-verdict-cache.test.js test/run-unit-tests.test.js test/temp-leak-guard.test.js test/release-workflow.test.js test/release-workflow-structure.test.js test/independent-population.test.js test/why-missed.test.js test/independent-scoring.test.js test/evidence-bundle.test.js test/obligation-evidence-pack.test.js test/policy-bundle.test.js test/discovery-memory.test.js test/mttr.test.js test/license-policy.test.js test/aibom.test.js test/sbom.test.js test/api-inventory.test.js test/iam-policy.test.js test/container.test.js test/container-runtime.test.js test/image-packages.test.js test/kev.test.js test/dep-confusion.test.js test/sca-deprecated.test.js test/sca-batch.test.js test/composite-risk.test.js test/epss-enrich.test.js test/sca-coverage.test.js test/gradle-deps.test.js test/sca-route-reachable.test.js test/sca-policy.test.js test/sca-verdict.test.js test/install-script.test.js test/sca-linked-findings.test.js test/packs.test.js test/flow-narration.test.js test/regression-test-gen.test.js test/deterministic-fix.test.js test/falsification.test.js test/verification-separation.test.js test/verification-independence.test.js test/candidate-qualification.test.js test/logic-claims.test.js test/attestation.test.js test/verify-attestation-cli.test.js test/comparison.test.js test/determinism-cross-machine.test.js test/fix-honesty-gate.test.js test/model-routing.test.js test/root-cause-sweep.test.js test/entrypoint-inventory.test.js test/relevance.test.js test/untrusted.test.js test/agent-hardening.test.js test/rule-synthesis.test.js test/policy-gate.test.js test/agents-memory.test.js test/cve-lookup.test.js test/cve-alert-daemon.test.js test/fix-verify-loop.test.js test/fix-verify-tests.test.js test/fix-acceptance.test.js test/exploitability-probability.test.js test/history-scan.test.js test/viral-features.test.js test/viral-v074.test.js test/state-dir.test.js test/ignore-pragma.test.js test/license-graph.test.js test/secret-live-check.test.js test/attack-taxonomy.test.js test/triage-memory.test.js test/triage-memory-scoped.test.js test/sibling-queue.test.js test/pr-augment.test.js test/chat-batch2.test.js test/chat-batch3.test.js test/chat-batch4.test.js test/chat-batch5.test.js test/chat-batch6.test.js test/risk-scenario-disclosure.test.js test/egress-policy.test.js test/egress-profile.test.js test/mcp-approval-binding.test.js test/egress-transport.test.js test/local-preset-callers.test.js test/egress-policy-completeness.test.js test/egress-policy-integration.test.js test/router.test.js test/legacy-alias-redirect.test.js test/cache-economics.test.js test/coverage-report.test.js test/corpus-status.test.js test/provenance.test.js test/secret-history.test.js test/execution-proof.test.js test/fix-metrics.test.js test/corpus-enroll.test.js test/prove-findings.test.js test/corpus-match.test.js test/corpus-provenance.test.js test/learning-quorum.test.js test/llm-cache-integrity.test.js test/scope/inventory.test.js test/scope/readtree-scope.test.js test/scope/cli-scope.test.js test/scope/scope-evidence.test.js test/scope/watch-scope.test.js test/workflow-flow.test.js test/oidc-trust.test.js test/haskell-coverage/coverage.test.js test/validation-disposition.test.js test/claim-groups.test.js test/validation-bounded-context.test.js test/evidence-record.test.js test/report-disposition-view.test.js test/sca-basis.test.js test/proof-artifact.test.js test/providers.test.js test/fleet.test.js test/autopilot.test.js test/autopilot-cli.test.js test/verifier-independence.test.js test/integrity-legacy-key.test.js test/suppression-visibility.test.js test/cost-ceiling.test.js test/local-endpoint.test.js test/ollama-provider.test.js test/ollama-capabilities.test.js test/ollama-offline-egress.test.js test/ollama-fix-proposal.test.js test/ollama-explain-proposal.test.js test/ollama-poc-proposal.test.js test/ollama-model-probe.test.js test/ollama-agent-tools.test.js test/ollama-agent-loop.test.js test/ollama-e2e.test.js test/ollama-oom-feedback.test.js test/ollama-doc-drift.test.js test/model-trust.test.js test/vuln-archaeology.test.js test/scan-checkpoint.test.js test/llm-redact.test.js test/world-class-batch2.test.js test/api-contract-scope.test.js test/world-class-modules.test.js test/privacy-ir-adapter.test.js test/privacy-taxonomy.test.js test/privacy-sink-policy.test.js test/privacy-governance.test.js test/privacy-inventory.test.js test/integration-end-to-end.test.js test/streak-cli-wiring.test.js test/stack-playbook.test.js test/machine-output-flag.test.js test/stdout-flush.test.js test/format-determinism.test.js test/only-flag-channels.test.js test/confidence.test.js test/security-trend.test.js test/calibration-drift.test.js test/validator-metrics.test.js test/clustering-ir-taint-line.test.js test/concurrency-cwe.test.js test/compliance-mapping-liveness.test.js test/module-artifact-liveness.test.js test/supply-chain-provenance-completeness.test.js test/compliance-severity-threshold.test.js test/compliance-severity-policy.test.js test/compliance-evidence-binding.test.js test/compliance-evidence-signing.test.js test/compliance-file-contains.test.js test/label-isolation.test.js test/evidence-grade-wording.test.js test/llm-model-status.test.js test/egress-policy-constraints.test.js test/egress-redact.test.js test/egress-audit.test.js test/egress-approved-providers.test.js test/family-registry.test.js test/coverage-strength.test.js test/framework-provenance.test.js test/framework-provenance-controlrefs.test.js test/third-party-verification.test.js test/proof-coverage.test.js test/calibration-holdout.test.js test/independent-population-gate.test.js test/catalog-freshness.test.js test/incremental-parity.test.js test/surface-smoke.test.js test/fix-coverage.test.js test/raw-source-carveout.test.js test/mlbom-conformance.test.js test/deploy-gate-replay.test.js test/sbom-conformance.test.js test/unscored-claims.test.js test/mcp-rug-pull.test.js test/agent-boundary-taint.test.js test/confinement-adversarial.test.js test/comment-strip-cost.test.js test/reachability-claim.test.js test/entrypoint-breadth.test.js test/dep-file-admission.test.js test/custom-rules-freshness.test.js test/posture/provenance-schema.test.js test/posture/provenance-cache.test.js test/helpers/build-git-fixture.test.js test/posture/provenance-git-evidence.test.js test/posture/provenance-dag-walk.test.js test/posture/provenance-confidence.test.js test/posture/provenance-predicate-replay.test.js test/posture/provenance-origin-resolver.test.js test/posture/provenance-ai-authorship.test.js test/posture/provenance-branch-entry.test.js test/posture/provenance-evidence-attribution.test.js test/posture/provenance-lifecycle.test.js test/posture/provenance-missing-control-resolver.test.js test/posture/provenance-missing-control-wiring.test.js test/posture/provenance-coordinator.test.js test/posture/provenance-sca-origin.test.js test/posture/provenance-transitive-sca.test.js test/posture/provenance-coordinator-sca.test.js test/posture/provenance-providers.test.js test/posture/provenance-evidence-bundle.test.js test/posture/provenance-repo-lineage.test.js test/posture/provenance-secrets-logic.test.js test/cli/provenance-flags.test.js test/cli/attest-provenance.test.js test/cli/attest-obligations.test.js test/cli/models.test.js test/cli/setup-llm.test.js test/cli/fix-retry.test.js test/cli/compliance-walkthrough-lineage.test.js test/cli/dataflow-export-privacy.test.js test/cli/dataflow-export-briefing.test.js test/cli/lineage-snapshot-persist.test.js test/cli/dataflow-diff.test.js test/cli/dataflow-watch.test.js test/cli/dataflow-recipients.test.js test/cli/dataflow-coverage.test.js test/security/provenance-safety.test.js test/security/provenance-injection.test.js test/posture/remediation-ledger.test.js test/posture/assurance-baseline.test.js test/posture/assurance-contracts.test.js test/posture/assurance-config.test.js test/evidence-issuer.test.js test/load-factor.test.js test/fleet/job-store.test.js test/fleet/enterprise-graph.test.js test/fleet/discover-edges.test.js test/fleet/code-boundaries.test.js test/fleet/job-runner.test.js test/fleet/accounting.test.js test/fleet/fleet-org-cli.test.js test/eval-lab/dataset-neutralize.test.js test/eval-lab/arms-score-stats.test.js test/eval-lab/run-report.test.js test/eval-lab/router.test.js test/eval-lab/arm-ollama.test.js test/eval-lab/import-independent.test.js test/cli/eval-lab-cli.test.js test/dnr/ir-and-outputs.test.js test/dnr/logs-and-hunt.test.js test/dnr/auth-truth-object-authz.test.js test/pilot/two-tenant-records.test.js test/pilot/app-to-cloud-and-db.test.js test/pilot/candidate-packet.test.js test/dnr/window-store-view.test.js test/dnr/exploit-correlation.test.js test/cli/dnr-cli.test.js test/proof-fabric/core.test.js test/proof-fabric/container.test.js test/proof-fabric/generate.test.js test/proof-fabric/siblings.test.js test/proof-fabric/generate-python.test.js test/proof-fabric/generate-haskell.test.js test/proof-fabric/generate-haskell-widened.test.js test/cli/proof-lifecycle-demo.test.js test/mcp-proof.test.js test/proof-fabric/fix-store-runner.test.js test/proof-fabric/fixtures.test.js test/cli/proof-fabric-cli.test.js test/rule-compiler/core.test.js test/rule-compiler/gates.test.js test/rule-compiler/ci-regression.test.js test/rule-compiler/synthesis.test.js test/rule-compiler/structure.test.js test/cli/rule-compiler-cli.test.js test/fleet/fleet-graph.test.js test/fleet/fleet-manifest.test.js test/fleet/fleet-cli.test.js test/artifact/safe-tar.test.js test/artifact/image-archive.test.js test/artifact/filesystem.test.js test/artifact/layer-secrets.test.js test/artifact/inventory.test.js test/artifact/vuln-correlate.test.js test/artifact/signature.test.js test/artifact/provenance-graph.test.js test/artifact/mismatch.test.js test/artifact/vex.test.js test/artifact/registry.test.js test/artifact/sources-cosign-evidence.test.js test/cli/artifact-sources-cli.test.js test/cli/artifact-provenance-cli.test.js test/witness-bundle.test.js test/scope/watch-cli.test.js",
91
91
  "test:dataflow": "node --test test/privacy-catalog.test.js test/privacy-deep-walker.test.js test/privacy-deep-e2e.test.js test/container-taint.test.js test/stored-taint-families.test.js test/py-annotation-sources.test.js test/fn-reach.test.js test/deep-taint.test.js test/calibration.test.js test/holdout-eval.test.js test/cross-lang-meta.test.js test/cross-lang-queues.test.js test/phase5-xlang.test.js test/phase5-coverage.test.js test/phase6-taint.test.js test/llm-validator-consistency.test.js test/llm-validator-default-on.test.js test/llm-validator-preset.test.js test/parser-py-cst.test.js test/chrome-probe.test.js test/parser-cs-kt.test.js test/parser-cs-annotations.test.js test/parser-cs-control-flow.test.js test/parser-kt-control-flow.test.js test/parser-js-annotations.test.js test/parser-java-annotations.test.js test/parser-java-control-flow.test.js test/parser-java-calls.test.js test/parser-go.test.js test/parser-rust.test.js test/rust-taint-flow.test.js test/parser-php-rb.test.js test/parser-php-control-flow.test.js test/interproc-k2.test.js test/proven-clean.test.js test/backward-default.test.js test/incremental-cache.test.js test/string-regex-lattice.test.js test/closure-capture.test.js test/points-to.test.js test/type-stubs.test.js test/soft-taint.test.js test/ifds.test.js test/symbolic-exec-proof.test.js test/ifds-summary-edges.test.js test/stub-aware-filter.test.js test/cross-repo.test.js test/proof-gate.test.js test/layer-recall.test.js test/java-taint-flow.test.js test/kt-taint-flow.test.js test/sanitizer-typed-flow.test.js test/sanitizer-gate-unit.test.js test/proof-safe.test.js test/collection-taint.test.js test/kcfa-context.test.js test/kcfa-callstring.test.js test/flow-parity.test.js test/callgraph-resolve.test.js test/import-reachability.test.js test/ir-stats.test.js test/parser-cpp.test.js test/parser-js-decorators.test.js test/parser-js-if-else-cfg.test.js test/cpp-integration.test.js test/engine-reconnect.test.js test/phase2-scoping.test.js test/engine-recall.test.js test/catalog-annotation-source.test.js test/catalog-cs-p1.test.js test/catalog-expanded.test.js test/builtin-summaries.test.js test/interproc-mutation-crash.test.js test/mutated-param-taint.test.js test/return-sink-check.test.js test/higher-order-inline-callback.test.js test/interproc-findings-discard.test.js test/runscan-deep-option.test.js test/summary-cache-convergence.test.js test/parser-rb-calls.test.js test/parser-rb-module-level.test.js test/balanced-call.test.js test/destructuring-taint.test.js test/computed-member-taint.test.js test/string-interpolation-taint.test.js test/dataflow-deadcode-severity.test.js test/catalog-dotted-callee-lookup.test.js test/builtin-summary-family-blind-kill.test.js test/deep-mode-annotator-pipeline.test.js test/receiver-type-and-nested-calls.test.js test/member-write-and-loop-taint.test.js test/parser-py-module-level.test.js test/parser-php-module-level.test.js test/r14b-module-level-e2e.test.js test/annotation-taint-engine.test.js test/interproc-nested-call-taint.test.js test/catalog-xpath-injection.test.js test/catalog-ldap-injection.test.js test/catalog-xxe.test.js test/catalog-response-splitting.test.js test/catalog-code-injection.test.js test/parser-rb-control-flow.test.js test/catalog-ruby-p1.test.js test/callee-receiver-taint.test.js test/catalog-command-injection-p4.test.js test/catalog-xss-p4.test.js test/catalog-path-ssrf-p3.test.js test/catalog-ai-model-provider-precision.test.js test/catalog-orm-write.test.js test/dataflow/provenance-pipeline-integration.test.js test/dataflow-progress.test.js test/scan-progress.test.js test/sard-leakage-pipeline.test.js test/sard-split-scoring.test.js test/sard-llm-isolation.test.js test/sard-mutation-adversarial.test.js test/sard-baseline-adaptive-tolerance.test.js test/sard-independent-holdout.test.js test/sard-flow-aware-scoring.test.js test/sard-macro-score-support-floor.test.js test/sard-php-split.test.js test/parser-java-nary-concat.test.js test/sard-naive-baseline.test.js test/field-taint-cross-method.test.js test/field-taint-engine-unit.test.js test/coercion-sanitizer-kill.test.js test/constant-propagation-path-sensitivity.test.js test/parser-java-assignments.test.js test/catalog-java-sard.test.js test/parser-cs-juliet-shapes.test.js test/catalog-cs-sard-p2.test.js test/sard-cwe-key-merge.test.js test/parser-php-backtick.test.js test/parser-php-include.test.js test/php-include-merge.test.js test/parser-php-array-field-taint.test.js test/parser-php-cond-assign.test.js test/parser-rust-redos.test.js test/multi-sink-taint-chain.test.js test/static-field-cross-class-call-string.test.js test/regex-guard-narrowing.test.js test/object-instance-field-taint.test.js test/param-type-inference.test.js test/smt-path-feasibility.test.js test/cross-service-taint.test.js test/dataflow-expr-taint-scaling.test.js test/evaluation/guard-dominance.test.js test/evaluation/engine-mechanisms.test.js test/rx-exec-receiver.test.js test/incremental-dependency.test.js test/incremental-integrity.test.js test/demand-query.test.js test/ir-cache.test.js test/canonicalize-parity.test.js test/dataflow-source-attribution.test.js",
92
92
  "test:mcp": "node --test test/mcp.test.js test/mcp-apply-fix-multifile.test.js test/mcp-protocol-smoke.test.js test/mcp-audit.test.js test/audit-cli.test.js test/mcp-scratchpad.test.js test/mcp-offload.test.js test/sca-upgrade.test.js test/lsp-server.test.js test/mcp-dataflow-tools.test.js test/cli/governance-propose-edit.test.js test/cli/remediation-open-update.test.js test/cli/remediation-verify-reopen.test.js test/cli/dataflow-observations.test.js test/cli/federate-declare-list.test.js test/capabilities/tools.test.js",
93
93
  "test:report": "node --test test/report-scan-invariants.test.js test/junit.test.js test/ci.test.js test/poc-generator.test.js test/verifier.test.js test/verifier-target.test.js test/annotator-errors.test.js test/git-history-batch.test.js test/default-scan-drift.test.js test/bundle-optional-deps.test.js test/scan-health.test.js test/finding-schema.test.js test/pipeline/finding-schema-provenance.test.js test/evidence-provenance.test.js test/report/provenance-output.test.js test/report/provenance-format-parity.test.js test/legacy-compat.test.js test/annotator-runner.test.js test/detector-runner.test.js test/detector-errors.test.js test/analyzer-supervisor.test.js test/cascade-worker-pool.test.js test/cascade-pool-wiring.test.js test/coverage-ledger.test.js test/coverage-ledger-completeness.test.js test/assurance-mode.test.js test/artifact-registry.test.js test/artifact-registry-completeness.test.js test/retention-policy.test.js test/state-lifecycle-report.test.js test/legal-hold.test.js test/production-feedback.test.js test/calibration-feedback.test.js test/encryption-provider.test.js test/grader-calibration.test.js test/pr-delta-gate.test.js test/vex.test.js test/report-render.test.js test/oscal-conformance.test.js test/cli-fix-apply.test.js test/apply-fix-service.test.js test/high-impact-approval-gate.test.js test/approver-registry.test.js test/suppression-exceptions.test.js test/fix-history.test.js test/producer-registry.test.js test/producer-collector.test.js test/enrichment-completion.test.js",
94
94
  "test:lineage": "node --test test/lineage/analysis-completion.test.js test/lineage/schema.test.js test/lineage/ids.test.js test/lineage/protection.test.js test/lineage/classification.test.js test/lineage/validate.test.js test/lineage/json-schema-parity.test.js test/lineage/flagship-fixture.test.js test/lineage/flagship-fixture-semantics.test.js test/lineage/field-identity.test.js test/lineage/engine-expr-resolver.test.js test/lineage/engine-walker.test.js test/lineage/engine-integration.test.js test/lineage/engine-receiver-identity.test.js test/lineage/summaries.test.js test/lineage/driver.test.js test/lineage/engine-provenance.test.js test/lineage/engine-provenance-interprocedural.test.js test/lineage/path-store.test.js test/lineage/path-query.test.js test/lineage/flow-grade.test.js test/lineage/source-registry.test.js test/lineage/sink-registry.test.js test/lineage/transform-catalog.test.js test/lineage/handling-analyzer.test.js test/lineage/registry-real-code.test.js test/lineage/source-seeding.test.js test/lineage/seeding-across-calls.test.js test/lineage/boundary-join.test.js test/contracts/code-boundaries.contract.test.js test/lineage/graph-builder.test.js test/lineage/coverage.test.js test/lineage/resolve-destination.test.js test/lineage/queue-detail.test.js test/lineage/index.test.js test/lineage/obligation-mapping.test.js test/lineage/obligation-predicates.test.js test/lineage/obligation-predicates-walkthrough.test.js test/lineage/transit-protection.test.js test/lineage/at-rest-protection.test.js test/lineage/protection-summary.test.js test/lineage/ac01-multi-sink.test.js test/lineage/policy-verdict.test.js test/lineage/governance-refs.test.js test/lineage/export-privacy.test.js test/lineage/export-json.test.js test/lineage/export-csv.test.js test/lineage/decision-story.test.js test/lineage/export-briefing.test.js test/lineage/graph-snapshot.test.js test/lineage/graph-diff.test.js test/lineage/drift-policy.test.js test/lineage/recipient-profile.test.js test/lineage/recipient-registry.test.js test/lineage/recipient-wiring.test.js test/lineage/redact-graph.test.js test/lineage-scan-wiring.test.js test/lineage-artifact-write.test.js test/bench-data-lineage-runner.test.js test/bundle-frontend.test.js test/bundle-frontend-golden.test.js test/generate-html-report.test.js test/export-image.test.js test/lineage/scenario.test.js test/lineage/scenario-engine.test.js test/lineage/scenario-diff.test.js test/lineage/scenario-no-obligation-wiring.test.js test/lineage/impact-assessment.test.js test/lineage/impact-engine.test.js test/lineage/impact-no-obligation-wiring.test.js test/lineage/governance-edit.test.js test/lineage/remediation.test.js test/lineage/runtime-observation.test.js test/lineage/observation-correlation.test.js test/lineage/observation-adapters.test.js test/lineage/observation-store.test.js test/lineage/runtime-corroboration-wiring.test.js test/lineage/cross-repo-link.test.js test/lineage/federation-loader.test.js test/lineage/cross-repo-link-wiring.test.js test/lineage/runtime-assurance.test.js test/lineage/runtime-edge-states.test.js test/lineage/runtime-explorer-view.test.js test/cli/runtime-explorer-cli.test.js test/cli/runtime-identity-cli.test.js test/cli/runtime-twin-cli.test.js test/lineage/cut-planner.test.js test/lineage/scc.test.js test/lineage/scc-solver.test.js test/graph/graph-views.test.js test/lineage/dag07-completion.test.js test/lineage/owned-build.test.js test/dataflow/boundary-propagation.test.js test/lineage/boundary-contract.test.js test/lineage/correlation-v2.test.js test/lineage/path-control-evidence.test.js test/lineage/smt-z3-backend.test.js test/cli/dag-commands.test.js",
95
95
  "test:bench-modules": "node --test test/phase4-harness.test.js test/pipeline.test.js test/proof-corpus-lib.test.js test/proof-corpus-runner.test.js",
96
96
  "test:server": "node --test test/server/graph-loader.test.js test/server/security.test.js test/server/routes.test.js test/server/http-server.test.js test/server/cmd-explore.test.js test/server/cmd-dataflow-export.test.js test/server/static-assets.test.js test/server/dataflow-scenario-cli.test.js test/server/dataflow-impact-cli.test.js",
97
- "test:haskell": "node --test test/haskell/haskell-core-families-runner.test.js test/haskell/haskell-project-models.test.js test/haskell/haskell-reachability.test.js test/haskell/haskell-real-code-shapes.test.js test/haskell/haskell-unseen-v5-shapes.test.js test/haskell/haskell-unseen-v4-shapes.test.js test/haskell/haskell-alias-taint.test.js test/haskell/haskell-weak-hash-names.test.js test/haskell/haskell-models-extended.test.js test/haskell/haskell-rules-extended.test.js test/haskell/haskell-injection.test.js test/haskell/haskell-ir.test.js test/haskell/haskell-manifests.test.js test/haskell/haskell-parser.test.js test/haskell/haskell-remediation.test.js test/haskell/haskell-remediation-0190.test.js test/haskell/haskell-remediation-kdf.test.js test/haskell/haskell-remediation-html-text.test.js test/haskell/haskell-remediation-scotty-body.test.js test/haskell/haskell-remediation-entropy.test.js test/haskell/haskell-remediation-text-command.test.js test/haskell/haskell-remediation-path-contain.test.js test/haskell/haskell-remediation-0195-16.test.js test/haskell/haskell-taint-shapes.test.js test/haskell/haskell-real-advisory-shapes.test.js test/haskell/haskell-discovery-shapes.test.js test/haskell/haskell-quasiquote-interp.test.js test/haskell/haskell-record-dot.test.js test/haskell/haskell-global-cell.test.js test/haskell/haskell-syntax-extensions.test.js test/haskell/haskell-rule-precision.test.js test/haskell/haskell-resolved-graph.test.js test/haskell/haskell-sca.test.js test/haskell/haskell-sca-severity.test.js test/haskell/haskell-web-guards.test.js test/haskell/haskell-advisory-feed.test.js test/haskell/haskell-security-rules.test.js test/haskell/haskell-taint.test.js test/haskell/haskell-boundaries.test.js test/haskell/haskell-live-feed-scan.test.js test/haskell/haskell-supply-bounds.test.js test/haskell/haskell-ir-scaling.test.js test/haskell/haskell-boundary-propagation.test.js test/haskell/haskell-generalization.test.js test/haskell/haskell-trust-boundary.test.js test/haskell/haskell-trust-fixed-file.test.js test/haskell/haskell-predictable-random-flow.test.js test/haskell/haskell-comment-invariance.test.js test/haskell/haskell-remediation-classes.test.js test/haskell/haskell-trust-boundary-frameworks.test.js test/haskell/haskell-generic-detectors.test.js",
97
+ "test:haskell": "node --test test/haskell/haskell-core-families-runner.test.js test/haskell/haskell-pilot.test.js test/haskell/haskell-project-models.test.js test/haskell/haskell-reachability.test.js test/haskell/haskell-real-code-shapes.test.js test/haskell/haskell-unseen-v5-shapes.test.js test/haskell/haskell-unseen-v4-shapes.test.js test/haskell/haskell-alias-taint.test.js test/haskell/haskell-weak-hash-names.test.js test/haskell/haskell-models-extended.test.js test/haskell/haskell-rules-extended.test.js test/haskell/haskell-injection.test.js test/haskell/haskell-ir.test.js test/haskell/haskell-manifests.test.js test/haskell/haskell-parser.test.js test/haskell/haskell-remediation.test.js test/haskell/haskell-remediation-0190.test.js test/haskell/haskell-remediation-kdf.test.js test/haskell/haskell-remediation-html-text.test.js test/haskell/haskell-remediation-scotty-body.test.js test/haskell/haskell-remediation-entropy.test.js test/haskell/haskell-remediation-text-command.test.js test/haskell/haskell-remediation-path-contain.test.js test/haskell/haskell-remediation-0195-16.test.js test/haskell/haskell-taint-shapes.test.js test/haskell/haskell-real-advisory-shapes.test.js test/haskell/haskell-discovery-shapes.test.js test/haskell/haskell-quasiquote-interp.test.js test/haskell/haskell-record-dot.test.js test/haskell/haskell-global-cell.test.js test/haskell/haskell-syntax-extensions.test.js test/haskell/haskell-rule-precision.test.js test/haskell/haskell-resolved-graph.test.js test/haskell/haskell-sca.test.js test/haskell/haskell-sca-severity.test.js test/haskell/haskell-web-guards.test.js test/haskell/haskell-advisory-feed.test.js test/haskell/haskell-security-rules.test.js test/haskell/haskell-taint.test.js test/haskell/haskell-boundaries.test.js test/haskell/haskell-live-feed-scan.test.js test/haskell/haskell-supply-bounds.test.js test/haskell/haskell-ir-scaling.test.js test/haskell/haskell-boundary-propagation.test.js test/haskell/haskell-generalization.test.js test/haskell/haskell-trust-boundary.test.js test/haskell/haskell-trust-fixed-file.test.js test/haskell/haskell-predictable-random-flow.test.js test/haskell/haskell-comment-invariance.test.js test/haskell/haskell-remediation-classes.test.js test/haskell/haskell-trust-boundary-frameworks.test.js test/haskell/haskell-generic-detectors.test.js",
98
98
  "test:nix": "node --test test/nix/nix-option-types.test.js test/nix/nix-development-shapes.test.js test/nix/nixos-hardening-daemon.test.js test/nix/nixos-hardening-extended.test.js test/nix/nix-build-trust.test.js test/nix/nix-eval-isolation.test.js test/nix/nix-input-inventory.test.js test/nix/nix-parser-ir.test.js test/nix/nix-remediation.test.js test/nix/nix-resolved-closure.test.js test/nix/nix-sca-patches.test.js test/nix/nix-sca-severity.test.js test/nix/nix-haskell-closure-names.test.js test/nix/nix-script-taint.test.js test/nix/nix-secrets-store.test.js test/nix/nixos-hardening.test.js test/nix/nixos-module-resolution.test.js test/nix/nix-fix-multifile.test.js test/nix/nixos-evaluator-forms.test.js test/nix/nix-advisory-feed.test.js test/nix/nix-fix-prewrite.test.js test/nix/nix-boundary-scope.test.js",
99
99
  "test:language": "node --test test/bench-haskell-precision.test.js test/bench-independent-haskell-panel.test.js test/language/capability-ledger.test.js test/language/language-accuracy-regression.test.js test/language/language-aibom.test.js test/language/language-assurance.test.js test/language/language-bom.test.js test/language/language-compliance.test.js test/language/language-contracts.test.js test/language/language-corpora-integrity.test.js test/language/language-discovery.test.js test/language/language-doc-coverage.test.js test/language/language-evidence-posture.test.js test/language/language-explorer.test.js test/language/frozen-splits.test.js test/language/language-gap-fixes.test.js test/language/language-gap-fixes-2.test.js test/language/language-governance.test.js test/language/language-integrations.test.js test/language/language-llm-agent.test.js test/language/language-local-ai.test.js test/language/language-parity-haskell.test.js test/language/language-polyglot-bridges.test.js test/language/language-privacy-lineage.test.js test/language/language-proof-remediation.test.js test/language/language-report-formats.test.js test/language/language-secrets.test.js test/language/language-trust-boundary.test.js",
100
100
  "test:language-stress": "node --test --test-concurrency=1 test/language/language-stress-offline.test.js",
@@ -102,7 +102,7 @@
102
102
  "bench:language-support:check": "node ../bench/language-support/check.mjs",
103
103
  "bench:live-feed": "node ../bench/live-feed/run.mjs",
104
104
  "bench:language-support:perf": "node ../bench/language-support/perf.mjs",
105
- "test:evaluation": "node --test test/evaluation/protocol.test.js test/evaluation/grouping.test.js test/evaluation/labels.test.js test/evaluation/custody.test.js test/evaluation/runner.test.js test/evaluation/gates-scorecard.test.js test/evaluation/reporting.test.js test/evaluation/why-missed.test.js test/evaluation/guard-dominance.test.js test/evaluation/engine-mechanisms.test.js test/evaluation/release-gate.test.js test/evaluation/publication.test.js",
105
+ "test:evaluation": "node --test test/evaluation/protocol.test.js test/evaluation/grouping.test.js test/evaluation/labels.test.js test/evaluation/custody.test.js test/evaluation/runner.test.js test/evaluation/gates-scorecard.test.js test/evaluation/reporting.test.js test/evaluation/why-missed.test.js test/evaluation/scorer-reconciliation.test.js test/evaluation/guard-dominance.test.js test/evaluation/engine-mechanisms.test.js test/evaluation/release-gate.test.js test/evaluation/publication.test.js",
106
106
  "reproduce:mini": "node ../scripts/public-reproduction.mjs",
107
107
  "reproduce:routing": "node ../scripts/routing-replay.mjs",
108
108
  "verification:patch-example": "node ../scripts/patch-replay-example.mjs",
@@ -122,12 +122,13 @@
122
122
  "test:release-closure": "node --test test/release-closure/closure-plan.test.js test/release-closure/closure-record.test.js test/release-closure/compatibility.test.js test/release-closure/rollout-stages.test.js test/release-closure/migration-readers.test.js test/release-closure/rollback.test.js test/release-closure/wrappers.test.js test/release-closure/final-closure.test.js test/release-closure/final-closure-controller.test.js test/release-closure/runner-hardening.test.js test/release-closure/remote-attestation.test.js test/release-closure/closure-parallel.test.js",
123
123
  "release:closure:check": "node ../scripts/release-closure.mjs --run",
124
124
  "release:closure:static": "node ../scripts/release-closure.mjs --static",
125
+ "pilot:qualify": "node ../bench/threat-model-pilot/qualify.mjs",
125
126
  "test:documentation": "node --test test/documentation/drift-check.test.js test/documentation/generated-pages.test.js test/documentation/scope-and-examples.test.js test/documentation/policy-cards.test.js test/documentation/operations-and-review.test.js",
126
127
  "test:capabilities": "node --test test/capabilities/manifest.test.js test/capabilities/filesystem.test.js test/capabilities/exec.test.js test/capabilities/network.test.js test/capabilities/canary.test.js test/capabilities/tools.test.js test/capabilities/recovery.test.js test/capabilities/receipts.test.js test/capabilities/adversarial.test.js test/capabilities/linux-enforcement.test.js test/supervise-async-sweep.test.js",
127
128
  "test:verification": "node --test test/verification/verification-record.test.js test/verification/oracle-adapters.test.js test/verification/replay-manifest.test.js test/verification/patch-negative.test.js test/verification/non-taint-classes.test.js test/verification/interface-equivalence.test.js test/verification/advisory-gating.test.js test/verification/oracle-conformance.test.js",
128
- "test:deployment": "node --test test/deployment/boundary-graph.test.js test/deployment/config-adapters.test.js test/deployment/trace-correlation.test.js test/deployment/cross-repo.test.js test/deployment/attack-paths.test.js test/deployment/drift.test.js test/deployment/boundary-presentation.test.js test/deployment/differentiation-ablation.test.js test/deployment/boundaries-cli.test.js",
129
+ "test:deployment": "node --test test/deployment/boundary-graph.test.js test/deployment/config-adapters.test.js test/deployment/trace-correlation.test.js test/deployment/cross-repo.test.js test/deployment/attack-paths.test.js test/deployment/iam-actions-and-limits.test.js test/deployment/proof-binding.test.js test/deployment/drift.test.js test/deployment/boundary-presentation.test.js test/deployment/differentiation-ablation.test.js test/deployment/boundaries-cli.test.js",
129
130
  "test:portfolio": "node --test test/portfolio/manifest.test.js test/portfolio/bundle.test.js test/portfolio/signing.test.js test/portfolio/work-units.test.js test/portfolio/resume.test.js test/portfolio/scheduler.test.js test/portfolio/progress.test.js test/portfolio/retention.test.js test/portfolio/backend.test.js",
130
- "test:invariants": "node --test test/invariants/invariant-schema.test.js test/invariants/invariant-lifecycle.test.js test/invariants/invariant-scenarios.test.js test/invariants/business-state-oracle.test.js test/invariants/scenario-shrinking.test.js test/invariants/invariant-repair.test.js test/invariants/business-coverage.test.js test/invariants/invariant-ablation.test.js",
131
+ "test:invariants": "node --test test/invariants/invariant-schema.test.js test/invariants/invariant-lifecycle.test.js test/invariants/invariant-scenarios.test.js test/invariants/business-state-oracle.test.js test/invariants/scenario-shrinking.test.js test/invariants/invariant-repair.test.js test/invariants/approval-workflow.test.js test/invariants/business-coverage.test.js test/invariants/invariant-ablation.test.js",
131
132
  "bench:invariant-ablation": "node ../scripts/invariant-ablation.mjs",
132
133
  "verification:conformance:check": "node ../scripts/verification-conformance-check.mjs",
133
134
  "verification:conformance:static": "node ../scripts/verification-conformance-check.mjs --static",
@@ -3080,7 +3080,7 @@ __webpack_require__.a(__webpack_module__, async (__webpack_handle_async_dependen
3080
3080
  /* harmony export */ __webpack_require__.d(__webpack_exports__, {
3081
3081
  /* harmony export */ Wi: () => (/* binding */ ALL_TOOLS)
3082
3082
  /* harmony export */ });
3083
- /* unused harmony exports _internals, scan_diff, query_taint, explain_finding, apply_fix, verify_fix, synthesize_fix, find_rule_module, append_scratchpad, read_scratchpad, append_agents_memory, read_agents_memory, query_triage_memory, query_findings_memory, lookup_cve, query_cache_telemetry, synthesize_sca_upgrade, apply_sca_upgrade */
3083
+ /* unused harmony exports _internals, scan_diff, query_taint, explain_finding, approvalBindingRefusal, apply_fix, verify_fix, synthesize_fix, find_rule_module, append_scratchpad, read_scratchpad, append_agents_memory, read_agents_memory, query_triage_memory, query_findings_memory, lookup_cve, query_cache_telemetry, synthesize_sca_upgrade, apply_sca_upgrade */
3084
3084
  /* harmony import */ var node_fs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(73024);
3085
3085
  /* harmony import */ var node_fs_promises__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(51455);
3086
3086
  /* harmony import */ var node_path__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(76760);
@@ -3097,7 +3097,7 @@ __webpack_require__.a(__webpack_module__, async (__webpack_handle_async_dependen
3097
3097
  /* harmony import */ var _report_index_js__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(75917);
3098
3098
  /* harmony import */ var _posture_verification_projection_js__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(51864);
3099
3099
  /* harmony import */ var _fix_verify_patch_js__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(2046);
3100
- /* harmony import */ var _lineage_deployment_projection_js__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(73596);
3100
+ /* harmony import */ var _lineage_deployment_projection_js__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(83929);
3101
3101
  /* harmony import */ var _posture_provenance_schema_js__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(34594);
3102
3102
  /* harmony import */ var _dataflow_tools_js__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(20095);
3103
3103
  /* harmony import */ var _invariant_tools_js__WEBPACK_IMPORTED_MODULE_18__ = __webpack_require__(82407);
@@ -3749,7 +3749,26 @@ const explain_finding = {
3749
3749
  // FR-307/FR-1002/FR-1003: the high-impact-change approval gate (approval evidence, approver identity, separation of duties),
3750
3750
  // shared by the caller-patch branch and the multi-file plan branch so the two cannot drift. Returns a refusal result, or null
3751
3751
  // when the change is not high-impact or is properly approved.
3752
- function _highImpactApprovalRefusal(ctx, materialClassification, fixMeta) {
3752
+ // Threat-model PRD FR-06.3: an approval is bound to what it approved. With a `scope` it names the tool, the action, the target finding
3753
+ // and the change (the digest of the exact edits), and with `expires` it lapses. Under the restricted execution profile
3754
+ // (AGENTIC_SECURITY_EGRESS_PROFILE=restricted) both are required; elsewhere a supplied scope or expiry is still enforced. An approval
3755
+ // copied to another finding, another change or another tool, or used after it expired, is refused.
3756
+ function approvalBindingRefusal(approval, expected, { now = Date.now(), restricted = process.env.AGENTIC_SECURITY_EGRESS_PROFILE === 'restricted' } = {}) {
3757
+ if (restricted && (!approval.expires || !approval.scope)) return 'under the restricted profile an approval must carry an expiry and a scope {tool, action, target, change}';
3758
+ if (approval.expires !== undefined) {
3759
+ const t = Date.parse(approval.expires);
3760
+ if (!Number.isFinite(t)) return 'the approval expiry is not a date';
3761
+ if (!(t > now)) return 'the approval has expired';
3762
+ }
3763
+ if (approval.scope !== undefined) {
3764
+ const sc = approval.scope;
3765
+ if (!sc || typeof sc !== 'object') return 'the approval scope is not an object';
3766
+ for (const k of ['tool', 'action', 'target', 'change']) if (sc[k] !== expected[k]) return `the approval was issued for another ${k}`;
3767
+ }
3768
+ return null;
3769
+ }
3770
+
3771
+ function _highImpactApprovalRefusal(ctx, materialClassification, fixMeta, expected = null) {
3753
3772
  if (!materialClassification.highImpactCategories.length) return null;
3754
3773
  const cats = materialClassification.highImpactCategories.join(', ');
3755
3774
  const approval = fixMeta && typeof fixMeta === 'object' ? fixMeta.approval : null;
@@ -3763,6 +3782,10 @@ function _highImpactApprovalRefusal(ctx, materialClassification, fixMeta) {
3763
3782
  materialClassification,
3764
3783
  };
3765
3784
  }
3785
+ if (expected) {
3786
+ const bound = approvalBindingRefusal(approval, expected);
3787
+ if (bound) return { _meta: META, applied: false, reason: `high-impact change (${cats}) approval rejected: ${bound}`, materialClassification };
3788
+ }
3766
3789
  const approverRegistry = (0,_fix_approver_registry_js__WEBPACK_IMPORTED_MODULE_7__.loadApproverRegistry)(ctx.sessionRoot);
3767
3790
  const requiredRoles = (0,_fix_approver_registry_js__WEBPACK_IMPORTED_MODULE_7__/* .requiredRolesFor */ .K)(approverRegistry, materialClassification.highImpactCategories);
3768
3791
  const identityCheck = (0,_fix_approver_registry_js__WEBPACK_IMPORTED_MODULE_7__.verifyApprover)(approverRegistry, approval.approvedBy, requiredRoles);
@@ -3848,7 +3871,7 @@ async function _applyMultiFilePlan({ f, planDigest, dryRun, fixMeta, ctx, status
3848
3871
  if (!targets.ok) return refuse(targets.reason);
3849
3872
  const classification = (0,_posture_material_change_js__WEBPACK_IMPORTED_MODULE_6__/* .classifyFixMaterialRisk */ .kz)(Object.fromEntries(plan.edits.map((e) => [e.file, { before: e.before, after: e.after }])));
3850
3873
  if (!dryRun) {
3851
- const approvalRefusal = _highImpactApprovalRefusal(ctx, classification, fixMeta);
3874
+ const approvalRefusal = _highImpactApprovalRefusal(ctx, classification, fixMeta, { tool: 'apply_fix', action: 'apply-plan', target: f.stableId || f.id, change: planDigest });
3852
3875
  if (approvalRefusal) return { ...approvalRefusal, multiFile: true };
3853
3876
  }
3854
3877
  // The verification gates (syntax, rescan with no new finding, effective value changed as intended) run inside the lifecycle,
@@ -4051,7 +4074,8 @@ const apply_fix = {
4051
4074
  if (dry_run) {
4052
4075
  return { _meta: META, applied: false, dryRun: true, verified: true, files: Object.keys(confinedAbs), summary: verdict.summary, verificationRecord: verdict.verificationRecord ?? null, ...(0,_posture_verification_projection_js__WEBPACK_IMPORTED_MODULE_13__/* .verificationFields */ .ze)(verdict.verificationRecord), materialClassification, ...(fixVerification ? (0,_fix_verify_patch_js__WEBPACK_IMPORTED_MODULE_14__/* .additiveFields */ .Y)(fixVerification) : {}) };
4053
4076
  }
4054
- const approvalRefusal = _highImpactApprovalRefusal(ctx, materialClassification, fixMeta);
4077
+ const changeDigest = _sha256(JSON.stringify(Object.entries(filesForMaterialClassification).sort(([a], [b]) => (a < b ? -1 : 1)).map(([k, v]) => [k, _sha256(v.before), _sha256(v.after)])));
4078
+ const approvalRefusal = _highImpactApprovalRefusal(ctx, materialClassification, fixMeta, { tool: 'apply_fix', action: 'apply-patch', target: f.stableId || f.id, change: changeDigest });
4055
4079
  if (approvalRefusal) return approvalRefusal;
4056
4080
  const written = [];
4057
4081
  try {