@clear-capabilities/agentic-security-scanner 0.143.0 → 0.145.0

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 (135) hide show
  1. package/CHANGELOG.md +555 -0
  2. package/bin/agentic-security.js +770 -49
  3. package/dist/1.index.js +223 -0
  4. package/dist/113.index.js +117 -18
  5. package/dist/144.index.js +163 -0
  6. package/dist/178.index.js +24 -6
  7. package/dist/238.index.js +3 -2
  8. package/dist/265.index.js +191 -0
  9. package/dist/271.index.js +165 -0
  10. package/dist/384.index.js +1 -1
  11. package/dist/435.index.js +187 -52
  12. package/dist/444.index.js +11 -2
  13. package/dist/449.index.js +76 -12
  14. package/dist/526.index.js +117 -18
  15. package/dist/552.index.js +97 -0
  16. package/dist/637.index.js +27 -5
  17. package/dist/730.index.js +311 -0
  18. package/dist/736.index.js +301 -0
  19. package/dist/824.index.js +7 -0
  20. package/dist/905.index.js +88 -22
  21. package/dist/920.index.js +491 -0
  22. package/dist/970.index.js +173 -0
  23. package/dist/agentic-security.mjs +13 -13
  24. package/dist/agentic-security.mjs.sha256 +1 -1
  25. package/dist/calibration-seed.json +2 -0
  26. package/package.json +23 -9
  27. package/src/compare.js +6 -1
  28. package/src/dataflow/CLAUDE.md +1 -1
  29. package/src/dataflow/index.js +18 -0
  30. package/src/dataflow/privacy-catalog.js +290 -0
  31. package/src/dataflow/privacy-deep-walker.js +515 -0
  32. package/src/dataflow/privacy-governance.js +126 -0
  33. package/src/dataflow/privacy-inventory.js +154 -0
  34. package/src/dataflow/privacy-sink-policy.js +125 -0
  35. package/src/dataflow/privacy-taint.js +115 -54
  36. package/src/dataflow/privacy-taxonomy.js +233 -0
  37. package/src/discovery/disprove.js +7 -3
  38. package/src/discovery/hunter.js +9 -5
  39. package/src/discovery/index.js +2 -2
  40. package/src/discovery/llm-invoke.js +69 -13
  41. package/src/egress/audit.js +147 -0
  42. package/src/egress/policy.js +313 -0
  43. package/src/egress/redact.js +180 -0
  44. package/src/engine.js +1048 -302
  45. package/src/fix/apply-fix-service.js +404 -0
  46. package/src/fix/approver-registry.js +157 -0
  47. package/src/history-scan.js +22 -5
  48. package/src/ir/CLAUDE.md +1 -1
  49. package/src/llm-validator/index.js +86 -9
  50. package/src/llm-validator/model-status.js +66 -0
  51. package/src/lsp/server.js +49 -2
  52. package/src/mcp/tools.js +177 -50
  53. package/src/pipeline/analyzer-supervisor.js +93 -0
  54. package/src/pipeline/analyzer-worker.js +26 -0
  55. package/src/pipeline/annotator-runner.js +33 -0
  56. package/src/pipeline/assurance-mode.js +154 -0
  57. package/src/pipeline/cascade-worker-pool.js +172 -0
  58. package/src/pipeline/cascade-worker.js +43 -0
  59. package/src/pipeline/coverage-ledger.js +0 -0
  60. package/src/pipeline/detector-runner.js +51 -0
  61. package/src/pipeline/enrichment-completion.js +58 -0
  62. package/src/pipeline/evidence-provenance.js +91 -0
  63. package/src/pipeline/finding-schema.js +108 -0
  64. package/src/pipeline/legacy-compat.js +101 -0
  65. package/src/pipeline/producer-collector.js +48 -0
  66. package/src/pipeline/producer-registry.js +112 -0
  67. package/src/pipeline/scan-health.js +144 -0
  68. package/src/posture/CLAUDE.md +123 -0
  69. package/src/posture/accuracy-scorecard.js +156 -1
  70. package/src/posture/adversary-agent.js +15 -3
  71. package/src/posture/artifact-registry.js +241 -0
  72. package/src/posture/auditor-walkthrough.js +186 -21
  73. package/src/posture/calibration-feedback.js +201 -0
  74. package/src/posture/calibration-seed.json +2 -0
  75. package/src/posture/calibration.js +25 -0
  76. package/src/posture/compliance-evidence-signing.js +131 -0
  77. package/src/posture/compliance-policy.js +324 -17
  78. package/src/posture/cross-repo-memory.js +7 -2
  79. package/src/posture/custom-rules.js +36 -0
  80. package/src/posture/deterministic.js +8 -1
  81. package/src/posture/encryption-provider.js +205 -0
  82. package/src/posture/evidence-grade-wording.js +71 -0
  83. package/src/posture/fix-history.js +137 -20
  84. package/src/posture/fix-honesty-gate.js +47 -6
  85. package/src/posture/fix-verify.js +65 -8
  86. package/src/posture/fleet.js +0 -0
  87. package/src/posture/flow-narration.js +7 -2
  88. package/src/posture/git-history.js +13 -5
  89. package/src/posture/legal-hold.js +140 -0
  90. package/src/posture/llm-redteam.js +10 -1
  91. package/src/posture/material-change.js +111 -2
  92. package/src/posture/mttr.js +75 -12
  93. package/src/posture/policy-bundle.js +274 -0
  94. package/src/posture/pre-incident-archaeology.js +39 -7
  95. package/src/posture/privacy-framework.js +47 -6
  96. package/src/posture/production-feedback.js +179 -0
  97. package/src/posture/provenance/ai-authorship.js +68 -0
  98. package/src/posture/provenance/branch-entry.js +80 -0
  99. package/src/posture/provenance/cache.js +143 -0
  100. package/src/posture/provenance/confidence.js +36 -0
  101. package/src/posture/provenance/coordinator.js +786 -0
  102. package/src/posture/provenance/dag-walk.js +249 -0
  103. package/src/posture/provenance/evidence-attribution.js +59 -0
  104. package/src/posture/provenance/git-evidence.js +310 -0
  105. package/src/posture/provenance/lifecycle.js +208 -0
  106. package/src/posture/provenance/missing-control-resolver.js +137 -0
  107. package/src/posture/provenance/origin-resolver.js +342 -0
  108. package/src/posture/provenance/predicate-replay.js +133 -0
  109. package/src/posture/provenance/providers/config.js +39 -0
  110. package/src/posture/provenance/providers/github.js +62 -0
  111. package/src/posture/provenance/providers/gitlab.js +58 -0
  112. package/src/posture/provenance/repo-lineage.js +74 -0
  113. package/src/posture/provenance/sca-origin.js +139 -0
  114. package/src/posture/provenance/schema.js +255 -0
  115. package/src/posture/provenance/transitive-sca.js +147 -0
  116. package/src/posture/provenance/validate.js +30 -0
  117. package/src/posture/provenance-evidence-bundle.js +144 -0
  118. package/src/posture/retention-policy.js +132 -0
  119. package/src/posture/risk-dollars.js +216 -26
  120. package/src/posture/sbom-diff.js +15 -2
  121. package/src/posture/scan-checkpoint.js +176 -31
  122. package/src/posture/secret-history.js +10 -2
  123. package/src/posture/state-dir.js +64 -5
  124. package/src/posture/state-lifecycle-report.js +77 -0
  125. package/src/posture/suppressions.js +59 -3
  126. package/src/posture/vuln-archaeology.js +8 -2
  127. package/src/pr-delta.js +25 -4
  128. package/src/privacy/ir-adapter.js +380 -0
  129. package/src/report/index.js +248 -5
  130. package/src/report/oscal.js +7 -2
  131. package/src/runScan.js +34 -5
  132. package/src/sast/cpp.js +3 -14
  133. package/src/sast/rate-limit.js +33 -3
  134. package/src/sca/llm-function-extract.js +6 -0
  135. package/src/util/git-hardening.js +128 -0
