@clear-capabilities/agentic-security-scanner 0.134.0 → 0.136.9

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 (170) hide show
  1. package/CHANGELOG.md +432 -0
  2. package/bin/agentic-security-audit.js +2 -1
  3. package/bin/agentic-security-consistency.js +2 -1
  4. package/bin/agentic-security.js +448 -74
  5. package/dist/113.index.js +16 -7
  6. package/dist/117.index.js +3 -1
  7. package/dist/178.index.js +1 -1
  8. package/dist/207.index.js +5 -4
  9. package/dist/220.index.js +5 -3
  10. package/dist/238.index.js +4 -4
  11. package/dist/317.index.js +300 -0
  12. package/dist/384.index.js +1 -1
  13. package/dist/435.index.js +196 -21
  14. package/dist/444.index.js +20 -11
  15. package/dist/449.index.js +8 -1
  16. package/dist/513.index.js +7 -3
  17. package/dist/526.index.js +6 -6
  18. package/dist/637.index.js +1 -1
  19. package/dist/675.index.js +7 -5
  20. package/dist/839.index.js +4 -3
  21. package/dist/905.index.js +1173 -0
  22. package/dist/agentic-security.mjs +14 -14
  23. package/dist/agentic-security.mjs.sha256 +1 -1
  24. package/dist/compliance-frameworks/ccpa.json +32 -0
  25. package/dist/compliance-frameworks/eu-ai-act.json +51 -0
  26. package/dist/compliance-frameworks/gdpr.json +45 -0
  27. package/dist/compliance-frameworks/hipaa-security-rule.json +56 -0
  28. package/dist/compliance-frameworks/nist-ai-600-1.json +51 -0
  29. package/dist/compliance-frameworks/nist-csf-2.json +73 -0
  30. package/dist/compliance-frameworks/nist-privacy-1-1.json +846 -0
  31. package/dist/compliance-frameworks/owasp-asvs-5.json +79 -0
  32. package/dist/compliance-frameworks/owasp-llm-top-10.json +69 -0
  33. package/package.json +24 -12
  34. package/src/badge.js +2 -1
  35. package/src/dataflow/CLAUDE.md +10 -4
  36. package/src/dataflow/builtin-summaries.js +1 -1
  37. package/src/dataflow/cross-service-taint.js +2 -1
  38. package/src/dataflow/engine.js +324 -60
  39. package/src/dataflow/ifds-precise.js +6 -4
  40. package/src/dataflow/implicit-flow.js +68 -36
  41. package/src/dataflow/incremental.js +25 -8
  42. package/src/dataflow/index.js +2 -1
  43. package/src/dataflow/proven-clean.js +41 -0
  44. package/src/dataflow/sanitizer-gate.js +35 -9
  45. package/src/dataflow/sanitizer-proof.js +21 -3
  46. package/src/dataflow/stub-aware-filter.js +36 -13
  47. package/src/dataflow/summaries.js +21 -2
  48. package/src/discovery/CLAUDE.md +10 -0
  49. package/src/discovery/index.js +175 -3
  50. package/src/discovery/llm-invoke.js +90 -1
  51. package/src/discovery/memory.js +163 -0
  52. package/src/engine.js +247 -50
  53. package/src/integrations/tickets.js +7 -6
  54. package/src/ir/CLAUDE.md +4 -1
  55. package/src/ir/balanced-call.js +55 -0
  56. package/src/ir/ir-stats.js +1 -1
  57. package/src/ir/parser-cpp.js +1 -1
  58. package/src/ir/parser-cs.js +62 -9
  59. package/src/ir/parser-go.js +29 -11
  60. package/src/ir/parser-java.js +96 -19
  61. package/src/ir/parser-js.js +151 -20
  62. package/src/ir/parser-php.js +44 -9
  63. package/src/ir/parser-rb.js +37 -7
  64. package/src/ir/ssa.js +6 -1
  65. package/src/leaderboard.js +3 -2
  66. package/src/llm-validator/consistency.js +6 -2
  67. package/src/llm-validator/index.js +1 -2
  68. package/src/lsp/server.js +28 -2
  69. package/src/mcp/CLAUDE.md +9 -2
  70. package/src/mcp/audit.js +2 -1
  71. package/src/mcp/redact.js +26 -0
  72. package/src/mcp/tools.js +159 -17
  73. package/src/posture/CLAUDE.md +45 -8
  74. package/src/posture/accuracy-scorecard.js +67 -1
  75. package/src/posture/agents-memory.js +5 -3
  76. package/src/posture/aibom.js +12 -8
  77. package/src/posture/auditor-walkthrough.js +111 -10
  78. package/src/posture/auth-posture-import.js +5 -4
  79. package/src/posture/autopilot.js +8 -1
  80. package/src/posture/calibration-drift.js +11 -5
  81. package/src/posture/calibration.js +24 -2
  82. package/src/posture/compliance-frameworks/nist-privacy-1-1.json +846 -0
  83. package/src/posture/compliance-frameworks/owasp-asvs-5.json +1 -1
  84. package/src/posture/compliance-policy.js +40 -10
  85. package/src/posture/confidence.js +44 -10
  86. package/src/posture/corpus-enroll.js +9 -5
  87. package/src/posture/corpus-match.js +19 -0
  88. package/src/posture/csharp-analysis.js +62 -3
  89. package/src/posture/custom-rules.js +7 -5
  90. package/src/posture/cve-alert-daemon.js +6 -5
  91. package/src/posture/dep-add-guard.js +2 -1
  92. package/src/posture/deploy-platform.js +4 -1
  93. package/src/posture/deterministic.js +3 -2
  94. package/src/posture/drift.js +7 -1
  95. package/src/posture/epss.js +13 -1
  96. package/src/posture/evidence-bundle.js +276 -0
  97. package/src/posture/exploitability-probability.js +15 -2
  98. package/src/posture/falsification.js +23 -2
  99. package/src/posture/feature-flags.js +3 -2
  100. package/src/posture/findings-memory.js +3 -3
  101. package/src/posture/fix-history.js +5 -2
  102. package/src/posture/fix-metrics.js +5 -5
  103. package/src/posture/fix-plan.js +2 -1
  104. package/src/posture/fix-verify-loop.js +10 -1
  105. package/src/posture/grader-calibration.js +3 -4
  106. package/src/posture/iac-reachability.js +14 -8
  107. package/src/posture/integrity.js +25 -7
  108. package/src/posture/intent-context.js +2 -1
  109. package/src/posture/learning.js +4 -3
  110. package/src/posture/license-attributions.js +5 -7
  111. package/src/posture/license-graph.js +2 -1
  112. package/src/posture/license-policy.js +2 -1
  113. package/src/posture/model-rescan.js +69 -3
  114. package/src/posture/mttr.js +5 -0
  115. package/src/posture/network-policy-import.js +3 -2
  116. package/src/posture/poc-inprocess.js +27 -8
  117. package/src/posture/pqc-migration-plan.js +7 -5
  118. package/src/posture/pr-augment.js +8 -5
  119. package/src/posture/privacy-framework.js +262 -0
  120. package/src/posture/regression-test-gen.js +23 -8
  121. package/src/posture/reverse-blast-radius.js +5 -1
  122. package/src/posture/risk-dollars.js +20 -3
  123. package/src/posture/router.js +5 -4
  124. package/src/posture/ruleset-version.js +2 -2
  125. package/src/posture/runtime-correlation.js +2 -1
  126. package/src/posture/sbom-diff.js +12 -3
  127. package/src/posture/sca-policy.js +7 -4
  128. package/src/posture/scan-checkpoint.js +15 -0
  129. package/src/posture/secret-history.js +20 -11
  130. package/src/posture/security-trend.js +7 -1
  131. package/src/posture/stack-playbook.js +22 -1
  132. package/src/posture/state-dir.js +34 -0
  133. package/src/posture/telemetry-ingest.js +4 -3
  134. package/src/posture/threat-model-auto.js +4 -1
  135. package/src/posture/threat-model-grounding.js +13 -3
  136. package/src/posture/time-to-fix.js +3 -2
  137. package/src/posture/triage-memory.js +3 -2
  138. package/src/posture/validator-metrics.js +10 -3
  139. package/src/posture/verifier.js +32 -57
  140. package/src/posture/waf-ingest.js +6 -5
  141. package/src/posture/watch-mode.js +4 -3
  142. package/src/report/index.js +183 -14
  143. package/src/runScan.js +1 -1
  144. package/src/sast/_comment-strip.js +15 -4
  145. package/src/sast/_secret-entropy.js +1 -1
  146. package/src/sast/authz.js +6 -4
  147. package/src/sast/bench-shape/index.js +2 -7
  148. package/src/sast/claude-md-prompt-injection.js +14 -3
  149. package/src/sast/cloud-iam.js +60 -7
  150. package/src/sast/code-injection-multilang.js +29 -0
  151. package/src/sast/cpp-bench-extras.js +1 -1
  152. package/src/sast/csrf.js +7 -5
  153. package/src/sast/env-hygiene.js +5 -2
  154. package/src/sast/iac-terraform.js +25 -0
  155. package/src/sast/java-bench-extras.js +1 -1
  156. package/src/sast/java-constant-fold.js +5 -5
  157. package/src/sast/llm-owasp.js +4 -2
  158. package/src/sast/mcp-audit.js +7 -0
  159. package/src/sast/pipeline.js +8 -0
  160. package/src/sast/prompt-template.js +8 -6
  161. package/src/sast/prototype-pollution.js +6 -2
  162. package/src/sast/redos-nfa.js +6 -6
  163. package/src/sast/secret-concat.js +13 -2
  164. package/src/sast/ssrf-cloud-metadata.js +6 -3
  165. package/src/sast/xss-reflected-multilang.js +1 -1
  166. package/src/sast/xxe.js +1 -1
  167. package/src/sca/CLAUDE.md +3 -4
  168. package/src/sca/container.js +35 -3
  169. package/src/sca/dep-confusion.js +9 -1
  170. package/src/sca/sarif-ingest.js +0 -187
