@clear-capabilities/agentic-security-scanner 0.150.1 → 0.151.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 (89) hide show
  1. package/CHANGELOG.md +137 -0
  2. package/bin/agentic-security.js +73 -10
  3. package/dist/1122.index.js +16 -16
  4. package/dist/1208.index.js +23 -0
  5. package/dist/1301.index.js +3 -3
  6. package/dist/1379.index.js +3 -3
  7. package/dist/1444.index.js +3 -3
  8. package/dist/1660.index.js +1 -1
  9. package/dist/1700.index.js +5 -5
  10. package/dist/1905.index.js +12 -12
  11. package/dist/1920.index.js +10 -10
  12. package/dist/2238.index.js +4 -4
  13. package/dist/2271.index.js +3 -3
  14. package/dist/2432.index.js +8 -8
  15. package/dist/2659.index.js +1 -1
  16. package/dist/2826.index.js +2 -2
  17. package/dist/2830.index.js +3 -3
  18. package/dist/2923.index.js +1 -1
  19. package/dist/3001.index.js +4 -4
  20. package/dist/3117.index.js +4 -4
  21. package/dist/3180.index.js +9 -9
  22. package/dist/3276.index.js +1 -1
  23. package/dist/3415.index.js +1 -1
  24. package/dist/3499.index.js +1 -1
  25. package/dist/3518.index.js +4 -4
  26. package/dist/3736.index.js +7 -7
  27. package/dist/3839.index.js +4 -4
  28. package/dist/4113.index.js +441 -418
  29. package/dist/4265.index.js +4 -4
  30. package/dist/4384.index.js +8 -3
  31. package/dist/4399.index.js +29 -3
  32. package/dist/4547.index.js +2 -2
  33. package/dist/4863.index.js +6 -6
  34. package/dist/4970.index.js +2 -2
  35. package/dist/5051.index.js +15 -15
  36. package/dist/5144.index.js +4 -4
  37. package/dist/5333.index.js +8 -8
  38. package/dist/5343.index.js +2 -2
  39. package/dist/5350.index.js +6 -6
  40. package/dist/5561.index.js +1 -1
  41. package/dist/5637.index.js +10 -5
  42. package/dist/5724.index.js +21642 -0
  43. package/dist/5756.index.js +99 -489
  44. package/dist/5830.index.js +3 -3
  45. package/dist/6257.index.js +21 -8
  46. package/dist/6626.index.js +5 -5
  47. package/dist/6662.index.js +1 -1
  48. package/dist/6675.index.js +4 -4
  49. package/dist/6730.index.js +10 -10
  50. package/dist/6829.index.js +4 -4
  51. package/dist/6944.index.js +2 -2
  52. package/dist/6994.index.js +5 -5
  53. package/dist/7039.index.js +83 -19
  54. package/dist/7178.index.js +11 -6
  55. package/dist/7227.index.js +3 -3
  56. package/dist/7552.index.js +2 -2
  57. package/dist/7709.index.js +2 -2
  58. package/dist/7838.index.js +1037 -0
  59. package/dist/8218.index.js +4 -4
  60. package/dist/8476.index.js +4 -4
  61. package/dist/8513.index.js +8 -8
  62. package/dist/8520.index.js +1 -1
  63. package/dist/8752.index.js +4 -4
  64. package/dist/9207.index.js +2 -2
  65. package/dist/9220.index.js +5 -5
  66. package/dist/9390.index.js +6 -6
  67. package/dist/9503.index.js +2 -2
  68. package/dist/9560.index.js +3232 -0
  69. package/dist/957.index.js +4 -4
  70. package/dist/9801.index.js +1 -1
  71. package/dist/9824.index.js +5 -5
  72. package/dist/agentic-security.mjs +23 -23
  73. package/dist/agentic-security.mjs.sha256 +1 -1
  74. package/package.json +21 -3
  75. package/src/dataflow/catalog.js +52 -0
  76. package/src/egress/redact.js +1 -1
  77. package/src/engine.js +13 -2
  78. package/src/lineage/source-registry.js +8 -0
  79. package/src/llm-validator/agent-loop.js +85 -5
  80. package/src/llm-validator/fix-proposal.js +16 -3
  81. package/src/llm-validator/model-capabilities.js +26 -1
  82. package/src/llm-validator/model-probe.js +46 -8
  83. package/src/llm-validator/ollama-provider.js +26 -1
  84. package/src/llm-validator/oom-feedback.js +69 -0
  85. package/src/llm-validator/providers.js +50 -0
  86. package/src/llm-validator/redact.js +177 -11
  87. package/src/posture/deterministic-fix.js +11 -0
  88. package/src/report/index.js +14 -3
  89. package/src/sast/java-ast-folding.js +18 -2
package/CHANGELOG.md CHANGED
@@ -9,6 +9,143 @@
9
9
  > make the history less accurate, not more.
10
10
 
11
11
 