package/dist/905.index.js CHANGED
@@ -111,6 +111,8 @@ function partitionCallGraph(callGraph, opts = {}) {
111
111
 
112
112
  // EXTERNAL MODULE: ./src/discovery/lenses.js
113
113
  var discovery_lenses = __webpack_require__(3499);
114
+ // EXTERNAL MODULE: ./src/egress/policy.js
115
+ var policy = __webpack_require__(5712);
114
116
  ;// CONCATENATED MODULE: ./src/discovery/llm-invoke.js
115
117
  //
116
118
  // Shared LLM endpoint caller. Both the hunter and the refutation panel need
@@ -119,6 +121,8 @@ var discovery_lenses = __webpack_require__(3499);
119
121
  // other does not, the bug stays buried in one direction.
120
122
  //
121
123
 
124
+
125
+
122
126
  const DEFAULT_TIMEOUT_MS = 60000;
123
127
 
124
128
  async function defaultLlmInvoke(prompt, opts = {}) {
@@ -202,12 +206,27 @@ function consensusOf(responses) {
202
206
  * An llmInvoke that queries several endpoints and returns the consensus answer.
203
207
  * Returns null when no endpoint answered — the callers already treat a null or
204
208
  * a throw as degradation, so an all-providers-down run degrades honestly.
209
+ *
210
+ * FR-605 (assurance-hardening PRD): each endpoint gets its OWN egress
211
+ * decision before being included — `mode: local-only` / `deniedProviders`
212
+ * must not be smuggled past just because the SINGLE-endpoint path already
213
+ * checks it. A policy-denied endpoint is EXCLUDED from the vote, the exact
214
+ * same treatment an unreachable endpoint already gets a few lines below
215
+ * (never counted as dissent) — the module's own established pattern
216
+ * extended to a second exclusion reason. Returns `{invoke, decisions}`:
217
+ * `invoke` is null only when EVERY endpoint was denied (the all-down
218
+ * equivalent); `decisions` is the full per-endpoint array for a caller
219
+ * that wants it, kept alongside the single aggregate `decision` the
220
+ * pre-existing callers already read.
205
221
  */
206
- function makeConsensusInvoke(endpoints, { timeoutMs } = {}) {
222
+ function makeConsensusInvoke(endpoints, { timeoutMs, scanRoot, purpose } = {}) {
207
223
  const list = parseEndpoints(endpoints);
208
- if (list.length === 0) return null;
209
- return async (prompt) => {
210
- const answers = await Promise.all(list.map(async (url) => {
224
+ if (list.length === 0) return { invoke: null, decisions: [] };
225
+ const decisions = list.map(url => (0,policy/* evaluateEgress */.nn)({ scanRoot, purpose: purpose || 'discovery-consensus', endpoint: url }));
226
+ const allowedList = list.filter((_, i) => decisions[i].allowed);
227
+ if (allowedList.length === 0) return { invoke: null, decisions };
228
+ const invoke = async (prompt) => {
229
+ const answers = await Promise.all(allowedList.map(async (url) => {
211
230
  try { return await defaultLlmInvoke(prompt, { timeoutMs, endpoint: url }); }
212
231
  catch { return null; } // excluded from the vote, never counted as dissent
213
232
  }));
@@ -215,22 +234,61 @@ function makeConsensusInvoke(endpoints, { timeoutMs } = {}) {
215
234
  if (value === null) throw new Error('no LLM endpoint answered');
216
235
  return value;
217
236
  };
237
+ return { invoke, decisions };
218
238
  }
219
239
 
220
- function resolveLlmInvoke(opts = {}) {
221
- // Precedence, most explicit first: an injected callback beats configuration,
222
- // and a multi-endpoint list beats a single endpoint. A caller who supplied
223
- // their own function must always get exactly that function.
224
- if (opts.llmInvoke) return opts.llmInvoke;
240
+ // FR-601: egress policy is evaluated here, BEFORE returning a callable and
241
+ // therefore before either caller (hunter.js, disprove.js) builds a prompt —
242
+ // a denied decision makes this resolve to `invoke: null`, which both callers
243
+ // already treat as "nothing to call" via their existing degrade path, so a
244
+ // denial produces no network request through the exact same code path a
245
+ // missing endpoint always has. `decision` carries the machine-readable
246
+ // reason so callers can distinguish "not configured" from "configured but
247
+ // policy-denied" in their own degrade message, rather than reporting a
248
+ // generic "not set" that would be actively wrong once policy is what
249
+ // blocked the call.
250
+ //
251
+ // FR-605 (assurance-hardening PRD): consensus mode (multiple endpoints) is
252
+ // now egress-filtered per endpoint, same as the single-endpoint path below —
253
+ // this is what closes the actual "a remote URL cannot be smuggled into
254
+ // local-only configuration" gap the paragraph below used to describe as
255
+ // open. Per-endpoint CONSTRAINT dimensions beyond allow/deny/local-only
256
+ // (role/region/repository/path/data-class — one provider allowed, another
257
+ // denied for a REASON beyond the deny-list) remain FR-602's separate scope;
258
+ // what changed here is that the single allow/deny/local-only gate FR-601
259
+ // already built is no longer bypassable just by using multiple endpoints
260
+ // instead of one.
261
+ function resolveLlmInvokeWithDecision(opts = {}) {
262
+ // An injected callback is a test/consumer-controlled escape hatch — it
263
+ // bypasses egress the same way it already bypasses endpoint resolution,
264
+ // because there is no real endpoint here for a policy to evaluate.
265
+ if (opts.llmInvoke) return { invoke: opts.llmInvoke, decision: null };
225
266
 
226
267
  const multi = opts.endpoints || process.env[DEFAULT_CONSENSUS_ENV];
227
268
  if (multi) {
228
- const consensus = makeConsensusInvoke(multi, { timeoutMs: opts.timeoutMs });
229
- if (consensus) return consensus;
269
+ const { invoke, decisions } = makeConsensusInvoke(multi, { timeoutMs: opts.timeoutMs, scanRoot: opts.scanRoot, purpose: opts.purpose });
270
+ // A single aggregate `decision` for the pre-existing callers (hunter.js,
271
+ // disprove.js), which only ever read `.reason` when `invoke` is null —
272
+ // the all-denied case. The full per-endpoint detail is on `decisions`
273
+ // for a caller that wants it.
274
+ const decision = decisions.find(d => !d.allowed) || decisions[0] || null;
275
+ return { invoke, decision, decisions };
230
276
  }
231
277
 
232
- if (!process.env.AGENTIC_SECURITY_LLM_ENDPOINT) return null;
233
- return (prompt) => defaultLlmInvoke(prompt, { timeoutMs: opts.timeoutMs });
278
+ const endpoint = process.env.AGENTIC_SECURITY_LLM_ENDPOINT;
279
+ if (!endpoint) return { invoke: null, decision: null };
280
+
281
+ const decision = (0,policy/* evaluateEgress */.nn)({ scanRoot: opts.scanRoot, purpose: opts.purpose || 'discovery', endpoint });
282
+ if (!decision.allowed) return { invoke: null, decision };
283
+
284
+ return { invoke: (prompt) => defaultLlmInvoke(prompt, { timeoutMs: opts.timeoutMs }), decision };
285
+ }
286
+
287
+ function resolveLlmInvoke(opts = {}) {
288
+ // Precedence, most explicit first: an injected callback beats configuration,
289
+ // and a multi-endpoint list beats a single endpoint. A caller who supplied
290
+ // their own function must always get exactly that function.
291
+ return resolveLlmInvokeWithDecision(opts).invoke;
234
292
  }
235
293
 
236
294
  ;// CONCATENATED MODULE: ./src/discovery/hunter.js
@@ -311,12 +369,16 @@ async function runHunter(focusArea, lens, ctx = {}, opts = {}) {
311
369
  const transcript = [];
312
370
  const lensKey = lens?.key || 'unknown';
313
371
  const base = { focusAreaId: focusArea.id, lens: lensKey, transcript };
314
- const llmInvoke = resolveLlmInvoke(opts);
372
+ const { invoke: llmInvoke, decision: egressDecision } = resolveLlmInvokeWithDecision({ ...opts, purpose: 'discovery-hunter' });
315
373
 
316
374
  if (typeof llmInvoke !== 'function') {
317
- const reason = 'no llmInvoke supplied and AGENTIC_SECURITY_LLM_ENDPOINT not set';
318
- appendEntry(transcript, { phase: 'init', reason });
319
- return { ...base, candidates: [], degraded: true, reason };
375
+ // FR-601: distinguish "policy denied a configured endpoint" from "nothing
376
+ // was configured at all" — the reason must reflect which actually happened.
377
+ const reason = egressDecision
378
+ ? `egress policy denied this call: ${egressDecision.reason}`
379
+ : 'no llmInvoke supplied and AGENTIC_SECURITY_LLM_ENDPOINT not set';
380
+ appendEntry(transcript, { phase: 'init', reason, egressDecision: egressDecision || undefined });
381
+ return { ...base, candidates: [], degraded: true, reason, egressDecision };
320
382
  }
321
383
 
322
384
  let prompt;
@@ -454,7 +516,11 @@ function parseVote(raw) {
454
516
 
455
517
  async function disproveCandidate(candidate, opts = {}) {
456
518
  const angles = Array.isArray(opts.angles) && opts.angles.length ? opts.angles : DEFAULT_ANGLES;
457
- const llmInvoke = resolveLlmInvoke(opts);
519
+ // FR-601: an egress-policy denial resolves llmInvoke to null the same way a
520
+ // missing endpoint always has, so it falls straight into this module's own
521
+ // pre-existing rule — "silence never refutes" — with zero votes cast and no
522
+ // prompt ever built for a denied endpoint.
523
+ const { invoke: llmInvoke, decision: egressDecision } = resolveLlmInvokeWithDecision({ ...opts, purpose: 'discovery-disprove' });
458
524
 
459
525
  const votes = [];
460
526
  if (typeof llmInvoke === 'function') {
@@ -469,7 +535,7 @@ async function disproveCandidate(candidate, opts = {}) {
469
535
  const refuteCount = votes.filter(v => v.refuted).length;
470
536
  const undecided = voterCount === 0;
471
537
  const refuted = !undecided && refuteCount * 2 > voterCount;
472
- return { ...candidate, refutation: { votes, voterCount, refuteCount, refuted, undecided } };
538
+ return { ...candidate, refutation: { votes, voterCount, refuteCount, refuted, undecided, egressDecision: egressDecision || undefined } };
473
539
  }
474
540
 
475
541
  async function disprovePanel(candidates, opts = {}) {
@@ -800,7 +866,7 @@ function makeTaintProbe(perFileIR, callGraph) {
800
866
 
801
867
  async function runDeepAnalysisSafe(perFileIR, callGraph) {
802
868
  try {
803
- const { runDeepAnalysis } = await Promise.resolve(/* import() */).then(__webpack_require__.bind(__webpack_require__, 6732));
869
+ const { runDeepAnalysis } = await Promise.resolve(/* import() */).then(__webpack_require__.bind(__webpack_require__, 7165));
804
870
  return runDeepAnalysis(perFileIR, callGraph, {});
805
871
  } catch {
806
872
  return null;
@@ -925,7 +991,7 @@ async function runDiscovery(ctx = {}, opts = {}) {
925
991
  for (const area of areas) {
926
992
  let areaDegradedCount = 0;
927
993
  for (const lens of lenses) {
928
- const run = await runHunter(area, lens, { fileContents: ctx.fileContents || {} }, { llmInvoke });
994
+ const run = await runHunter(area, lens, { fileContents: ctx.fileContents || {} }, { llmInvoke, scanRoot: opts.scanRoot });
929
995
  runs.push({ focusAreaId: run.focusAreaId, lens: run.lens, degraded: run.degraded, reason: run.reason, candidateCount: run.candidates.length });
930
996
  if (run.degraded && run.reason) reasons.push(`${area.label} × ${lens.key}: ${run.reason}`);
931
997
  if (run.degraded) areaDegradedCount += 1;
@@ -987,7 +1053,7 @@ async function runDiscovery(ctx = {}, opts = {}) {
987
1053
  'every candidate is reported unconfirmed and severity is not evidence-derived');
988
1054
  }
989
1055
  const confirmed = await confirmAll(candidates, { taintProbe });
990
- const { survivors, refuted } = await disprovePanel(confirmed, { llmInvoke });
1056
+ const { survivors, refuted } = await disprovePanel(confirmed, { llmInvoke, scanRoot: opts.scanRoot });
991
1057
  const { fresh, duplicates, suppressed } = judgeCandidates(survivors, ctx.priorScan, ctx.triageFeedback);
992
1058
 
993
1059
  // A spent budget is a coverage gap, stated once at the top level rather than