package/dist/435.index.js CHANGED
@@ -124,6 +124,32 @@ const PATTERNS = [
124
124
  [/rk_(?:live|test)_[A-Za-z0-9]{20,}/g, 'stripe-restricted-key'],
125
125
  [/SG\.[A-Za-z0-9_-]{22}\.[A-Za-z0-9_-]{43}/g, 'sendgrid-key'],
126
126
  [/AIza[0-9A-Za-z_-]{35}/g, 'google-api-key'],
127
+ // Stage 4 correctness audit (coverage breadth, AI security): this list
128
+ // only covered a small subset of what the scanner's OWN credential
129
+ // detector (engine.js's CREDENTIAL_PATTERNS, 40+ provider shapes) finds
130
+ // — a Shopify/Telegram/Twilio/Discord-webhook/Square/Google-OAuth/JDBC
131
+ // secret detected and reported by a scan reached explain_finding's
132
+ // output completely unredacted, because none of those shapes were in
133
+ // THIS separate, narrower list. Reusing the same regex bodies as
134
+ // engine.js's CREDENTIAL_PATTERNS for the shapes verified to leak
135
+ // (rather than importing engine.js itself, which would pull its entire
136
+ // multi-thousand-line module graph into the MCP server's dependency
137
+ // surface for a handful of consts).
138
+ [/ya29\.[0-9A-Za-z_-]{20,}/g, 'google-oauth-token'],
139
+ [/shp(?:at|ss|ca|pa)_[a-fA-F0-9]{32}/g, 'shopify-token'],
140
+ [/(?<![0-9])[0-9]{8,10}:AA[0-9A-Za-z_-]{33}(?![A-Za-z0-9_])/g, 'telegram-bot-token'],
141
+ [/twilio.{0,20}SK[0-9a-fA-F]{32}/gi, 'twilio-api-key'],
142
+ [/sq0atp-[0-9A-Za-z_-]{22}/g, 'square-access-token'],
143
+ [/sq0csp-[0-9A-Za-z_-]{43}/g, 'square-oauth-secret'],
144
+ [/access_token\$production\$[0-9a-z]{16}\$[0-9a-f]{32}/g, 'paypal-braintree-token'],
145
+ [/https:\/\/(?:discordapp|discord)\.com\/api\/webhooks\/[0-9]+\/[A-Za-z0-9_-]+/g, 'discord-webhook'],
146
+ [/https:\/\/hooks\.slack\.com\/services\/T[a-zA-Z0-9_]{8}\/B[a-zA-Z0-9_]{8,12}\/[a-zA-Z0-9_]{24}/g, 'slack-webhook'],
147
+ [/https:\/\/outlook\.office\.com\/webhook\/[A-Za-z0-9\-@]+\/IncomingWebhook\/[A-Za-z0-9-]+\/[A-Za-z0-9-]+/g, 'teams-webhook'],
148
+ [/https:\/\/(?:www\.)?hooks\.zapier\.com\/hooks\/catch\/[A-Za-z0-9]+\/[A-Za-z0-9]+\//g, 'zapier-webhook'],
149
+ // JDBC connection string carrying a password: only redact when password
150
+ // evidence is actually on the line (matches engine.js's own ctx gate),
151
+ // so a credential-free JDBC URL in docs isn't needlessly mangled.
152
+ [/jdbc:[a-z:]+:\/\/[A-Za-z0-9.\-_:;=/@?,&]*(?:@|password=|passwd=|pwd=)[A-Za-z0-9.\-_:;=/@?,&]*/gi, 'jdbc-connection-string'],
127
153
  // JWT — three dot-separated b64url segments starting with eyJ
128
154
  [/eyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/g, 'jwt'],
129
155
  // PEM-encoded private keys
@@ -171,6 +197,10 @@ function redactArgsBlob(s) {
171
197
  return redactString(s);
172
198
  }
173
199
 
200
+ // EXTERNAL MODULE: ./src/report/index.js + 2 modules
201
+ var report = __webpack_require__(3550);
202
+ // EXTERNAL MODULE: ./src/posture/state-dir.js
203
+ var state_dir = __webpack_require__(1174);
174
204
  ;// CONCATENATED MODULE: ./src/posture/agents-memory.js
175
205
  // AGENTS.md — writable continual-learning memory (harness-anatomy #2).
176
206
  //
@@ -203,6 +233,7 @@ function redactArgsBlob(s) {
203
233
 
204
234
 
205
235
 
236
+
206
237
  const MEMORY_FILE = '.agentic-security/AGENTS.md';
207
238
  const ARCHIVE_FILE = '.agentic-security/AGENTS.md.archive';
208
239
  const MAX_BYTES = 20 * 1024;
@@ -210,8 +241,8 @@ const MAX_ENTRY_BYTES = 2 * 1024;
210
241
  const ARCHIVE_MAX_BYTES = 200 * 1024;
211
242
  const HEADER = '# AGENTS.md\n\nAgent-authored continual-learning notes. Each entry: timestamp + agent name + one short paragraph. New entries appended at the bottom; oldest entries rotate to AGENTS.md.archive when this file exceeds 20 KB.\n\n';
212
243
 
213
- function _resolve(scanRoot) { return external_node_path_.join(scanRoot, MEMORY_FILE); }
214
- function _archivePath(scanRoot) { return external_node_path_.join(scanRoot, ARCHIVE_FILE); }
244
+ function _resolve(scanRoot) { return (0,state_dir/* statePath */.BQ)(scanRoot, 'AGENTS.md'); }
245
+ function _archivePath(scanRoot) { return (0,state_dir/* statePath */.BQ)(scanRoot, 'AGENTS.md.archive'); }
215
246
 
216
247
  function readAgentsMemory(scanRoot) {
217
248
  const fp = _resolve(scanRoot);
@@ -239,7 +270,8 @@ function appendAgentsMemory(scanRoot, { agent, body }) {
239
270
  const entry = `\n## ${ts} agent: ${agent}\n\n${snippet}\n`;
240
271
  try {
241
272
  const fp = _resolve(scanRoot);
242
- external_node_fs_.mkdirSync(external_node_path_.dirname(fp), { recursive: true });
273
+ if (!(0,state_dir.stateWritesEnabled)()) return;
274
+ external_node_fs_.mkdirSync(external_node_path_.dirname(fp), { recursive: true });
243
275
  if (!external_node_fs_.existsSync(fp)) external_node_fs_.writeFileSync(fp, HEADER);
244
276
  external_node_fs_.appendFileSync(fp, entry);
245
277
  _maybeRotate(scanRoot);
@@ -466,6 +498,7 @@ const cve_lookup_internals = { CACHE_DIR, CVE_RE, _stalenessTier };
466
498
 
467
499
 
468
500
 
501
+
469
502
  // Lazy-loaded: these transitively pull in npm packages (@babel/core and
470
503
  // friends) that aren't available in the plugin-cache install path
471
504
  // (no node_modules). Deferring keeps the MCP server bootable everywhere;
@@ -596,12 +629,17 @@ function _validateScratchpadPath(relPath) {
596
629
  return { ok: true, agent, session, fileParts };
597
630
  }
598
631
 
632
+ // Routes through the same lstat+realpath confinement every other write/
633
+ // path-taking tool uses (OWASP MCP05) — a lexical prefix/charset check
634
+ // alone doesn't stop a pre-planted symlink at any path component from
635
+ // relocating the write/read outside the session root. Throws on escape;
636
+ // callers must catch (see append_scratchpad / read_scratchpad).
599
637
  function _scratchpadAbs(sessionRoot, relPath) {
600
- return external_node_path_.resolve(sessionRoot, relPath.replace(/\\/g, '/'));
638
+ return _confine(sessionRoot, relPath.replace(/\\/g, '/'), 'scratchpad path');
601
639
  }
602
640
 
603
641
  function _scratchpadTotalBytes(sessionRoot) {
604
- const base = external_node_path_.join(sessionRoot, '.agentic-security', 'agent-scratchpad');
642
+ const base = (0,state_dir/* statePath */.BQ)(sessionRoot, 'agent-scratchpad');
605
643
  if (!external_node_fs_.existsSync(base)) return 0;
606
644
  let total = 0;
607
645
  const walk = (dir) => {
@@ -666,8 +704,8 @@ function _confine(sessionRoot, candidate, label) {
666
704
  }
667
705
 
668
706
  function _readLastScanVerified(sessionRoot, { allowUnsigned = false } = {}) {
669
- const stateDir = external_node_path_.join(sessionRoot, '.agentic-security');
670
- const scanFile = external_node_path_.join(stateDir, 'last-scan.json');
707
+ const stateDirPath = (0,state_dir/* stateDir */.Pn)(sessionRoot);
708
+ const scanFile = external_node_path_.join(stateDirPath, 'last-scan.json');
671
709
  const sigFile = scanFile + '.sig';
672
710
  if (!external_node_fs_.existsSync(scanFile)) return { scan: null, status: 'missing' };
673
711
  const body = external_node_fs_.readFileSync(scanFile, 'utf8');
@@ -781,12 +819,22 @@ const scan_diff = {
781
819
  const wantSet = new Set(Object.keys(fileContents));
782
820
  const sevRank = { info: 0, low: 1, medium: 2, high: 3, critical: 4 };
783
821
  const min = sevRank[severity] ?? 0;
784
- const findings = (result.scan.findings || [])
822
+ // Stage 6 correctness audit: this only ever read result.scan.findings
823
+ // (the SAST channel) — scan.secrets and scan.logicVulns are separate
824
+ // arrays on the raw runScan() result (report/index.js's normalizeFindings
825
+ // is what merges all four channels, and that merge hasn't run yet here).
826
+ // A file containing a bare hardcoded credential reported findingCount: 0
827
+ // through a tool whose own description promises "Use BEFORE writing a
828
+ // Write/Edit to disk so the agent can self-correct". Also reused
829
+ // _remediationOf so a fix-string detector (the majority of engine.js's
830
+ // own, ~127 call sites) doesn't silently report an empty `description`
831
+ // the way reading only `.remediation` did.
832
+ const findings = [...(result.scan.findings || []), ...(result.scan.secrets || []), ...(result.scan.logicVulns || [])]
785
833
  .filter(f => wantSet.has(String(f.file || '').replace(/\\/g, '/')) && (sevRank[f.severity] ?? 0) >= min)
786
834
  .map(f => redactFinding({
787
835
  id: f.id, severity: f.severity, file: f.file, line: f.line,
788
836
  title: f.title || f.vuln, cwe: f.cwe,
789
- description: f.description, remediation: f.remediation,
837
+ description: f.description, remediation: (0,report/* _remediationOf */.uV)(f),
790
838
  }));
791
839
  // Harness-anatomy #1: offload when the result exceeds OFFLOAD_THRESHOLD.
792
840
  // The agent gets a head+tail preview plus a path it can page through;
@@ -949,10 +997,40 @@ const apply_fix = {
949
997
  additionalProperties: { type: 'string', maxLength: 500_000 },
950
998
  minProperties: 1, maxProperties: 8,
951
999
  },
1000
+ // Stage 6 correctness audit: same gap and same fix as verify_fix — the
1001
+ // honesty gate is reachable but was never wired to any real caller.
1002
+ // Here it's stronger than advisory: the inline re-verify below already
1003
+ // gates the WRITE on `verdict.ok`, and verifyFixCore's own `ok`
1004
+ // formula already folds in `honesty.ok` when fixMeta is supplied — so
1005
+ // passing it through here makes a dishonest fixMeta (hand-wave
1006
+ // residual, uncited false-positive verdict) block the write itself,
1007
+ // not just report a verdict.
1008
+ fixMeta: {
1009
+ type: 'object',
1010
+ additionalProperties: false,
1011
+ properties: {
1012
+ residual: { type: 'string', maxLength: 2000 },
1013
+ verdict: { type: 'string', maxLength: 64 },
1014
+ evidence: { type: 'array', items: { type: 'string', maxLength: 500 }, maxItems: 20 },
1015
+ signals: {
1016
+ type: 'object',
1017
+ additionalProperties: false,
1018
+ properties: {
1019
+ sinkSignatureChanged: { type: 'boolean' },
1020
+ allCallersRouted: { type: 'boolean' },
1021
+ testDiscriminates: { type: 'boolean' },
1022
+ rateLimitOnly: { type: 'boolean' },
1023
+ docsOnly: { type: 'boolean' },
1024
+ logOnlyNoReject: { type: 'boolean' },
1025
+ partialSanitization: { type: 'boolean' },
1026
+ },
1027
+ },
1028
+ },
1029
+ },
952
1030
  },
953
1031
  required: ['finding_id', 'confirm'],
954
1032
  },
955
- async handler({ finding_id, confirm, dry_run = false, patch = null }, ctx) {
1033
+ async handler({ finding_id, confirm, dry_run = false, patch = null, fixMeta = null }, ctx) {
956
1034
  if (confirm !== true) {
957
1035
  return { _meta: META, applied: false, reason: 'apply_fix requires confirm: true.' };
958
1036
  }
@@ -1008,6 +1086,7 @@ const apply_fix = {
1008
1086
  scanRoot: ctx.sessionRoot,
1009
1087
  originalFindingStableId: f.stableId,
1010
1088
  files: _files,
1089
+ fixMeta,
1011
1090
  });
1012
1091
  }
1013
1092
  } catch (e) {
@@ -1017,7 +1096,7 @@ const apply_fix = {
1017
1096
  return {
1018
1097
  _meta: META, applied: false,
1019
1098
  reason: `patch rejected by verifier: ${verdict.summary || verdict.rescan?.reason || 'did not verify'}`,
1020
- verify: { rescan: verdict.rescan, lint: { runner: verdict.lint?.runner, ok: verdict.lint?.ok } },
1099
+ verify: { rescan: verdict.rescan, lint: { runner: verdict.lint?.runner, ok: verdict.lint?.ok }, honesty: verdict.honesty || null },
1021
1100
  };
1022
1101
  }
1023
1102
  if (dry_run) {
@@ -1029,7 +1108,7 @@ const apply_fix = {
1029
1108
  const originalContent = external_node_fs_.existsSync(v.abs) ? await promises_.readFile(v.abs, 'utf8') : '';
1030
1109
  const entry = await (0,fix_history/* applyFix */.oM)({
1031
1110
  scanRoot: ctx.sessionRoot, file: rel, originalContent, newContent: v.content,
1032
- findingId: f.id, stableId: f.stableId, ruleId: f.rule || null, vuln: f.vuln || f.title || null,
1111
+ findingId: f.id, stableId: f.stableId, ruleId: f.ruleId || f.cwe || f.family || null, vuln: f.vuln || f.title || null,
1033
1112
  });
1034
1113
  written.push({ file: rel, historyId: entry.id, backupPath: entry.backupPath });
1035
1114
  }
@@ -1087,7 +1166,7 @@ const apply_fix = {
1087
1166
  newContent: f.fix.replacement,
1088
1167
  findingId: f.id,
1089
1168
  stableId: f.stableId || null, // premortem 4R-8
1090
- ruleId: f.rule || null,
1169
+ ruleId: f.ruleId || f.cwe || f.family || null,
1091
1170
  vuln: f.vuln || f.title || null,
1092
1171
  });
1093
1172
  } catch (e) {
@@ -1124,7 +1203,7 @@ const apply_fix = {
1124
1203
  // proceed with apply_fix.
1125
1204
  const verify_fix = {
1126
1205
  name: 'verify_fix',
1127
- description: 'Verify a proposed patch before applying. Re-scans the patched files in memory and runs the project linter. Returns { ok, rescan, lint, summary }. No filesystem writes.',
1206
+ description: 'Verify a proposed patch before applying. Re-scans the patched files in memory, runs the project linter, runs the project test suite, checks fix honesty (FULL/MITIGATION/WORKAROUND) when fixMeta is supplied, and re-runs the PoC when one exists. Returns { ok, rescan, lint, tests, honesty, poc, summary }. Does not write to the target project’s own files, but DOES append one record per attempt to .agentic-security/fix-metrics.jsonl for the measured fix-loop.',
1128
1207
  inputSchema: {
1129
1208
  type: 'object',
1130
1209
  additionalProperties: false,
@@ -1136,10 +1215,41 @@ const verify_fix = {
1136
1215
  minProperties: 1,
1137
1216
  maxProperties: 8,
1138
1217
  },
1218
+ // Stage 6 correctness audit: posture/fix-honesty-gate.js's deterministic
1219
+ // honesty checks (vague-assurance residual prose, unbacked false-
1220
+ // positive verdicts, tier/residual consistency) were fully built and
1221
+ // fix-verify.js already consulted them when given a `fixMeta` — but
1222
+ // this schema never had a `fixMeta` property, so no call through the
1223
+ // MCP surface could ever supply one. The gate can only run against
1224
+ // claims the AGENT self-reports (residual risk, verdict, evidence,
1225
+ // completeness signals) — nothing here is server-computable — so
1226
+ // fixing this meant exposing the property, not inventing a lookup.
1227
+ fixMeta: {
1228
+ type: 'object',
1229
+ additionalProperties: false,
1230
+ properties: {
1231
+ residual: { type: 'string', maxLength: 2000 },
1232
+ verdict: { type: 'string', maxLength: 64 },
1233
+ evidence: { type: 'array', items: { type: 'string', maxLength: 500 }, maxItems: 20 },
1234
+ signals: {
1235
+ type: 'object',
1236
+ additionalProperties: false,
1237
+ properties: {
1238
+ sinkSignatureChanged: { type: 'boolean' },
1239
+ allCallersRouted: { type: 'boolean' },
1240
+ testDiscriminates: { type: 'boolean' },
1241
+ rateLimitOnly: { type: 'boolean' },
1242
+ docsOnly: { type: 'boolean' },
1243
+ logOnlyNoReject: { type: 'boolean' },
1244
+ partialSanitization: { type: 'boolean' },
1245
+ },
1246
+ },
1247
+ },
1248
+ },
1139
1249
  },
1140
1250
  required: ['stable_id', 'files'],
1141
1251
  },
1142
- async handler({ stable_id, files }, ctx) {
1252
+ async handler({ stable_id, files, fixMeta }, ctx) {
1143
1253
  // Confine every file path before passing to the verifier.
1144
1254
  const confined = {};
1145
1255
  for (const [relPath, content] of Object.entries(files || {})) {
@@ -1151,17 +1261,48 @@ const verify_fix = {
1151
1261
  confined[relPath] = String(content);
1152
1262
  }
1153
1263
  try {
1264
+ // The PoC-re-check leg (verifyFixCore's `pocLeg`) needs a `poc` param
1265
+ // to do anything — until now nothing supplied one, so it always
1266
+ // reported {status:'not-requested'} through this surface (see
1267
+ // posture/CLAUDE.md's disclosure). Rather than widening inputSchema
1268
+ // to make the CALLER pass PoC data back, look it up server-side: the
1269
+ // scan pipeline already attaches an HTTP-shaped f.poc to matching
1270
+ // findings by default (engine.js's annotatePocs), and last-scan.json
1271
+ // already carries it under the same stableId this handler receives.
1272
+ // Best-effort: a missing/unsigned/tampered scan just means no PoC is
1273
+ // available to re-check, not a verify_fix failure — the rescan/lint/
1274
+ // tests legs below are independent of this and still apply.
1275
+ let poc = null;
1276
+ try {
1277
+ const { scan: lastScan } = _readLastScanVerified(ctx.sessionRoot, { allowUnsigned: true });
1278
+ const orig = lastScan && (lastScan.findings || []).find(f => f.stableId === stable_id);
1279
+ if (orig && orig.poc && orig.poc.code) poc = { ...orig.poc, finding: orig };
1280
+ } catch { /* best-effort lookup; poc stays null */ }
1281
+
1154
1282
  const verifyFixCore = await getVerifyFixCore();
1155
1283
  const r = await verifyFixCore({
1156
1284
  scanRoot: ctx.sessionRoot,
1157
1285
  originalFindingStableId: stable_id,
1158
1286
  files: confined,
1287
+ poc,
1288
+ fixMeta,
1159
1289
  });
1160
1290
  return {
1161
1291
  _meta: META,
1162
1292
  ok: r.ok,
1163
1293
  rescan: { ok: r.rescan.ok, reason: r.rescan.reason, introduced: r.rescan.introduced || [] },
1164
1294
  lint: { runner: r.lint.runner, ok: r.lint.ok, skipped: r.lint.skipped || false, output: redactString(r.lint.output || '').slice(0, 1500) },
1295
+ // verifyFix computes five legs, not two — tests/honesty/poc were
1296
+ // being silently dropped here, leaving an agent with no structured
1297
+ // way to see WHY verification failed when the failure was in one
1298
+ // of those three (only the free-text summary carried it).
1299
+ // test-runner.js's runProjectTests never returns raw stdout/stderr,
1300
+ // so no redaction is needed there; honesty.violations are static,
1301
+ // code-generated strings; poc.reason is redacted defensively since
1302
+ // it can echo proof-harness detail derived from scanned source.
1303
+ tests: r.tests,
1304
+ honesty: r.honesty,
1305
+ poc: r.poc ? { ...r.poc, reason: r.poc.reason ? redactString(r.poc.reason) : r.poc.reason } : r.poc,
1165
1306
  summary: r.summary,
1166
1307
  };
1167
1308
  } catch (e) {
@@ -1245,7 +1386,13 @@ const synthesize_fix = {
1245
1386
  regression_test: f.regression_test || null,
1246
1387
  remediation: typeof fix.description === 'string' ? fix.description : (typeof fix === 'string' ? fix : null),
1247
1388
  patchBounds: { touchedFiles, locDelta, oversized },
1248
- recommendsFixPlan: oversized && !hasReplacement && !autofix,
1389
+ // oversized can only be true when hasReplacement is true (locDelta is
1390
+ // only computed in that branch, and touchedFiles never varies) — a
1391
+ // `!hasReplacement` conjunct here was a structural contradiction that
1392
+ // made this permanently false. The correct signal: the stored
1393
+ // replacement itself is too big to trust auto-applying, and there's
1394
+ // no safer deterministic alternative.
1395
+ recommendsFixPlan: oversized && !autofix,
1249
1396
  };
1250
1397
  },
1251
1398
  };
@@ -1377,7 +1524,9 @@ const append_scratchpad = {
1377
1524
  async handler({ path: relPath, content }, ctx) {
1378
1525
  const v = _validateScratchpadPath(relPath);
1379
1526
  if (!v.ok) return { _meta: META, ok: false, reason: v.reason };
1380
- const abs = _scratchpadAbs(ctx.sessionRoot, relPath);
1527
+ let abs;
1528
+ try { abs = _scratchpadAbs(ctx.sessionRoot, relPath); }
1529
+ catch (e) { return { _meta: META, ok: false, reason: `path-escape refused: ${e.message}` }; }
1381
1530
  const total = _scratchpadTotalBytes(ctx.sessionRoot);
1382
1531
  if (total + content.length > SCRATCHPAD_MAX_TOTAL_BYTES) {
1383
1532
  return {
@@ -1423,7 +1572,9 @@ const read_scratchpad = {
1423
1572
  async handler({ path: relPath, offset, limit }, ctx) {
1424
1573
  const v = _validateScratchpadPath(relPath);
1425
1574
  if (!v.ok) return { _meta: META, ok: false, reason: v.reason };
1426
- const abs = _scratchpadAbs(ctx.sessionRoot, relPath);
1575
+ let abs;
1576
+ try { abs = _scratchpadAbs(ctx.sessionRoot, relPath); }
1577
+ catch (e) { return { _meta: META, ok: false, reason: `path-escape refused: ${e.message}` }; }
1427
1578
  if (!external_node_fs_.existsSync(abs)) return { _meta: META, ok: false, reason: 'not-found' };
1428
1579
  let stat;
1429
1580
  try { stat = external_node_fs_.statSync(abs); } catch (e) { return { _meta: META, ok: false, reason: `stat-failed: ${e.message}` }; }
@@ -1457,6 +1608,7 @@ const read_scratchpad = {
1457
1608
 
1458
1609
 
1459
1610
 
1611
+
1460
1612
  const append_agents_memory = {
1461
1613
  name: 'append_agents_memory',
1462
1614
  description: 'Append a short narrative entry to AGENTS.md — agent-authored continual-learning notes. Use at session end to record "what worked / what didn\'t / what I\'d try differently next time" so the next agent can pick up the lesson. Bounded: 2 KB per entry, 20 KB total before rotation to AGENTS.md.archive. Use sparingly — narrative, not structured data.',
@@ -1518,7 +1670,20 @@ const query_triage_memory = {
1518
1670
  },
1519
1671
  async handler({ query }, ctx) {
1520
1672
  const { queryMemory } = await Promise.resolve(/* import() */).then(__webpack_require__.bind(__webpack_require__, 1905));
1521
- const results = queryMemory(ctx.sessionRoot, query || '');
1673
+ const raw = queryMemory(ctx.sessionRoot, query || '');
1674
+ // Stage 6 correctness audit: this returned queryMemory's output
1675
+ // verbatim, with no redaction pass — every other tool that echoes
1676
+ // scanned-source-derived text redacts it (mcp/CLAUDE.md's "Adding a new
1677
+ // tool" step 3). Round-trip through redactString the same way
1678
+ // redactFinding already does for its own opaque `.trace` field: results
1679
+ // here mix shapes (a triage decision's free-text `reason`, a finding's
1680
+ // `vuln`/`family`/file path), so scrubbing the whole serialized
1681
+ // structure catches secret-shaped substrings regardless of which field
1682
+ // they landed in, rather than hardcoding a field allowlist that could
1683
+ // miss one.
1684
+ let results;
1685
+ try { results = JSON.parse(redactString(JSON.stringify(raw))); }
1686
+ catch { results = raw; }
1522
1687
  return {
1523
1688
  _meta: META,
1524
1689
  count: results.length,
@@ -1546,7 +1711,16 @@ const query_findings_memory = {
1546
1711
  },
1547
1712
  async handler({ query }, ctx) {
1548
1713
  const { queryFindingsMemory } = await __webpack_require__.e(/* import() */ 839).then(__webpack_require__.bind(__webpack_require__, 3839));
1549
- return { _meta: META, ...queryFindingsMemory(ctx.sessionRoot, query || '') };
1714
+ const raw = queryFindingsMemory(ctx.sessionRoot, query || '');
1715
+ // Stage 6 correctness audit — same redaction gap and same fix as
1716
+ // query_triage_memory just above: this mixes four differently-shaped
1717
+ // result kinds (finding / triage / history / AGENTS.md text), so a
1718
+ // whole-structure redactString round-trip is applied rather than a
1719
+ // per-field allowlist that could miss one of the four shapes.
1720
+ let body;
1721
+ try { body = JSON.parse(redactString(JSON.stringify(raw))); }
1722
+ catch { body = raw; }
1723
+ return { _meta: META, ...body };
1550
1724
  },
1551
1725
  };
1552
1726
 
@@ -1754,6 +1928,7 @@ function validate(schema, value, path = 'arguments') {
1754
1928
 
1755
1929
 
1756
1930
 
1931
+
1757
1932
  const MAX_ARG_BYTES = 1024;
1758
1933
  const GENESIS = 'GENESIS';
1759
1934
  const REMOTE_TIMEOUT_MS = 1500;
@@ -1814,7 +1989,7 @@ function auditCall({ sessionRoot, tool, args, outcome, reason }) {
1814
1989
  let hasMarker = false;
1815
1990
  for (const m of MARKERS) { try { if (external_node_fs_.existsSync(external_node_path_.join(sessionRoot, m))) { hasMarker = true; break; } } catch {} }
1816
1991
  if (!hasMarker) return;
1817
- const dir = external_node_path_.join(sessionRoot, '.agentic-security');
1992
+ const dir = (0,state_dir/* stateDir */.Pn)(sessionRoot);
1818
1993
  external_node_fs_.mkdirSync(dir, { recursive: true });
1819
1994
  const logFile = external_node_path_.join(dir, 'mcp-audit.log');
1820
1995
  const entry = {
package/dist/444.index.js CHANGED
@@ -43,17 +43,26 @@ function scanHistoryDiff(diffText, commit, detectFn) {
43
43
  if (!added.trim()) return [];
44
44
  let findings = [];
45
45
  try { findings = detectFn(`git-history@${commit}`, added) || []; } catch { return []; }
46
- return findings.map((f) => ({
47
- ...f,
48
- id: `secret-history:${commit}:${f.id || f.vuln || 'secret'}`,
49
- file: `git-history@${commit}`,
50
- line: 0,
51
- commit,
52
- _historical: true,
53
- vuln: `${f.vuln || 'Hardcoded Secret'} (in git history)`,
54
- description: `${f.description || 'A credential was committed.'} Found in commit ${commit}; even if removed from HEAD it remains recoverable from git and must be rotated.`,
55
- remediation: 'Rotate the credential now, then purge it from history (git filter-repo / BFG) and move it to a secrets manager. Removing it from HEAD alone is insufficient.',
56
- }));
46
+ return findings.map((f) => {
47
+ const remediation = 'Rotate the credential now, then purge it from history (git filter-repo / BFG) and move it to a secrets manager. Removing it from HEAD alone is insufficient.';
48
+ return {
49
+ ...f,
50
+ id: `secret-history:${commit}:${f.id || f.vuln || 'secret'}`,
51
+ file: `git-history@${commit}`,
52
+ line: 0,
53
+ commit,
54
+ _historical: true,
55
+ vuln: `${f.vuln || 'Hardcoded Secret'} (in git history)`,
56
+ description: `${f.description || 'A credential was committed.'} Found in commit ${commit}; even if removed from HEAD it remains recoverable from git and must be rotated.`,
57
+ remediation,
58
+ // report/index.js's _remediationOf checks `.fix` before `.remediation`
59
+ // — the underlying detector already set `.fix` to a generic "remove
60
+ // the line" string, which would otherwise silently shadow this
61
+ // history-specific instruction ("removing it from HEAD alone is
62
+ // insufficient") in every report format.
63
+ fix: remediation,
64
+ };
65
+ });
57
66
  }
58
67
 
59
68
  /**
package/dist/449.index.js CHANGED
@@ -7,10 +7,12 @@ export const modules = {
7
7
 
8
8
  /* harmony export */ __webpack_require__.d(__webpack_exports__, {
9
9
  /* harmony export */ buildBaselineMap: () => (/* binding */ buildBaselineMap),
10
+ /* harmony export */ computeMTTR: () => (/* binding */ computeMTTR),
11
+ /* harmony export */ fingerprintFinding: () => (/* binding */ fingerprintFinding),
10
12
  /* harmony export */ renderSlaSummary: () => (/* binding */ renderSlaSummary),
11
13
  /* harmony export */ stampFindingTimestamps: () => (/* binding */ stampFindingTimestamps)
12
14
  /* harmony export */ });
13
- /* unused harmony exports findingsExceedingSLA, computeMTTR */
15
+ /* unused harmony export findingsExceedingSLA */
14
16
  /* harmony import */ var node_crypto__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(7598);
15
17
  // 0.8.0 Feat-11: MTTR / finding-age tracking — per-finding firstSeenAt/lastSeenAt with SLA breach detection.
16
18
  //
@@ -24,6 +26,11 @@ export const modules = {
24
26
 
25
27
 
26
28
  // Stable fingerprint for cross-scan finding identity. Mirrors the dedupe key.
29
+ // Exported so a caller can compute the "removed since baseline" (i.e. fixed)
30
+ // set that computeMTTR needs, using the exact same identity function
31
+ // buildBaselineMap uses internally — a caller-side reimplementation would
32
+ // risk drifting from this one and silently under/over-counting fixes.
33
+ function fingerprintFinding(f) { return _fingerprint(f); }
27
34
  function _fingerprint(f) {
28
35
  const file = (f.file || '').split(' -> ').pop();
29
36
  const line = f.line || f.source?.line || f.sink?.line || 0;
package/dist/513.index.js CHANGED
@@ -12,6 +12,7 @@ export const modules = {
12
12
  /* unused harmony exports badgeFromScanRoot, renderSvg, _internal */
13
13
  /* harmony import */ var node_fs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(3024);
14
14
  /* harmony import */ var node_path__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(6760);
15
+ /* harmony import */ var _posture_state_dir_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(1174);
15
16
  // Live SVG badge generator (v0.72).
16
17
  //
17
18
  // Every repo can drop a badge in its README pulling from the latest scan:
@@ -42,6 +43,7 @@ export const modules = {
42
43
 
43
44
 
44
45
 
46
+
45
47
  const COLORS = {
46
48
  critical: '#e05d44', // red
47
49
  high: '#fe7d37', // orange
@@ -56,7 +58,7 @@ const SEVERITIES = ['critical', 'high', 'medium', 'low', 'info'];
56
58
 
57
59
  function _readLastScan(scanRoot) {
58
60
  if (!scanRoot) return null;
59
- const fp = node_path__WEBPACK_IMPORTED_MODULE_1__.join(scanRoot, '.agentic-security', 'last-scan.json');
61
+ const fp = (0,_posture_state_dir_js__WEBPACK_IMPORTED_MODULE_2__/* .statePath */ .BQ)(scanRoot, 'last-scan.json');
60
62
  if (!node_fs__WEBPACK_IMPORTED_MODULE_0__.existsSync(fp)) return null;
61
63
  try { return JSON.parse(node_fs__WEBPACK_IMPORTED_MODULE_0__.readFileSync(fp, 'utf8')); }
62
64
  catch { return null; }
@@ -214,6 +216,7 @@ const _internal = { COLORS, _ageString, _readLastScan };
214
216
  /* harmony import */ var node_fs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(3024);
215
217
  /* harmony import */ var node_path__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(6760);
216
218
  /* harmony import */ var _badge_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(3117);
219
+ /* harmony import */ var _posture_state_dir_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(1174);
217
220
  // Leaderboard backend (v0.72).
218
221
  //
219
222
  // Generates the data shape that powers the future public leaderboard at
@@ -243,6 +246,7 @@ const _internal = { COLORS, _ageString, _readLastScan };
243
246
 
244
247
 
245
248
 
249
+
246
250
  // Grade thresholds. Critical findings dominate; high/medium contribute
247
251
  // secondarily. These numbers are heuristic — calibrate against the
248
252
  // public leaderboard corpus once data lands.
@@ -306,7 +310,7 @@ function _deltaTrend(history) {
306
310
  */
307
311
  function leaderboardRowFor({ scanRoot, repo, badgeBase = 'https://agentic-security.dev/badge' } = {}) {
308
312
  if (!repo) throw new Error('leaderboardRowFor: repo slug is required');
309
- const lastScanPath = node_path__WEBPACK_IMPORTED_MODULE_1__.join(scanRoot || '.', '.agentic-security', 'last-scan.json');
313
+ const lastScanPath = (0,_posture_state_dir_js__WEBPACK_IMPORTED_MODULE_3__/* .statePath */ .BQ)(scanRoot || '.', 'last-scan.json');
310
314
  let scan = null;
311
315
  try { scan = JSON.parse(node_fs__WEBPACK_IMPORTED_MODULE_0__.readFileSync(lastScanPath, 'utf8')); } catch {}
312
316
  const summary = (0,_badge_js__WEBPACK_IMPORTED_MODULE_2__/* .summarizeForBadge */ .iI)(scan);
@@ -314,7 +318,7 @@ function leaderboardRowFor({ scanRoot, repo, badgeBase = 'https://agentic-securi
314
318
  const topCwe = _topCwe(scan);
315
319
 
316
320
  // Optional scan history for the trend signal.
317
- const historyPath = node_path__WEBPACK_IMPORTED_MODULE_1__.join(scanRoot || '.', '.agentic-security', 'scan-history.jsonl');
321
+ const historyPath = (0,_posture_state_dir_js__WEBPACK_IMPORTED_MODULE_3__/* .statePath */ .BQ)(scanRoot || '.', 'scan-history.jsonl');
318
322
  let history = [];
319
323
  if (node_fs__WEBPACK_IMPORTED_MODULE_0__.existsSync(historyPath)) {
320
324
  try {
package/dist/526.index.js CHANGED
@@ -52,7 +52,6 @@ export const modules = {
52
52
 
53
53
 
54
54
 
55
- const STATE_DIR = '.agentic-security';
56
55
  const LOG_FILE = 'fix-metrics.jsonl';
57
56
 
58
57
  // Below this many samples a percentile is an artifact of the sample, not a
@@ -65,7 +64,7 @@ const RELIABLE_N = 10;
65
64
  const FIX_STAGES = Object.freeze(['rescan', 'lint', 'tests', 'honesty', 'poc']);
66
65
 
67
66
  function _logPath(scanRoot) {
68
- return node_path__WEBPACK_IMPORTED_MODULE_1__.join(scanRoot, STATE_DIR, LOG_FILE);
67
+ return (0,_state_dir_js__WEBPACK_IMPORTED_MODULE_2__/* .statePath */ .BQ)(scanRoot, LOG_FILE);
69
68
  }
70
69
 
71
70
  /**
@@ -77,9 +76,10 @@ function _logPath(scanRoot) {
77
76
  function recordFixAttempt(scanRoot, record) {
78
77
  if (!scanRoot || !record || typeof record !== 'object') return false;
79
78
  try {
80
- const dir = node_path__WEBPACK_IMPORTED_MODULE_1__.join(scanRoot, STATE_DIR);
79
+ const dir = (0,_state_dir_js__WEBPACK_IMPORTED_MODULE_2__/* .stateDir */ .Pn)(scanRoot);
81
80
  if (!(0,_state_dir_js__WEBPACK_IMPORTED_MODULE_2__.isSafeStateDir)(dir)) return false;
82
- node_fs__WEBPACK_IMPORTED_MODULE_0__.mkdirSync(dir, { recursive: true });
81
+ if (!(0,_state_dir_js__WEBPACK_IMPORTED_MODULE_2__.stateWritesEnabled)()) return false;
82
+ node_fs__WEBPACK_IMPORTED_MODULE_0__.mkdirSync(dir, { recursive: true });
83
83
  // One writeSync of one newline-terminated line: a concurrent reader sees
84
84
  // whole records or nothing, and a torn tail is dropped on read.
85
85
  node_fs__WEBPACK_IMPORTED_MODULE_0__.appendFileSync(_logPath(scanRoot), JSON.stringify(record) + '\n', 'utf8');
@@ -234,8 +234,8 @@ var external_node_child_process_ = __webpack_require__(1421);
234
234
  var external_node_fs_ = __webpack_require__(3024);
235
235
  // EXTERNAL MODULE: external "node:path"
236
236
  var external_node_path_ = __webpack_require__(6760);
237
- // EXTERNAL MODULE: ./src/engine.js + 501 modules
238
- var engine = __webpack_require__(5695);
237
+ // EXTERNAL MODULE: ./src/engine.js + 196 modules
238
+ var engine = __webpack_require__(2739);
239
239
  ;// CONCATENATED MODULE: ./src/posture/fix-honesty-gate.js
240
240
  // Deterministic honesty gates on fix / finding output (#7).
241
241
  //
package/dist/637.index.js CHANGED
@@ -11,7 +11,7 @@ export const modules = {
11
11
  /* harmony export */ renderPrDeltaText: () => (/* binding */ renderPrDeltaText)
12
12
  /* harmony export */ });
13
13
  /* harmony import */ var node_child_process__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1421);
14
- /* harmony import */ var _engine_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(5695);
14
+ /* harmony import */ var _engine_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(2739);
15
15
  // Shadowscan / security-DELTA on PR (v0.72).
16
16
  //
17
17
  // Most SAST PR-comment integrations show absolute counts — "12 findings
package/dist/675.index.js CHANGED
@@ -11,6 +11,7 @@ export const modules = {
11
11
  /* unused harmony exports readDeps, _queryOsvForDep, loadState, persistState, loadConfig, formatPayload, _internals */
12
12
  /* harmony import */ var node_fs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(3024);
13
13
  /* harmony import */ var node_path__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(6760);
14
+ /* harmony import */ var _state_dir_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(1174);
14
15
  // Continuous CVE-watch daemon.
15
16
  //
16
17
  // Polls OSV for the project's dependency tree on a schedule (or one-shot),
@@ -40,9 +41,9 @@ export const modules = {
40
41
 
41
42
 
42
43
 
44
+
43
45
  const OSV_API = 'https://api.osv.dev/v1/query';
44
- const CFG_PATH = '.agentic-security/cve-alerts.json';
45
- const STATE_PATH = '.agentic-security/cve-alerts-state.json';
46
+
46
47
 
47
48
  // Multi-ecosystem dep extraction. Each entry: { manifest filename → ecosystem }
48
49
  const ECOSYSTEM_BY_MANIFEST = {
@@ -280,7 +281,7 @@ async function _queryOsvForDep({ name, ecosystem }, { fetchImpl = globalThis.fet
280
281
  // ─── State persistence ───────────────────────────────────────────────────
281
282
 
282
283
  function loadState(scanRoot) {
283
- const fp = node_path__WEBPACK_IMPORTED_MODULE_1__.join(scanRoot, STATE_PATH);
284
+ const fp = (0,_state_dir_js__WEBPACK_IMPORTED_MODULE_2__/* .statePath */ .BQ)(scanRoot, 'cve-alerts-state.json');
284
285
  if (!node_fs__WEBPACK_IMPORTED_MODULE_0__.existsSync(fp)) return { known: new Set(), lastRun: null };
285
286
  try {
286
287
  const j = JSON.parse(node_fs__WEBPACK_IMPORTED_MODULE_0__.readFileSync(fp, 'utf8'));
@@ -289,7 +290,8 @@ function loadState(scanRoot) {
289
290
  }
290
291
 
291
292
  function persistState(scanRoot, state) {
292
- const fp = node_path__WEBPACK_IMPORTED_MODULE_1__.join(scanRoot, STATE_PATH);
293
+ const fp = (0,_state_dir_js__WEBPACK_IMPORTED_MODULE_2__/* .statePath */ .BQ)(scanRoot, 'cve-alerts-state.json');
294
+ if (!(0,_state_dir_js__WEBPACK_IMPORTED_MODULE_2__.stateWritesEnabled)()) return;
293
295
  node_fs__WEBPACK_IMPORTED_MODULE_0__.mkdirSync(node_path__WEBPACK_IMPORTED_MODULE_1__.dirname(fp), { recursive: true });
294
296
  node_fs__WEBPACK_IMPORTED_MODULE_0__.writeFileSync(fp, JSON.stringify({
295
297
  known: [...state.known].sort(),
@@ -298,7 +300,7 @@ function persistState(scanRoot, state) {
298
300
  }
299
301
 
300
302
  function loadConfig(scanRoot) {
301
- const fp = node_path__WEBPACK_IMPORTED_MODULE_1__.join(scanRoot, CFG_PATH);
303
+ const fp = (0,_state_dir_js__WEBPACK_IMPORTED_MODULE_2__/* .statePath */ .BQ)(scanRoot, 'cve-alerts.json');
302
304
  if (!node_fs__WEBPACK_IMPORTED_MODULE_0__.existsSync(fp)) return null;
303
305
  try { return JSON.parse(node_fs__WEBPACK_IMPORTED_MODULE_0__.readFileSync(fp, 'utf8')); } catch { return null; }
304
306
  }