12
+ ## 0.151.0 - SARD/Juliet benchmarking: leakage-clean scoring, macro-F1, mutation testing, and fix verification (SARD_AGENTIC_SECURITY_PRD.md)
13
+
14
+ Builds a full benchmarking subsystem against NIST SARD's Juliet (Java/C#) and PHP Vulnerability
15
+ Test Suite corpora, extending the pre-existing `bench-realworld.js` harness rather than
16
+ duplicating it. New: `bench/sard/` (dataset lockfile, PHP ingestion, leakage audit, macro-F1 +
17
+ per-CWE + CWE-confusion-matrix + localization-accuracy scoring, structural train/dev/test
18
+ splitting with a duplicate-crossing audit, semantic mutation testing, and independent fix
19
+ verification), plus a `--cwe` targeted/smoke-run flag and a new CI job (`sard-blind-smoke`)
20
+ measuring genuine leakage-clean detection quality separately from the pre-existing
21
+ non-blind `sard-juliet-java` job (which tracks a different thing: whether the corpus-shape-aware
22
+ fallback code still works, not detection quality).
23
+
24
+ **Real, measured numbers** (all reproduced, none quoted without a fresh run — see
25
+ `bench/sard/IMPLEMENTATION_STATUS.md` for the full ledger and every command that produced them):
26
+ Java macro-F1 45.6% (P=66.5%, R=36.9%, leakage-clean, vulnerability-level scoring); C# macro-F1
27
+ 8.4% (26/32 CWE families found to have zero detector coverage at all — a real, root-caused gap,
28
+ not yet fixed); Semantic Robustness Rate 100% (84/84 real mutations survived, sanity-checked
29
+ against a deliberately-safe negative control); Fully Verified Fix Rate 100% (32/32 real Java
30
+ weak-hash fixes); leakage reduced from 907,516 to 3 residual hits (99.9997%).
31
+
32
+ **Real engine fixes found via this benchmarking work** (general capability improvements, not
33
+ SARD-specific shortcuts — each verified with `bench:layer-recall`/`test:dataflow` before and
34
+ after):
35
+ - PHP: `$_SESSION`/`$_ENV` added as taint sources (previously entirely absent from the catalog),
36
+ `mysql_query()` given a real taint-dataflow sink (previously only a same-line structural
37
+ regex, missing the dominant assign-then-call-later shape). `bench:layer-recall` PHP taint
38
+ recall moved 12→13; baseline re-recorded (a real improvement, not silently left stale — this
39
+ gate compares for equality, not a floor, precisely to prevent that).
40
+ - Java: `BufferedReader.readLine()`/`Console.readLine()` and `ResultSet.getString`/`getObject`
41
+ added as taint sources, root-caused from real, evidence-based error-cluster analysis (grouping
42
+ false negatives by Juliet's own filename descriptor) rather than guessed.
43
+ - `deterministic-fix.js`'s weak-hash rule had a real Java coverage gap: its `applies()` gate
44
+ already matched Java CWE-327/328/916 findings, but `transform()` had no Java branch at all, so
45
+ every Java weak-hash finding silently produced no fix. Added
46
+ `MessageDigest.getInstance("MD5"|"SHA1")` → `"SHA-256"`.
47
+ - `bench-realworld.js` and `leakage-audit.mjs` both had an unconditional top-level `main()` (and,
48
+ in `bench-realworld.js`'s case, a separate top-level usage-check) with no `import.meta.url`
49
+ guard — importing either file's helper functions as a module silently ran the ENTIRE CLI
50
+ against the *importer's* own `process.argv`. Found the hard way when a new mutation-testing
51
+ script's own `--app`/`--cwe` flags happened to also be valid `bench-realworld.js` flags,
52
+ triggering an unwanted ~160s benchmark run as a side effect of a function import. Both files
53
+ now guard their top-level side effects the same way every other multi-purpose script in
54
+ `bench/sard/scripts/` already did.
55
+ - Root `CLAUDE.md` described `bench:layer-recall:check` as "a FLOOR, not an equality check...
56
+ silent on a rise" — stale documentation of a design the gate itself had already moved past
57
+ (its own code comment says "PRD F12.2 — compares for EQUALITY, not against a floor"). Fixed.
58
+ - `src/lineage/source-registry.js`'s `NO_PROVENANCE_OVERRIDES` table (Data Flow Explorer) had no
59
+ entries for the new `java-io-readline`/`java-resultset-getstring`/`java-resultset-getobject`
60
+ catalog sources, or for the pre-existing `php-session`/`php-env` sources — all five failed the
61
+ registry's own completeness guard (`source-registry.test.js`), which fails loudly rather than
62
+ silently producing an uncategorized graph node. Added (`user-input`/`database-read`/
63
+ `http-cookie`/`env-value` respectively); pinned coverage counts re-measured and updated
64
+ (185 total source entries, 87 `candidate`). Two `bench/sard/scripts/` helpers
65
+ (`analyze-errors.mjs`, `score-php.mjs`) were also wired into `scanner/package.json` after
66
+ `no-orphan-scripts.test.js` flagged them as unreachable from any npm script.
67
+
68
+ 14 new automated tests (`java-taint-flow.test.js` +3, `deterministic-fix.test.js` +3, new
69
+ `sard-leakage-pipeline.test.js` +8 covering PRD §62's literal leakage-injection list and a
70
+ synthetic-fixture integration test through the real neutralization pipeline).
71
+
72
+
73
+
74
+ ## 0.150.2 - Two adversarial-review passes on 0.150.0's Ollama support: redaction, disclosure, and robustness fixes
75
+
76
+ 0.150.0's offline-Ollama release was put through two rounds of adversarial premortem review after
77
+ shipping. Both surfaced real gaps; this release closes them.
78
+
79
+ **Security — secret redaction (the most important fix here).** The general-purpose `redactSecrets`
80
+ function every Ollama-backed role (`fix`/`explain`/`poc`, the `ask` agent loop's `read_file`/
81
+ `search_code` tools) routes untrusted file content through before it reaches a model had several
82
+ real gaps, found and closed in order as review went deeper:
83
+
84
+ 1. Unquoted `.env`/shell-export syntax (`DB_PASSWORD=x`, no quotes) evaded the existing
85
+ quote-requiring pattern entirely — this was a previously-documented, never-fixed-at-the-general-
86
+ level gap (`secret-redaction.test.js`'s own long-standing comment named it).
87
+ 2. Compound identifiers joined by `_`/`-` (`DB_PASSWORD`, `STRIPE_API_KEY`) didn't match even in the
88
+ already-supported quoted form, because a plain `\b` treats `_` as a word character.
89
+ 3. A second review pass found the above fix was narrower than the real threat surface: **camelCase**
90
+ compounds (`authToken`, `apiSecret`), a **JSON-quoted key** (`"password": "value"` — the closing
91
+ quote wasn't consumed before the operator), **YAML** `key: value` syntax (added, deliberately
92
+ scoped to `.yml`/`.yaml` files only, to avoid colliding with the already-tested TypeScript
93
+ type-annotation and JS object-literal-key exclusions), and a secret **split across a string
94
+ concatenation** (`"Super" + "Secret123"` — only the first segment was redacted, leaking the tail)
95
+ all still leaked. All four are fixed, each with an explicit false-positive regression test
96
+ (`primaryKey`/`cacheKey` must not redact; `password: string;` in a `.ts` file must not redact;
97
+ ordinary string-building with no secret-shaped key name must not redact).
98
+
99
+ **Governance — the "AI Assistance" report's role-scope disclosure had a blind spot.** 0.150.0 added
100
+ a report block scoped to the `validate` role, with a warning when another role resolves to a remote
101
+ provider — but `hunt` (the heaviest LLM-driven discovery work) is deliberately not one of
102
+ `providers.js`'s per-role-overridable roles, so it could never appear in that warning, by
103
+ construction. `hunt` is now checked explicitly.
104
+
105
+ **Robustness.**
106
+ - The wall-clock/per-call timeout mismatch in the `ask` agent loop (a slow call could exceed the
107
+ loop's own overall budget, and raising the documented remedy for slow models did nothing to help)
108
+ is fixed and was verified against two real installed Ollama models on real hardware
109
+ (`gemma4:e4b`, `qwen3.5:9b`) — both complete a live tool-calling round trip within the new budget.
110
+ - The capability-probe cache now has a 30-day safety-net TTL and a `--force` flag
111
+ (`agentic-security models test <model> --force`), on top of its existing version+digest+name key.
112
+ - Added `AGENTIC_SECURITY_OLLAMA_DISABLED=1`, a kill switch checked in the one function every Ollama
113
+ call path resolves through — closes a gap where a per-role override could survive an operator
114
+ unsetting only the global preset during an incident.
115
+ - `agentic-security fix`'s Ollama-proposed-patch path now retries exactly once with the
116
+ deterministic gate's own rejection reason fed back into the prompt, instead of asking an
117
+ identical (temperature-0) question and getting an identical bad patch back. Automated test
118
+ coverage added (`test/cli/fix-retry.test.js`); previously this had only ever been verified by a
119
+ single manual run.
120
+ - A real, observed out-of-memory failure is now recorded per-model and surfaced as a warning
121
+ wherever that model is used again — including proactively in the `ask` loop itself, not only in
122
+ `models doctor` (a user who never runs `doctor` used to get no warning at all before repeating the
123
+ same failure).
124
+ - Built the PRD's own required opt-in live-model contract tier (`AGENTIC_SECURITY_OLLAMA_E2E=1 npm
125
+ run test:ollama-e2e`), which did not exist at 0.150.0 despite being an explicit requirement — run
126
+ against two real installed models, all 7 required contract checks pass. This tests the WIRE
127
+ CONTRACT (valid structured output, a real tool-call round trip, prompt-injection resistance,
128
+ graceful context-overflow handling), not output QUALITY — a model-quality benchmark suite remains
129
+ future work, and `docs/guides/ollama.md` says so explicitly rather than letting contract-passing
130
+ read as quality-proven.
131
+
132
+ **Documentation.** `docs/guides/ollama.md` gained an honest statement of the Ollama supply-chain
133
+ trust boundary (no Sigstore-equivalent verification exists for the `ollama` binary or pulled model
134
+ weights, unlike this project's own npm dependencies) and a doc-drift test
135
+ (`test/ollama-doc-drift.test.js`) that fails if the guide's stated numeric defaults (timeouts,
136
+ context sizes, cache TTL, the tool-loop iteration ceiling) ever diverge from the real source
137
+ constants — verified by deliberately breaking one and confirming the test catches it.
138
+
139
+ **Clarification, not a correction, to 0.150.0's own wording** (this project's changelog convention
140
+ is to never rewrite a past entry): "the same offline guarantee the existing `local` preset already
141
+ made — nothing leaves the machine" describes LLM inference traffic only. The same scan still makes
142
+ separate, deterministic OSV/KEV/EPSS network calls unless `AGENTIC_SECURITY_OFFLINE=1` is also set —
143
+ `docs/guides/ollama.md` already said this; this entry says it here too, next to the original claim.
144
+ Separately, PRD §26 asked for a combined validate/verify/explain/fix/hunt "AI Assistance" summary;
145
+ what shipped in 0.150.0 measures the `validate` role only (the one role that runs automatically
146
+ during a plain `scan`) — `docs/guides/ollama.md` and this entry disclose the scope-down; the other
147
+ five roles are each single-finding or discovery-run commands with no natural combined-scan moment to
148
+ summarize.
12
149
 
13
150
  ## 0.150.1 - Fix 0.150.0's release-gate failure: @babel/core dependency currency
14
151
 
@@ -1938,6 +1938,7 @@ async function cmdModels(args) {
1938
1938
  const { getModelCapabilities } = await import('../src/llm-validator/model-probe.js');
1939
1939
  const { resolveProvider } = await import('../src/llm-validator/providers.js');
1940
1940
  const wantsProbe = !!args.flags.probe;
1941
+ const wantsForceProbe = !!args.flags.force;
1941
1942
 
1942
1943
  const envOverride = {};
1943
1944
  if (args.flags.host) envOverride.AGENTIC_SECURITY_OLLAMA_HOST = String(args.flags.host);
@@ -2004,12 +2005,16 @@ async function cmdModels(args) {
2004
2005
  } else {
2005
2006
  lines.push(` ✗ memory admission FAILED — ${admission.reason}`);
2006
2007
  }
2007
- const capsResult = await getModelCapabilities({ host: cfg.config.host, model, probe: wantsProbe });
2008
+ // Adversarial-review fix (2026-09): a memory estimate that has already
2009
+ // been proven wrong by a real OOM on this machine no longer gets
2010
+ // presented with the same unqualified confidence.
2011
+ if (admission.priorOOMWarning) lines.push(` ⚠ ${admission.priorOOMWarning}`);
2012
+ const capsResult = await getModelCapabilities({ host: cfg.config.host, model, probe: wantsProbe, force: wantsForceProbe });
2008
2013
  const caps = capsResult.capabilities;
2009
2014
  const capSourceLabel = caps.source?.runtimeProbe ? (capsResult.cached ? 'runtime-probed, cached' : 'runtime-probed')
2010
2015
  : caps.source?.metadata ? 'Ollama metadata' : 'family hint — not runtime-probed';
2011
2016
  lines.push(` chat=${caps.chat ? 'yes' : 'no'} structuredJson=${caps.structuredJson} tools=${caps.tools} (${capSourceLabel})`);
2012
- if (wantsProbe) lines.push(capsResult.cached ? ' ✓ capability probe cached' : ' ✓ capability probe ran (now cached)');
2017
+ if (wantsProbe) lines.push(capsResult.cached ? ' ✓ capability probe cached (pass --force to re-probe)' : ' ✓ capability probe ran (now cached)');
2013
2018
  else lines.push(' ↗ run with --probe to runtime-verify structured output / tool calling (consumes inference time)');
2014
2019
  } else {
2015
2020
  lines.push('Default model: (none resolved)');
@@ -2039,7 +2044,7 @@ async function cmdModels(args) {
2039
2044
  if (!modelsResult.ok) { console.log(`✗ Ollama server is not reachable at ${cfg.config.host} (${modelsResult.code})`); return 1; }
2040
2045
  const info = modelsResult.models.find((m) => m.name === name);
2041
2046
  const family = classifyModelFamily(name);
2042
- const capsResult = await getModelCapabilities({ host: cfg.config.host, model: name, probe: wantsProbe });
2047
+ const capsResult = await getModelCapabilities({ host: cfg.config.host, model: name, probe: wantsProbe, force: wantsForceProbe });
2043
2048
  const caps = capsResult.capabilities;
2044
2049
  const out = { name, installed: !!info, family, capabilities: caps, cached: capsResult.cached, metadata: info || null };
2045
2050
  if (args.flags.json) { writeStdout(JSON.stringify(out, null, 2) + '\n'); return 0; }
@@ -2073,19 +2078,25 @@ async function cmdModels(args) {
2073
2078
  console.log(`✗ Model '${name}' is not installed. Run \`ollama pull ${name}\` first.`);
2074
2079
  return 1;
2075
2080
  }
2076
- const capsResult = await getModelCapabilities({ host: cfg.config.host, model: name, probe: true });
2081
+ // ollama-offline-prd.md premortem fix (2026-09): the capability cache has
2082
+ // no expiry beyond its version+digest+name key, which doesn't always
2083
+ // change on a same-tag re-pull — `--force` is the documented way to get
2084
+ // a definitively fresh answer right now, mirroring the existing
2085
+ // `validator-cache stats|gc --older-than <days>` convention rather than
2086
+ // leaving `test` unable to ever overrule its own past result.
2087
+ const capsResult = await getModelCapabilities({ host: cfg.config.host, model: name, probe: true, force: !!args.flags.force });
2077
2088
  const caps = capsResult.capabilities;
2078
2089
  if (args.flags.json) {
2079
2090
  writeStdout(JSON.stringify({ ok: true, name, capabilities: caps, cached: capsResult.cached }, null, 2) + '\n');
2080
2091
  return 0;
2081
2092
  }
2082
- console.log(`agentic-security models test ${name}`);
2093
+ console.log(`agentic-security models test ${name}${args.flags.force ? ' --force' : ''}`);
2083
2094
  console.log('');
2084
2095
  console.log(` chat: ${caps.chat ? '✓ yes' : '✗ no'}`);
2085
2096
  console.log(` structured JSON: ${caps.structuredJson === true ? '✓ yes' : caps.structuredJson === false ? '✗ no' : '? unknown (probe inconclusive)'}`);
2086
2097
  console.log(` tool calling: ${caps.tools === true ? '✓ yes' : caps.tools === false ? '✗ no' : '? unknown (probe inconclusive)'}`);
2087
2098
  if (caps.contextTokens) console.log(` context window: ${caps.contextTokens} tokens`);
2088
- console.log(` ${capsResult.cached ? '✓ capability probe cached (already ran before)' : '✓ capability probe ran (now cached)'}`);
2099
+ console.log(` ${capsResult.cached ? '✓ capability probe cached (already ran before — pass --force to re-probe)' : '✓ capability probe ran (now cached)'}`);
2089
2100
  return 0;
2090
2101
  }
2091
2102
 
@@ -2109,7 +2120,7 @@ async function cmdModels(args) {
2109
2120
  return 1;
2110
2121
  }
2111
2122
 
2112
- console.error('Usage: agentic-security models <list|status|doctor|inspect <model>|test <model>|pull <model>> [--host <url>] [--json] [--probe]');
2123
+ console.error('Usage: agentic-security models <list|status|doctor|inspect <model>|test <model>|pull <model>> [--host <url>] [--json] [--probe] [--force]');
2113
2124
  console.error(`Default Ollama host: ${DEFAULT_OLLAMA_HOST}`);
2114
2125
  return 4;
2115
2126
  }
@@ -2126,17 +2137,29 @@ async function cmdModels(args) {
2126
2137
  // "does this project have a rate limiter").
2127
2138
  async function cmdAsk(args) {
2128
2139
  const goal = args._[1];
2129
- if (!goal) { console.error('Usage: agentic-security ask "<question>" [target] [--max-iterations N]'); return 4; }
2140
+ if (!goal) { console.error('Usage: agentic-security ask "<question>" [target] [--max-iterations N] [--timeout-ms N]'); return 4; }
2130
2141
  const target = path.resolve(args._[2] && !args._[2].startsWith('--') ? args._[2] : '.');
2131
2142
  const { runAgentLoop, AGENT_LOOP_ERROR, DEFAULT_MAX_TOOL_ITERATIONS } = await import('../src/llm-validator/agent-loop.js');
2132
2143
 
2133
2144
  const envOverride = {};
2134
2145
  if (args.flags.host) envOverride.AGENTIC_SECURITY_OLLAMA_HOST = String(args.flags.host);
2135
2146
  if (args.flags['allow-remote-ollama']) envOverride.AGENTIC_SECURITY_OLLAMA_ALLOW_REMOTE = '1';
2147
+ // ollama-offline-prd.md premortem fix (2026-09): --timeout-ms sets the
2148
+ // LOOP's own wall-clock budget (AGENTIC_SECURITY_LLM_AGENT_TIMEOUT_MS),
2149
+ // which used to be a fixed 5 minutes no matter how high
2150
+ // AGENTIC_SECURITY_LLM_TIMEOUT_MS (the PER-CALL timeout, still the right
2151
+ // knob for "this one model reply is slow") was raised — raising only the
2152
+ // per-call setting could not extend the loop, since a single slow call
2153
+ // could already exceed the whole fixed budget.
2154
+ if (args.flags['timeout-ms']) envOverride.AGENTIC_SECURITY_LLM_AGENT_TIMEOUT_MS = String(args.flags['timeout-ms']);
2136
2155
  const env = { ...process.env, ...envOverride };
2137
2156
  const maxToolIterations = args.flags['max-iterations'] ? parseInt(args.flags['max-iterations'], 10) : DEFAULT_MAX_TOOL_ITERATIONS;
2138
2157
 
2139
2158
  const r = await runAgentLoop({ goal, scanRoot: target, env, maxToolIterations });
2159
+ // Adversarial-review fix (2026-09): surfaced here, not just in `models
2160
+ // doctor`, so a user who never runs `doctor` still sees it before/after
2161
+ // the exact command that would otherwise repeat a known OOM.
2162
+ if (r.priorOOMWarning) console.log(`⚠ ${r.priorOOMWarning}`);
2140
2163
 
2141
2164
  if (!r.ok) {
2142
2165
  if (r.code === AGENT_LOOP_ERROR.NOT_CONFIGURED) {
@@ -2170,7 +2193,7 @@ async function cmdAsk(args) {
2170
2193
  console.log(`✗ Stopped after the ${DEFAULT_MAX_TOOL_ITERATIONS}-iteration bound without a final answer. Try narrowing the question.`);
2171
2194
  return 1;
2172
2195
  }
2173
- console.log('✗ Stopped: wall-clock timeout reached without a final answer.');
2196
+ console.log('✗ Stopped: wall-clock timeout reached without a final answer. Try --timeout-ms <N> for a slow/cold-loading model (raising AGENTIC_SECURITY_LLM_TIMEOUT_MS alone does not extend this budget).');
2174
2197
  return 1;
2175
2198
  }
2176
2199
 
@@ -3475,12 +3498,51 @@ async function cmdFix(args) {
3475
3498
  const fixMeta = (approvedBy || approvalReason || patchAuthor)
3476
3499
  ? { approval: { approvedBy: approvedBy || '', reason: approvalReason || '' }, ...(patchAuthor ? { author: patchAuthor } : {}) }
3477
3500
  : null;
3478
- const result = await applyVerifiedFix({
3501
+ let result = await applyVerifiedFix({
3479
3502
  scanRoot,
3480
3503
  finding: { file: f.file, id: f.id, stableId: f.stableId || null, ruleId: f.cwe || f.title, vuln: f.vuln || f.title },
3481
3504
  files: { [f.file]: newContent },
3482
3505
  fixMeta,
3483
3506
  });
3507
+ // Adversarial-review fix (2026-09): an Ollama-sourced patch that gets
3508
+ // rejected used to just fail outright — at temperature 0, a bare re-run
3509
+ // of `fix` would almost certainly ask the identical question and get the
3510
+ // identical bad patch back, burning the finite retry budget
3511
+ // (applyVerifiedFix's own maxAttempts) with no chance of a better outcome.
3512
+ // Orchestration test coverage: test/cli/fix-retry.test.js (real spawned
3513
+ // CLI + fake Ollama server; statically skipped in this sandbox's
3514
+ // documented child-process-loopback limitation, same as
3515
+ // test/cli/models.test.js — flip to `test` where a spawned child can
3516
+ // reach the parent's loopback server).
3517
+ // ONE bounded retry, feeding the gate's own rejection reason back into the
3518
+ // prompt so the model has an actual reason to propose something different
3519
+ // — never more than one, matching this codebase's existing "exactly one
3520
+ // retry" philosophy for model output elsewhere (callOllamaStructured's
3521
+ // schema retry). A deterministic/stored patch (no ollamaFixMeta) is never
3522
+ // retried — asking the same static template again cannot produce a
3523
+ // different answer.
3524
+ if (!result.ok && ollamaFixMeta && !result.budgetExceeded) {
3525
+ console.log(`AI-assisted proposal was rejected (${result.reason}) — asking for one revised attempt...`);
3526
+ const { proposeOllamaFix } = await import('../src/llm-validator/fix-proposal.js');
3527
+ const retryProposal = await proposeOllamaFix({
3528
+ finding: { file: f.file, line: f.line, vuln: f.vuln, cwe: f.cwe, severity: f.severity },
3529
+ fileContent: originalContent,
3530
+ scanRoot,
3531
+ rejectionFeedback: result.reason,
3532
+ });
3533
+ if (retryProposal.ok) {
3534
+ const retryResult = await applyVerifiedFix({
3535
+ scanRoot,
3536
+ finding: { file: f.file, id: f.id, stableId: f.stableId || null, ruleId: f.cwe || f.title, vuln: f.vuln || f.title },
3537
+ files: { [f.file]: retryProposal.replacement },
3538
+ fixMeta,
3539
+ });
3540
+ result = retryResult;
3541
+ if (retryResult.ok) {
3542
+ console.log(` revised proposal accepted (rationale: ${retryProposal.rationale || 'n/a'})`);
3543
+ }
3544
+ }
3545
+ }
3484
3546
  if (!result.ok) {
3485
3547
  console.error(`Refusing to apply: ${result.reason}`);
3486
3548
  if (result.budgetExceeded) console.error(` (${result.attempts}/${result.maxAttempts} attempts already made for this finding)`);
@@ -3620,6 +3682,7 @@ async function cmdSetupLlmOllama(args) {
3620
3682
  if (!admission.admitted) {
3621
3683
  console.log(` ↗ Memory admission check: ${admission.reason}`);
3622
3684
  }
3685
+ if (admission.priorOOMWarning) console.log(` ⚠ ${admission.priorOOMWarning}`);
3623
3686
  console.log('');
3624
3687
  console.log('To use this configuration, export:');
3625
3688
  console.log(' export AGENTIC_SECURITY_LLM_PRESET=ollama');
@@ -2,7 +2,7 @@ export const id = 1122;
2
2
  export const ids = [1122,3180];
3
3
  export const modules = {
4
4
 
5
- /***/ 1122:
5
+ /***/ 21122:
6
6
  /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
7
7
 
8
8
 
@@ -16,17 +16,17 @@ __webpack_require__.d(__webpack_exports__, {
16
16
  // UNUSED EXPORTS: _validTimeoutMs
17
17
 
18
18
  // EXTERNAL MODULE: external "node:fs"
19
- var external_node_fs_ = __webpack_require__(3024);
19
+ var external_node_fs_ = __webpack_require__(73024);
20
20
  // EXTERNAL MODULE: external "node:os"
21
- var external_node_os_ = __webpack_require__(8161);
21
+ var external_node_os_ = __webpack_require__(48161);
22
22
  // EXTERNAL MODULE: external "node:path"
23
- var external_node_path_ = __webpack_require__(6760);
23
+ var external_node_path_ = __webpack_require__(76760);
24
24
  // EXTERNAL MODULE: external "node:child_process"
25
- var external_node_child_process_ = __webpack_require__(1421);
25
+ var external_node_child_process_ = __webpack_require__(31421);
26
26
  // EXTERNAL MODULE: external "node:url"
27
- var external_node_url_ = __webpack_require__(3136);
27
+ var external_node_url_ = __webpack_require__(73136);
28
28
  // EXTERNAL MODULE: ./scripts/generate-html-report.mjs + 1 modules
29
- var generate_html_report = __webpack_require__(3180);
29
+ var generate_html_report = __webpack_require__(73180);
30
30
  ;// CONCATENATED MODULE: ./src/ir/chrome-probe.mjs
31
31
  // chrome-probe.mjs — Milestone 4, sub-project PNG/SVG/PDF export.
32
32
  //
@@ -402,7 +402,7 @@ async function exportSvg(graph, opts = {}) {
402
402
 
403
403
  /***/ }),
404
404
 
405
- /***/ 3180:
405
+ /***/ 73180:
406
406
  /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
407
407
 
408
408
 
@@ -412,11 +412,11 @@ __webpack_require__.d(__webpack_exports__, {
412
412
  });
413
413
 
414
414
  // EXTERNAL MODULE: external "node:fs"
415
- var external_node_fs_ = __webpack_require__(3024);
415
+ var external_node_fs_ = __webpack_require__(73024);
416
416
  // EXTERNAL MODULE: external "node:path"
417
- var external_node_path_ = __webpack_require__(6760);
417
+ var external_node_path_ = __webpack_require__(76760);
418
418
  // EXTERNAL MODULE: external "node:url"
419
- var external_node_url_ = __webpack_require__(3136);
419
+ var external_node_url_ = __webpack_require__(73136);
420
420
  ;// CONCATENATED MODULE: ./scripts/bundle-frontend.mjs
421
421
  // bundle-frontend.mjs — Milestone 4, sub-project Self-contained HTML
422
422
  // report. A minimal, hand-rolled ES-module bundler, deliberately NOT a
@@ -596,9 +596,9 @@ function bundleFrontendModules(entryAbsPath) {
596
596
  }
597
597
 
598
598
  // EXTERNAL MODULE: ./src/lineage/export-json.js
599
- var export_json = __webpack_require__(859);
599
+ var export_json = __webpack_require__(40859);
600
600
  // EXTERNAL MODULE: ./src/shared/frontend-root.js
601
- var frontend_root = __webpack_require__(1185);
601
+ var frontend_root = __webpack_require__(61185);
602
602
  ;// CONCATENATED MODULE: ./scripts/generate-html-report.mjs
603
603
  // generate-html-report.mjs — Milestone 4, sub-project Self-contained
604
604
  // HTML report. Assembles ONE offline-safe HTML document: inline CSS,
@@ -712,14 +712,14 @@ ${bundledJs}
712
712
 
713
713
  /***/ }),
714
714
 
715
- /***/ 1185:
715
+ /***/ 61185:
716
716
  /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
717
717
 
718
718
  /* harmony export */ __webpack_require__.d(__webpack_exports__, {
719
719
  /* harmony export */ D: () => (/* binding */ resolveFrontendRoot)
720
720
  /* harmony export */ });
721
- /* harmony import */ var node_fs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(3024);
722
- /* harmony import */ var node_path__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(6760);
721
+ /* harmony import */ var node_fs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(73024);
722
+ /* harmony import */ var node_path__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(76760);
723
723
  // frontend-root.js — locates the Data Flow Explorer's `frontend/` assets
724
724
  // (index.html/src/styles) regardless of whether this code is running:
725
725
  // - unbundled, straight out of scanner/src/ or scanner/scripts/ (dev/test —
@@ -0,0 +1,23 @@
1
+ export const id = 1208;
2
+ export const ids = [1208];
3
+ export const modules = {
4
+
5
+ /***/ 91208:
6
+ /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
7
+
8
+ __webpack_require__.r(__webpack_exports__);
9
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
10
+ /* harmony export */ BaseJavaCstVisitor: () => (/* reexport safe */ _node_modules_java_parser_src_index_js__WEBPACK_IMPORTED_MODULE_0__.ak),
11
+ /* harmony export */ BaseJavaCstVisitorWithDefaults: () => (/* reexport safe */ _node_modules_java_parser_src_index_js__WEBPACK_IMPORTED_MODULE_0__.C5),
12
+ /* harmony export */ "default": () => (/* reexport safe */ _node_modules_java_parser_src_index_js__WEBPACK_IMPORTED_MODULE_0__.Ay),
13
+ /* harmony export */ lexAndParse: () => (/* reexport safe */ _node_modules_java_parser_src_index_js__WEBPACK_IMPORTED_MODULE_0__.Ch),
14
+ /* harmony export */ parse: () => (/* reexport safe */ _node_modules_java_parser_src_index_js__WEBPACK_IMPORTED_MODULE_0__.qg)
15
+ /* harmony export */ });
16
+ /* harmony import */ var _node_modules_java_parser_src_index_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(65724);
17
+
18
+
19
+
20
+
21
+ /***/ })
22
+
23
+ };
@@ -2,7 +2,7 @@ export const id = 1301;
2
2
  export const ids = [1301];
3
3
  export const modules = {
4
4
 
5
- /***/ 1301:
5
+ /***/ 81301:
6
6
  /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
7
7
 
8
8
  /* harmony export */ __webpack_require__.d(__webpack_exports__, {
@@ -11,8 +11,8 @@ export const modules = {
11
11
  /* harmony export */ validateTarget: () => (/* binding */ validateTarget)
12
12
  /* harmony export */ });
13
13
  /* unused harmony export _internals */
14
- /* harmony import */ var node_fs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(3024);
15
- /* harmony import */ var node_path__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(6760);
14
+ /* harmony import */ var node_fs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(73024);
15
+ /* harmony import */ var node_path__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(76760);
16
16
  // Verifier target harness (FR-LIVE-HARNESS).
17
17
  //
18
18
  // Customer projects describe how to bring up the app they want the verifier
@@ -2,7 +2,7 @@ export const id = 1379;
2
2
  export const ids = [1379];
3
3
  export const modules = {
4
4
 
5
- /***/ 1379:
5
+ /***/ 31379:
6
6
  /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
7
7
 
8
8
 
@@ -322,9 +322,9 @@ function renderStageCell(cell, row) {
322
322
  }
323
323
 
324
324
  // EXTERNAL MODULE: ./src/dataflow/privacy-governance.js
325
- var privacy_governance = __webpack_require__(3308);
325
+ var privacy_governance = __webpack_require__(63308);
326
326
  // EXTERNAL MODULE: ./src/lineage/export-json.js
327
- var export_json = __webpack_require__(859);
327
+ var export_json = __webpack_require__(40859);
328
328
  ;// CONCATENATED MODULE: ./src/lineage/export-privacy.js
329
329
  // export-privacy.js — Milestone 4 deliverable #10 (DFG-020): graph-derived
330
330
  // DPIA/RoPA export, migrating off the Layer-2 taint engine's
@@ -2,15 +2,15 @@ export const id = 1444;
2
2
  export const ids = [1444];
3
3
  export const modules = {
4
4
 
5
- /***/ 1444:
5
+ /***/ 51444:
6
6
  /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
7
7
 
8
8
  /* harmony export */ __webpack_require__.d(__webpack_exports__, {
9
9
  /* harmony export */ sweepGitHistory: () => (/* binding */ sweepGitHistory)
10
10
  /* harmony export */ });
11
11
  /* unused harmony exports extractAddedLines, scanHistoryDiff */
12
- /* harmony import */ var node_child_process__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1421);
13
- /* harmony import */ var _util_git_hardening_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(8844);
12
+ /* harmony import */ var node_child_process__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(31421);
13
+ /* harmony import */ var _util_git_hardening_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(78844);
14
14
  // R15 (PRD §5) — git-history secret sweep.
15
15
  //
16
16
  // A secret removed from HEAD but present in any past commit is still
@@ -2,7 +2,7 @@ export const id = 1660;
2
2
  export const ids = [1660];
3
3
  export const modules = {
4
4
 
5
- /***/ 1660:
5
+ /***/ 51660:
6
6
  /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
7
7
 
8
8
  /* harmony export */ __webpack_require__.d(__webpack_exports__, {
@@ -2,17 +2,17 @@ export const id = 1700;
2
2
  export const ids = [1700];
3
3
  export const modules = {
4
4
 
5
- /***/ 1700:
5
+ /***/ 31700:
6
6
  /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
7
7
 
8
8
  /* harmony export */ __webpack_require__.d(__webpack_exports__, {
9
9
  /* harmony export */ validateOsvFunctionsExist: () => (/* binding */ validateOsvFunctionsExist)
10
10
  /* harmony export */ });
11
11
  /* unused harmony export extractPythonPackageFunctions */
12
- /* harmony import */ var node_fs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(3024);
13
- /* harmony import */ var node_path__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(6760);
14
- /* harmony import */ var node_child_process__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(1421);
15
- /* harmony import */ var _ir_parser_py_cst_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(682);
12
+ /* harmony import */ var node_fs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(73024);
13
+ /* harmony import */ var node_path__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(76760);
14
+ /* harmony import */ var node_child_process__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(31421);
15
+ /* harmony import */ var _ir_parser_py_cst_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(40682);
16
16
  // Python package function extraction via the CST parser.
17
17
  //
18
18
  // Locates an installed Python package in site-packages or .venv,
@@ -2,7 +2,7 @@ export const id = 1905;
2
2
  export const ids = [1905,3499];
3
3
  export const modules = {
4
4
 
5
- /***/ 4286:
5
+ /***/ 11905:
6
6
  /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
7
7
 
8
8
 
@@ -14,7 +14,7 @@ __webpack_require__.d(__webpack_exports__, {
14
14
  // UNUSED EXPORTS: makeBudget, makeTaintProbe
15
15
 
16
16
  // EXTERNAL MODULE: external "node:crypto"
17
- var external_node_crypto_ = __webpack_require__(7598);
17
+ var external_node_crypto_ = __webpack_require__(77598);
18
18
  ;// CONCATENATED MODULE: ./src/discovery/partition.js
19
19
  //
20
20
  // Split the codebase into disjoint focus areas so parallel hunters cannot
@@ -110,13 +110,13 @@ function partitionCallGraph(callGraph, opts = {}) {
110
110
  }
111
111
 
112
112
  // EXTERNAL MODULE: ./src/discovery/lenses.js
113
- var discovery_lenses = __webpack_require__(3499);
113
+ var discovery_lenses = __webpack_require__(33499);
114
114
  // EXTERNAL MODULE: ./src/egress/policy.js
115
- var policy = __webpack_require__(5712);
115
+ var policy = __webpack_require__(45712);
116
116
  // EXTERNAL MODULE: ./src/llm-validator/providers.js
117
- var providers = __webpack_require__(8947);
117
+ var providers = __webpack_require__(38947);
118
118
  // EXTERNAL MODULE: ./src/llm-validator/ollama-provider.js
119
- var ollama_provider = __webpack_require__(3837);
119
+ var ollama_provider = __webpack_require__(23837);
120
120
  ;// CONCATENATED MODULE: ./src/discovery/llm-invoke.js
121
121
  //
122
122
  // Shared LLM endpoint caller. Both the hunter and the refutation panel need
@@ -643,7 +643,7 @@ async function disprovePanel(candidates, opts = {}) {
643
643
  }
644
644
 
645
645
  // EXTERNAL MODULE: ./src/posture/stable-id.js
646
- var stable_id = __webpack_require__(838);
646
+ var stable_id = __webpack_require__(90838);
647
647
  ;// CONCATENATED MODULE: ./src/discovery/judge.js
648
648
  //
649
649
  // Turn surviving candidates into findings, then decide which are actually new.
@@ -744,11 +744,11 @@ function judgeCandidates(candidates, priorScan, triageFeedback) {
744
744
  }
745
745
 
746
746
  // EXTERNAL MODULE: external "node:fs"
747
- var external_node_fs_ = __webpack_require__(3024);
747
+ var external_node_fs_ = __webpack_require__(73024);
748
748
  // EXTERNAL MODULE: external "node:path"
749
- var external_node_path_ = __webpack_require__(6760);
749
+ var external_node_path_ = __webpack_require__(76760);
750
750
  // EXTERNAL MODULE: ./src/posture/state-dir.js
751
- var state_dir = __webpack_require__(1174);
751
+ var state_dir = __webpack_require__(31174);
752
752
  ;// CONCATENATED MODULE: ./src/discovery/memory.js
753
753
  // Cross-run discovery memory — PRD Phase 3 / C4.
754
754
  //
@@ -961,7 +961,7 @@ function makeTaintProbe(perFileIR, callGraph) {
961
961
 
962
962
  async function runDeepAnalysisSafe(perFileIR, callGraph) {
963
963
  try {
964
- const { runDeepAnalysis } = await Promise.resolve(/* import() */).then(__webpack_require__.bind(__webpack_require__, 2555));
964
+ const { runDeepAnalysis } = await Promise.resolve(/* import() */).then(__webpack_require__.bind(__webpack_require__, 72555));
965
965
  return runDeepAnalysis(perFileIR, callGraph, {});
966
966
  } catch {
967
967
  return null;
@@ -1250,7 +1250,7 @@ async function runDiscovery(ctx = {}, opts = {}) {
1250
1250
 
1251
1251
  /***/ }),
1252
1252
 
1253
- /***/ 3499:
1253
+ /***/ 33499:
1254
1254
  /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
1255
1255
 
1256
1256
  /* harmony export */ __webpack_require__.d(__webpack_exports__, {