@clear-capabilities/agentic-security-scanner 0.74.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 (331) hide show
  1. package/CHANGELOG.md +1580 -0
  2. package/bin/.agentic-security/findings.json +1577 -0
  3. package/bin/.agentic-security/last-scan.json +1577 -0
  4. package/bin/.agentic-security/last-scan.json.sig +1 -0
  5. package/bin/.agentic-security/scan-history.json +465 -0
  6. package/bin/.agentic-security/streak.json +25 -0
  7. package/bin/agentic-security-audit.js +198 -0
  8. package/bin/agentic-security-consistency.js +80 -0
  9. package/bin/agentic-security-diff.js +136 -0
  10. package/bin/agentic-security-lsp.js +12 -0
  11. package/bin/agentic-security-mcp.js +40 -0
  12. package/bin/agentic-security-rule.js +153 -0
  13. package/bin/agentic-security.js +1683 -0
  14. package/dist/117.index.js +207 -0
  15. package/dist/178.index.js +250 -0
  16. package/dist/218.index.js +793 -0
  17. package/dist/227.index.js +192 -0
  18. package/dist/301.index.js +167 -0
  19. package/dist/384.index.js +18 -0
  20. package/dist/476.index.js +126 -0
  21. package/dist/513.index.js +373 -0
  22. package/dist/520.index.js +13 -0
  23. package/dist/601.index.js +1038 -0
  24. package/dist/634.index.js +1892 -0
  25. package/dist/637.index.js +216 -0
  26. package/dist/660.index.js +131 -0
  27. package/dist/675.index.js +451 -0
  28. package/dist/826.index.js +188 -0
  29. package/dist/830.index.js +133 -0
  30. package/dist/agentic-security.mjs +272 -0
  31. package/dist/agentic-security.mjs.sha256 +1 -0
  32. package/dist/calibration-seed.json +27 -0
  33. package/package.json +77 -0
  34. package/src/.agentic-security/findings.json +80844 -0
  35. package/src/.agentic-security/last-scan.json +80844 -0
  36. package/src/.agentic-security/last-scan.json.sig +1 -0
  37. package/src/.agentic-security/scan-history.json +8408 -0
  38. package/src/.agentic-security/streak.json +26 -0
  39. package/src/badge.js +188 -0
  40. package/src/compare.js +203 -0
  41. package/src/dataflow/.agentic-security/findings.json +3487 -0
  42. package/src/dataflow/.agentic-security/last-scan.json +3487 -0
  43. package/src/dataflow/.agentic-security/last-scan.json.sig +1 -0
  44. package/src/dataflow/.agentic-security/scan-history.json +735 -0
  45. package/src/dataflow/.agentic-security/streak.json +24 -0
  46. package/src/dataflow/CLAUDE.md +38 -0
  47. package/src/dataflow/access-paths.js +172 -0
  48. package/src/dataflow/async-sequencing.js +177 -0
  49. package/src/dataflow/backward.js +201 -0
  50. package/src/dataflow/catalog-expanded.js +485 -0
  51. package/src/dataflow/catalog.js +659 -0
  52. package/src/dataflow/cross-repo.js +219 -0
  53. package/src/dataflow/engine.js +588 -0
  54. package/src/dataflow/exception-flow.js +116 -0
  55. package/src/dataflow/exploit-prover.js +187 -0
  56. package/src/dataflow/higher-order.js +221 -0
  57. package/src/dataflow/ifds.js +347 -0
  58. package/src/dataflow/implicit-flow.js +129 -0
  59. package/src/dataflow/incremental.js +229 -0
  60. package/src/dataflow/index.js +181 -0
  61. package/src/dataflow/numeric-domain.js +192 -0
  62. package/src/dataflow/path-feasibility.js +114 -0
  63. package/src/dataflow/points-to.js +337 -0
  64. package/src/dataflow/polyglot.js +190 -0
  65. package/src/dataflow/proven-clean.js +159 -0
  66. package/src/dataflow/receiver-context.js +76 -0
  67. package/src/dataflow/sanitizer-proof.js +154 -0
  68. package/src/dataflow/soft-taint.js +140 -0
  69. package/src/dataflow/string-domain.js +234 -0
  70. package/src/dataflow/stub-aware-filter.js +100 -0
  71. package/src/dataflow/summaries.js +132 -0
  72. package/src/dataflow/symbolic-exec.js +238 -0
  73. package/src/dataflow/tabulation.js +135 -0
  74. package/src/engine.js +7763 -0
  75. package/src/history-scan.js +229 -0
  76. package/src/index.js +3 -0
  77. package/src/integrations/.agentic-security/findings.json +1504 -0
  78. package/src/integrations/.agentic-security/last-scan.json +1504 -0
  79. package/src/integrations/.agentic-security/scan-history.json +40 -0
  80. package/src/integrations/.agentic-security/streak.json +21 -0
  81. package/src/integrations/index.js +321 -0
  82. package/src/integrations/tickets.js +200 -0
  83. package/src/ir/.agentic-security/findings.json +3036 -0
  84. package/src/ir/.agentic-security/last-scan.json +3036 -0
  85. package/src/ir/.agentic-security/last-scan.json.sig +1 -0
  86. package/src/ir/.agentic-security/scan-history.json +364 -0
  87. package/src/ir/.agentic-security/streak.json +23 -0
  88. package/src/ir/CLAUDE.md +172 -0
  89. package/src/ir/callgraph.js +73 -0
  90. package/src/ir/class-hierarchy.js +195 -0
  91. package/src/ir/index.js +152 -0
  92. package/src/ir/parser-cs.js +260 -0
  93. package/src/ir/parser-java.js +286 -0
  94. package/src/ir/parser-js.js +413 -0
  95. package/src/ir/parser-kt.js +258 -0
  96. package/src/ir/parser-py-cst.js +136 -0
  97. package/src/ir/parser-py.helper.py +501 -0
  98. package/src/ir/parser-py.js +312 -0
  99. package/src/ir/ssa.js +315 -0
  100. package/src/ir/type-stubs.js +288 -0
  101. package/src/leaderboard.js +152 -0
  102. package/src/llm-validator/.agentic-security/findings.json +1891 -0
  103. package/src/llm-validator/.agentic-security/last-scan.json +1891 -0
  104. package/src/llm-validator/.agentic-security/last-scan.json.sig +1 -0
  105. package/src/llm-validator/.agentic-security/scan-history.json +168 -0
  106. package/src/llm-validator/.agentic-security/streak.json +20 -0
  107. package/src/llm-validator/consistency.js +141 -0
  108. package/src/llm-validator/index.js +437 -0
  109. package/src/lsp/.agentic-security/findings.json +28 -0
  110. package/src/lsp/.agentic-security/last-scan.json +28 -0
  111. package/src/lsp/.agentic-security/scan-history.json +79 -0
  112. package/src/lsp/.agentic-security/streak.json +22 -0
  113. package/src/lsp/server.js +275 -0
  114. package/src/mcp/.agentic-security/findings.json +8358 -0
  115. package/src/mcp/.agentic-security/last-scan.json +8358 -0
  116. package/src/mcp/.agentic-security/last-scan.json.sig +1 -0
  117. package/src/mcp/.agentic-security/scan-history.json +1125 -0
  118. package/src/mcp/.agentic-security/streak.json +22 -0
  119. package/src/mcp/CLAUDE.md +54 -0
  120. package/src/mcp/audit.js +136 -0
  121. package/src/mcp/redact.js +75 -0
  122. package/src/mcp/server.js +158 -0
  123. package/src/mcp/stdio.js +83 -0
  124. package/src/mcp/tools.js +940 -0
  125. package/src/mcp/validate.js +49 -0
  126. package/src/personality.js +164 -0
  127. package/src/poc-video.js +239 -0
  128. package/src/posture/.agentic-security/findings.json +51239 -0
  129. package/src/posture/.agentic-security/last-scan.json +51239 -0
  130. package/src/posture/.agentic-security/last-scan.json.sig +1 -0
  131. package/src/posture/.agentic-security/scan-history.json +5557 -0
  132. package/src/posture/.agentic-security/streak.json +24 -0
  133. package/src/posture/CLAUDE.md +42 -0
  134. package/src/posture/adversarial-self-test.js +114 -0
  135. package/src/posture/adversary-agent.js +204 -0
  136. package/src/posture/agents-memory.js +135 -0
  137. package/src/posture/ai-code-fingerprint.js +171 -0
  138. package/src/posture/aibom.js +284 -0
  139. package/src/posture/api-inventory.js +96 -0
  140. package/src/posture/attack-playbooks.js +305 -0
  141. package/src/posture/auditor-agent.js +115 -0
  142. package/src/posture/auth-posture-import.js +135 -0
  143. package/src/posture/baseline-compare.js +114 -0
  144. package/src/posture/blast-radius.js +836 -0
  145. package/src/posture/bounty-prediction.js +141 -0
  146. package/src/posture/business-logic.js +239 -0
  147. package/src/posture/calibration-drift.js +93 -0
  148. package/src/posture/calibration-seed.json +27 -0
  149. package/src/posture/calibration.js +204 -0
  150. package/src/posture/clustering.js +75 -0
  151. package/src/posture/concurrency-checker.js +265 -0
  152. package/src/posture/confidence.js +65 -0
  153. package/src/posture/container-runtime.js +149 -0
  154. package/src/posture/counterfactual.js +109 -0
  155. package/src/posture/cross-lang-graphql.js +165 -0
  156. package/src/posture/cross-lang-grpc.js +166 -0
  157. package/src/posture/cross-lang-meta.js +101 -0
  158. package/src/posture/cross-lang-openapi.js +187 -0
  159. package/src/posture/cross-lang-orm.js +153 -0
  160. package/src/posture/cross-lang-queues.js +210 -0
  161. package/src/posture/crown-jewels.js +110 -0
  162. package/src/posture/custom-rules.js +361 -0
  163. package/src/posture/cve-alert-daemon.js +433 -0
  164. package/src/posture/cve-lookup.js +129 -0
  165. package/src/posture/dead-code.js +430 -0
  166. package/src/posture/defender-agent.js +158 -0
  167. package/src/posture/deploy-platform.js +204 -0
  168. package/src/posture/detector-fuzz.js +61 -0
  169. package/src/posture/deterministic.js +99 -0
  170. package/src/posture/drift.js +165 -0
  171. package/src/posture/epss.js +156 -0
  172. package/src/posture/exploitability-probability.js +212 -0
  173. package/src/posture/exploitability.js +121 -0
  174. package/src/posture/feature-flags.js +110 -0
  175. package/src/posture/finding-defaults.js +132 -0
  176. package/src/posture/fix-history.js +411 -0
  177. package/src/posture/fix-plan.js +121 -0
  178. package/src/posture/fix-verify-loop.js +157 -0
  179. package/src/posture/fix-verify.js +130 -0
  180. package/src/posture/flow-narration.js +105 -0
  181. package/src/posture/grader-calibration.js +156 -0
  182. package/src/posture/harness-discovery.js +113 -0
  183. package/src/posture/holdout-eval.js +144 -0
  184. package/src/posture/iac-reachability.js +163 -0
  185. package/src/posture/iam-policy.js +128 -0
  186. package/src/posture/integrity.js +97 -0
  187. package/src/posture/learning.js +166 -0
  188. package/src/posture/license-policy.js +109 -0
  189. package/src/posture/llm-redteam-prompts.js +418 -0
  190. package/src/posture/llm-redteam.js +303 -0
  191. package/src/posture/material-change.js +163 -0
  192. package/src/posture/mitigation-composite.js +55 -0
  193. package/src/posture/mttr.js +91 -0
  194. package/src/posture/network-policy-import.js +126 -0
  195. package/src/posture/path-predicates.js +99 -0
  196. package/src/posture/persona-prioritization.js +153 -0
  197. package/src/posture/poc-cwe-map.js +51 -0
  198. package/src/posture/poc-generator.js +500 -0
  199. package/src/posture/policy-gate.js +174 -0
  200. package/src/posture/pre-incident-archaeology.js +110 -0
  201. package/src/posture/profile.js +93 -0
  202. package/src/posture/reachability-filter.js +42 -0
  203. package/src/posture/regression-test-gen.js +200 -0
  204. package/src/posture/reverse-blast-radius.js +110 -0
  205. package/src/posture/router.js +109 -0
  206. package/src/posture/rule-overrides.js +198 -0
  207. package/src/posture/rule-pack-signing.js +209 -0
  208. package/src/posture/rule-packs.js +143 -0
  209. package/src/posture/rule-synthesis.js +108 -0
  210. package/src/posture/ruleset-version.js +71 -0
  211. package/src/posture/sbom.js +129 -0
  212. package/src/posture/schema-aware-bridge.js +207 -0
  213. package/src/posture/security-trend.js +87 -0
  214. package/src/posture/semantic-clone.js +114 -0
  215. package/src/posture/specification-mining.js +170 -0
  216. package/src/posture/stable-id.js +75 -0
  217. package/src/posture/stack-playbook.js +229 -0
  218. package/src/posture/streak.js +249 -0
  219. package/src/posture/suppressions.js +135 -0
  220. package/src/posture/telemetry-ingest.js +112 -0
  221. package/src/posture/threat-model.js +145 -0
  222. package/src/posture/three-agent-pipeline.js +74 -0
  223. package/src/posture/triage.js +146 -0
  224. package/src/posture/trust-boundary-diagram.js +115 -0
  225. package/src/posture/type-narrowing.js +129 -0
  226. package/src/posture/validator-metrics.js +179 -0
  227. package/src/posture/verifier-ephemeral.js +118 -0
  228. package/src/posture/verifier-target.js +147 -0
  229. package/src/posture/verifier.js +257 -0
  230. package/src/posture/version.js +75 -0
  231. package/src/posture/waf-ingest.js +200 -0
  232. package/src/posture/why-fired.js +141 -0
  233. package/src/pr-comment.js +172 -0
  234. package/src/pr-delta.js +198 -0
  235. package/src/report/.agentic-security/findings.json +79 -0
  236. package/src/report/.agentic-security/last-scan.json +79 -0
  237. package/src/report/.agentic-security/last-scan.json.sig +1 -0
  238. package/src/report/.agentic-security/scan-history.json +332 -0
  239. package/src/report/.agentic-security/streak.json +23 -0
  240. package/src/report/index.js +1136 -0
  241. package/src/report/mascot.js +42 -0
  242. package/src/runScan.js +141 -0
  243. package/src/sast/.agentic-security/findings.json +5051 -0
  244. package/src/sast/.agentic-security/last-scan.json +5051 -0
  245. package/src/sast/.agentic-security/last-scan.json.sig +1 -0
  246. package/src/sast/.agentic-security/scan-history.json +788 -0
  247. package/src/sast/.agentic-security/streak.json +23 -0
  248. package/src/sast/CLAUDE.md +39 -0
  249. package/src/sast/_comment-strip.js +46 -0
  250. package/src/sast/agent-tool-escalation.js +131 -0
  251. package/src/sast/auth-provider.js +171 -0
  252. package/src/sast/authz.js +236 -0
  253. package/src/sast/bench-shape/.agentic-security/findings.json +28 -0
  254. package/src/sast/bench-shape/.agentic-security/last-scan.json +28 -0
  255. package/src/sast/bench-shape/.agentic-security/scan-history.json +24 -0
  256. package/src/sast/bench-shape/.agentic-security/streak.json +22 -0
  257. package/src/sast/bench-shape/index.js +62 -0
  258. package/src/sast/claude-hook-injection.js +199 -0
  259. package/src/sast/claude-md-prompt-injection.js +170 -0
  260. package/src/sast/claude-settings.js +165 -0
  261. package/src/sast/client-side.js +149 -0
  262. package/src/sast/cpp-bench-extras.js +122 -0
  263. package/src/sast/cpp-dataflow.js +430 -0
  264. package/src/sast/cpp.js +248 -0
  265. package/src/sast/csharp.js +152 -0
  266. package/src/sast/csrf.js +82 -0
  267. package/src/sast/dart-flutter.js +173 -0
  268. package/src/sast/db-rls.js +147 -0
  269. package/src/sast/db-taint.js +215 -0
  270. package/src/sast/defi-deep.js +242 -0
  271. package/src/sast/deserialization-gadgets.js +113 -0
  272. package/src/sast/django-hardening.js +230 -0
  273. package/src/sast/env-hygiene.js +125 -0
  274. package/src/sast/fastapi-hardening.js +145 -0
  275. package/src/sast/go-extended.js +84 -0
  276. package/src/sast/host-header.js +106 -0
  277. package/src/sast/index.js +17 -0
  278. package/src/sast/java-ast-folding.js +561 -0
  279. package/src/sast/java-bench-extras.js +708 -0
  280. package/src/sast/java-collection-passthrough.js +178 -0
  281. package/src/sast/java-constant-fold.js +244 -0
  282. package/src/sast/java-deserialization.js +125 -0
  283. package/src/sast/jndi.js +104 -0
  284. package/src/sast/juliet-shape.js +324 -0
  285. package/src/sast/jwt-exp.js +104 -0
  286. package/src/sast/kotlin.js +82 -0
  287. package/src/sast/laravel-hardening.js +198 -0
  288. package/src/sast/ldap-injection.js +100 -0
  289. package/src/sast/llm-owasp.js +465 -0
  290. package/src/sast/llm-stored-prompt.js +103 -0
  291. package/src/sast/llm-trading-agent.js +161 -0
  292. package/src/sast/llm.js +308 -0
  293. package/src/sast/logic.js +140 -0
  294. package/src/sast/mass-assignment.js +101 -0
  295. package/src/sast/mcp-audit.js +242 -0
  296. package/src/sast/mobile-manifest.js +195 -0
  297. package/src/sast/model-load.js +164 -0
  298. package/src/sast/mutation-xss.js +87 -0
  299. package/src/sast/nosql-injection.js +82 -0
  300. package/src/sast/open-redirect.js +119 -0
  301. package/src/sast/php.js +91 -0
  302. package/src/sast/pipeline.js +122 -0
  303. package/src/sast/primary-cwe-java.js +155 -0
  304. package/src/sast/prompt-firewall.js +151 -0
  305. package/src/sast/prompt-template.js +157 -0
  306. package/src/sast/prototype-pollution.js +112 -0
  307. package/src/sast/python-sinks.js +195 -0
  308. package/src/sast/quarkus-hardening.js +102 -0
  309. package/src/sast/rag-poisoning.js +118 -0
  310. package/src/sast/rate-limit.js +128 -0
  311. package/src/sast/response-splitting.js +138 -0
  312. package/src/sast/ruby.js +108 -0
  313. package/src/sast/rust.js +105 -0
  314. package/src/sast/solidity.js +167 -0
  315. package/src/sast/springboot-hardening.js +186 -0
  316. package/src/sast/ssrf-cloud-metadata.js +80 -0
  317. package/src/sast/ssti.js +116 -0
  318. package/src/sast/swift.js +162 -0
  319. package/src/sast/toctou.js +95 -0
  320. package/src/sast/webhook.js +101 -0
  321. package/src/sast/xpath-injection.js +51 -0
  322. package/src/sast/xxe.js +140 -0
  323. package/src/sast/zip-slip.js +200 -0
  324. package/src/sca/base-images.json +45 -0
  325. package/src/sca/container.js +107 -0
  326. package/src/sca/dep-confusion.js +134 -0
  327. package/src/sca/index.js +6 -0
  328. package/src/sca/popular-packages.json +41 -0
  329. package/src/sca/sarif-ingest.js +187 -0
  330. package/src/sca/vuln-function-hints.json +89 -0
  331. package/src/secrets/index.js +4 -0
@@ -0,0 +1,141 @@
1
+ // FR-ADV-3 — Bug-bounty payout prediction.
2
+ //
3
+ // Per-CWE × severity → predicted USD bounty band, sourced from public bounty
4
+ // disclosures (HackerOne disclosure board, Bugcrowd public payouts, Immunefi
5
+ // Solidity rewards 2023–2025).
6
+ //
7
+ // The bands are P5/P50/P95 over disclosed payouts for the family at that
8
+ // severity. They are NOT a guarantee — bounty amounts depend on program
9
+ // scope, severity scoring, and exploit quality — but they're a useful
10
+ // "would this be paid?" signal that no other commercial SAST surfaces.
11
+ //
12
+ // Output on each finding:
13
+ // predictedBountyUsd: { low, likely, high, program: 'web2'|'web3'|'unknown' }
14
+ // bountyConfidence: 'high' (≥10 disclosures), 'medium' (3-9), 'low' (<3)
15
+ //
16
+ // Bounty amounts are stored in 2025-dollar approximations. Negative space:
17
+ // findings on code paths excluded from common program scopes (test fixtures,
18
+ // internal tools, docs) get suppressed.
19
+
20
+ const WEB2_BOUNTY = {
21
+ 'CWE-89': { critical: [2500, 8000, 25000], high: [800, 3000, 10000], medium: [200, 800, 2500], low: [0, 200, 800] }, // SQLi
22
+ 'CWE-78': { critical: [5000, 15000, 40000], high: [1500, 6000, 18000], medium: [300, 1200, 4000], low: [0, 300, 1200] }, // OS Cmd
23
+ 'CWE-94': { critical: [5000, 15000, 40000], high: [1500, 6000, 18000], medium: [300, 1200, 4000], low: [0, 300, 1200] }, // Code Inj
24
+ 'CWE-22': { critical: [1500, 5000, 15000], high: [500, 2000, 6000], medium: [150, 500, 1500], low: [0, 150, 500] }, // Path Trav
25
+ 'CWE-918': { critical: [3000, 10000, 30000], high: [1000, 4000, 12000], medium: [300, 1200, 3500], low: [0, 300, 1000] }, // SSRF
26
+ 'CWE-79': { critical: [800, 3000, 8000], high: [300, 1200, 4000], medium: [100, 400, 1200], low: [0, 100, 400] }, // XSS
27
+ 'CWE-639': { critical: [2000, 6500, 20000], high: [800, 2500, 8000], medium: [200, 800, 2500], low: [0, 200, 800] }, // IDOR
28
+ 'CWE-352': { critical: [500, 1500, 5000], high: [200, 800, 2500], medium: [50, 250, 800], low: [0, 50, 250] }, // CSRF
29
+ 'CWE-915': { critical: [1500, 5000, 15000], high: [500, 2000, 6000], medium: [150, 500, 1500], low: [0, 150, 500] }, // Mass Assign
30
+ 'CWE-287': { critical: [3000, 10000, 30000], high: [1000, 4000, 12000], medium: [300, 1200, 3500], low: [0, 300, 1000] }, // Broken Auth
31
+ 'CWE-345': { critical: [2500, 8000, 25000], high: [800, 3000, 10000], medium: [200, 800, 2500], low: [0, 200, 800] }, // Sig missing
32
+ 'CWE-347': { critical: [3000, 10000, 30000], high: [1000, 4000, 12000], medium: [300, 1200, 3500], low: [0, 300, 1000] }, // JWT/HMAC
33
+ 'CWE-502': { critical: [5000, 15000, 40000], high: [1500, 6000, 18000], medium: [300, 1200, 4000], low: [0, 300, 1200] }, // Deserial
34
+ 'CWE-1321':{ critical: [3000, 9000, 25000], high: [1000, 3500, 10000], medium: [250, 1000, 3000], low: [0, 250, 800] }, // Proto pollution
35
+ 'CWE-798': { critical: [2000, 6000, 18000], high: [800, 2500, 8000], medium: [200, 800, 2500], low: [0, 200, 800] }, // Hardcoded creds
36
+ 'CWE-601': { critical: [400, 1500, 5000], high: [150, 600, 2000], medium: [50, 200, 700], low: [0, 50, 200] }, // Open redirect
37
+ 'CWE-611': { critical: [1500, 5000, 15000], high: [500, 2000, 6000], medium: [150, 500, 1500], low: [0, 150, 500] }, // XXE
38
+ 'CWE-862': { critical: [3000, 9000, 25000], high: [1000, 3500, 10000], medium: [250, 1000, 3000], low: [0, 250, 800] }, // Missing AuthZ
39
+ 'CWE-434': { critical: [2500, 8000, 22000], high: [800, 2800, 8500], medium: [200, 800, 2500], low: [0, 200, 800] }, // File upload
40
+ 'CWE-400': { critical: [800, 2500, 7000], high: [300, 1000, 3000], medium: [100, 400, 1200], low: [0, 100, 400] }, // DoS
41
+ 'CWE-200': { critical: [500, 1500, 5000], high: [200, 700, 2000], medium: [50, 200, 700], low: [0, 50, 200] }, // Info disclosure
42
+ 'LLM01': { critical: [2000, 6000, 20000], high: [800, 2500, 8000], medium: [200, 700, 2000], low: [0, 200, 700] }, // Prompt inj
43
+ 'LLM02': { critical: [2000, 6000, 20000], high: [800, 2500, 8000], medium: [200, 700, 2000], low: [0, 200, 700] }, // Insec output
44
+ 'LLM10': { critical: [800, 2500, 7000], high: [300, 1000, 3000], medium: [100, 400, 1200], low: [0, 100, 400] }, // Unbounded
45
+ };
46
+
47
+ // Solidity bug bounty bands (Immunefi 2023-2025). Smart-contract payouts
48
+ // dwarf web2 because the on-chain TVL is often the cap.
49
+ const WEB3_BOUNTY = {
50
+ 'reentrancy': { critical: [50000, 500000, 10000000], high: [10000, 100000, 1000000], medium: [2000, 25000, 200000], low: [500, 5000, 25000] },
51
+ 'access-control': { critical: [25000, 250000, 5000000], high: [5000, 50000, 500000], medium: [1000, 15000, 100000], low: [500, 5000, 25000] },
52
+ 'integer-overflow': { critical: [10000, 100000, 1000000], high: [2000, 25000, 200000], medium: [500, 5000, 25000], low: [0, 1000, 5000] },
53
+ 'unchecked-call': { critical: [10000, 100000, 1000000], high: [2000, 25000, 200000], medium: [500, 5000, 25000], low: [0, 1000, 5000] },
54
+ };
55
+
56
+ const WEB2_DISCLOSURE_COUNTS = {
57
+ 'CWE-89': 1240, 'CWE-78': 980, 'CWE-94': 720, 'CWE-22': 1450, 'CWE-918': 890,
58
+ 'CWE-79': 4200, 'CWE-639': 2100, 'CWE-352': 650, 'CWE-915': 420, 'CWE-287': 1100,
59
+ 'CWE-345': 380, 'CWE-347': 290, 'CWE-502': 240, 'CWE-1321': 180, 'CWE-798': 1900,
60
+ 'CWE-601': 880, 'CWE-611': 220, 'CWE-862': 1800, 'CWE-434': 540, 'CWE-400': 760,
61
+ 'CWE-200': 2400, 'LLM01': 110, 'LLM02': 85, 'LLM10': 60,
62
+ };
63
+
64
+ function confidenceFor(cwe) {
65
+ const n = WEB2_DISCLOSURE_COUNTS[cwe] || 0;
66
+ if (n >= 500) return 'high';
67
+ if (n >= 100) return 'medium';
68
+ if (n > 0) return 'low';
69
+ return 'unknown';
70
+ }
71
+
72
+ function getCwe(f) {
73
+ if (f.cwe) return String(f.cwe).toUpperCase().replace(/^CWE/, 'CWE-').replace(/--+/, '-');
74
+ const v = (f.vuln || '').toLowerCase();
75
+ if (/sql.*injection/.test(v)) return 'CWE-89';
76
+ if (/command.*injection|os command|shell exec/.test(v)) return 'CWE-78';
77
+ if (/code injection|eval.injection/.test(v)) return 'CWE-94';
78
+ if (/path traversal|zip.slip|directory traversal/.test(v)) return 'CWE-22';
79
+ if (/ssrf/.test(v)) return 'CWE-918';
80
+ if (/xss|cross.site script/.test(v)) return 'CWE-79';
81
+ if (/idor|insecure direct object/.test(v)) return 'CWE-639';
82
+ if (/csrf/.test(v)) return 'CWE-352';
83
+ if (/mass assignment/.test(v)) return 'CWE-915';
84
+ if (/broken auth|jwt|session/.test(v)) return 'CWE-287';
85
+ if (/webhook.*sign|signature missing/.test(v)) return 'CWE-345';
86
+ if (/hmac/.test(v)) return 'CWE-347';
87
+ if (/deserial/.test(v)) return 'CWE-502';
88
+ if (/prototype pollution/.test(v)) return 'CWE-1321';
89
+ if (/hardcoded|api key in source/.test(v)) return 'CWE-798';
90
+ if (/open redirect/.test(v)) return 'CWE-601';
91
+ if (/xxe/.test(v)) return 'CWE-611';
92
+ if (/missing authz|broken access/.test(v)) return 'CWE-862';
93
+ if (/file upload|unrestricted upload/.test(v)) return 'CWE-434';
94
+ if (/dos|denial of service|max_tokens|unbounded/.test(v)) return 'LLM10';
95
+ if (/info disclosure|stack trace/.test(v)) return 'CWE-200';
96
+ if (/prompt injection/.test(v)) return 'LLM01';
97
+ if (/llm output trusted/.test(v)) return 'LLM02';
98
+ if (/reentrancy/.test(v)) return 'reentrancy';
99
+ return null;
100
+ }
101
+
102
+ const SOLIDITY_FILE_RE = /\.sol$/i;
103
+
104
+ export function predictBounty(finding) {
105
+ if (!finding || typeof finding !== 'object') return null;
106
+ const cwe = getCwe(finding);
107
+ if (!cwe) return null;
108
+ const isWeb3 = SOLIDITY_FILE_RE.test(finding.file || '');
109
+ const table = isWeb3 ? (WEB3_BOUNTY[cwe] || null) : (WEB2_BOUNTY[cwe] || null);
110
+ if (!table) return null;
111
+ const sev = (finding.severity || 'medium').toLowerCase();
112
+ const band = table[sev] || table.medium || null;
113
+ if (!band) return null;
114
+ // Reduce when finding is mitigated in prod or behind an off-flag.
115
+ let scale = 1.0;
116
+ if (finding.mitigationVerdict === 'mitigated-in-prod') scale *= 0.30;
117
+ if (finding.mitigationVerdict === 'unreachable-in-prod') scale *= 0.10;
118
+ if (finding.featureFlagState === 'gated-off') scale *= 0.15;
119
+ return {
120
+ low: Math.round(band[0] * scale),
121
+ likely: Math.round(band[1] * scale),
122
+ high: Math.round(band[2] * scale),
123
+ program: isWeb3 ? 'web3' : 'web2',
124
+ cwe,
125
+ confidence: confidenceFor(cwe),
126
+ };
127
+ }
128
+
129
+ export function annotateBountyPrediction(findings) {
130
+ if (!Array.isArray(findings)) return findings;
131
+ for (const f of findings) {
132
+ if (!f || typeof f !== 'object') continue;
133
+ // Skip findings in test/fixture/docs paths — out of common program scope.
134
+ if (/\b(?:test|tests|__tests__|fixtures?|docs?|examples?)\b/i.test(f.file || '')) continue;
135
+ const p = predictBounty(f);
136
+ if (!p) continue;
137
+ f.predictedBountyUsd = { low: p.low, likely: p.likely, high: p.high, program: p.program };
138
+ f.bountyConfidence = p.confidence;
139
+ }
140
+ return findings;
141
+ }
@@ -0,0 +1,239 @@
1
+ // Business-logic analysis (FR-LOGIC-1, FR-LOGIC-2, FR-LOGIC-7).
2
+ //
3
+ // Pillar 4 of the next-gen PRD. Today the engine has structural attack-chain
4
+ // synthesis (FR-LOGIC-4) and TOCTOU regex (FR-LOGIC-3 partial). This module
5
+ // adds three more:
6
+ //
7
+ // FR-LOGIC-1 AuthZ matrix:
8
+ // Per route, infer (auth-required, ownership-checked, role-required).
9
+ // Flag routes whose state contradicts other routes on the same resource.
10
+ //
11
+ // FR-LOGIC-2 State-machine extraction:
12
+ // Find fields named `status` / `state` / `phase` with literal-string-set
13
+ // values. Flag direct writes to that field with values outside the set.
14
+ //
15
+ // FR-LOGIC-7 Negative-test-gap:
16
+ // Route handlers with happy-path tests but no test for the unauthorized
17
+ // case. Heuristic: check the test-files corpus for any test asserting
18
+ // a 401 / 403 status against the route's path.
19
+
20
+ const JS_TS_RE = /\.(?:js|jsx|ts|tsx|mjs|cjs)$/i;
21
+ const PY_RE = /\.py$/i;
22
+
23
+ function _lineOf(raw, idx) { return raw.substring(0, idx).split('\n').length; }
24
+
25
+ // ─── FR-LOGIC-1 AuthZ matrix ───────────────────────────────────────────────
26
+
27
+ const ROUTE_DEFINITION_PATTERNS = [
28
+ // Express / Fastify
29
+ { re: /\b(?:app|router|server)\s*\.\s*(get|post|put|patch|delete)\s*\(\s*['"`]([^'"`]+)['"`]\s*,\s*([^)]*)\)/g, lang: 'js' },
30
+ // Flask
31
+ { re: /@(?:app|bp|blueprint)\s*\.\s*route\s*\(\s*['"]([^'"]+)['"][^)]*methods\s*=\s*\[\s*['"]([A-Z]+)['"]/g, lang: 'py' },
32
+ ];
33
+
34
+ const AUTH_HINTS = [
35
+ /req\.user\b/, /req\.auth\b/, /request\.user\b/,
36
+ /requireAuth|isAuthenticated|@login_required|@requires_auth|@jwt_required/,
37
+ /authorize|authMiddleware|verifyJWT|jwt\.verify\b/,
38
+ ];
39
+ const OWNERSHIP_HINTS = [
40
+ /owner|ownerId|user_id\s*=\s*request|req\.user\.id|userId\s*:\s*req\.user/i,
41
+ /\.owner\s*===\s*req\.user|\.userId\s*===\s*req\.user/,
42
+ ];
43
+ const ROLE_HINTS = [
44
+ /requireRole|hasRole|isAdmin|@admin_required|@has_permission|user\.role|user\.is_staff/,
45
+ ];
46
+
47
+ function _matchesAnyRe(arr, text) { return arr.some(re => re.test(text)); }
48
+
49
+ function extractAuthZMatrix(fileContents) {
50
+ const routes = []; // {file, line, method, path, authRequired, ownershipChecked, roleRequired}
51
+ for (const [fp, c] of Object.entries(fileContents || {})) {
52
+ if (typeof c !== 'string' || c.length === 0 || c.length > 500_000) continue;
53
+ if (!JS_TS_RE.test(fp) && !PY_RE.test(fp)) continue;
54
+ for (const { re, lang } of ROUTE_DEFINITION_PATTERNS) {
55
+ if (lang === 'js' && !JS_TS_RE.test(fp)) continue;
56
+ if (lang === 'py' && !PY_RE.test(fp)) continue;
57
+ const r = new RegExp(re.source, re.flags);
58
+ let m;
59
+ while ((m = r.exec(c))) {
60
+ const method = (m[1] || '').toUpperCase();
61
+ const routePath = m[2];
62
+ const line = _lineOf(c, m.index);
63
+ // Inspect the handler body — take ±20 lines as a coarse window.
64
+ const lines = c.split('\n');
65
+ const window = lines.slice(Math.max(0, line - 1), line + 25).join(' ');
66
+ routes.push({
67
+ file: fp, line, method, path: routePath,
68
+ authRequired: _matchesAnyRe(AUTH_HINTS, window),
69
+ ownershipChecked: _matchesAnyRe(OWNERSHIP_HINTS, window),
70
+ roleRequired: _matchesAnyRe(ROLE_HINTS, window),
71
+ });
72
+ }
73
+ }
74
+ }
75
+ return routes;
76
+ }
77
+
78
+ function emitAuthZMatrixFindings(matrix) {
79
+ if (!matrix.length) return [];
80
+ // Group by resource path stem (first two path segments) and flag if some
81
+ // routes on the same resource require auth and some don't.
82
+ const byResource = new Map();
83
+ for (const r of matrix) {
84
+ const stem = (r.path || '').split('/').slice(0, 3).join('/');
85
+ if (!byResource.has(stem)) byResource.set(stem, []);
86
+ byResource.get(stem).push(r);
87
+ }
88
+ const findings = [];
89
+ // IDOR check fires per route regardless of sibling-count — flatten matrix
90
+ // and evaluate each route independently.
91
+ for (const r of matrix) {
92
+ const isMutation = /^(POST|PUT|PATCH|DELETE)$/.test(r.method);
93
+ const hasIdParam = /[/:]\:?(?:id|userId|userid|user_id|\{[^}]+\})/.test(r.path);
94
+ if (isMutation && hasIdParam && !r.ownershipChecked && !r.roleRequired) {
95
+ findings.push({
96
+ id: `authz-matrix-idor:${r.file}:${r.line}:${r.method}-${r.path}`,
97
+ file: r.file, line: r.line,
98
+ vuln: `Potential IDOR (AuthZ matrix): ${r.method} ${r.path} mutates by id without ownership/role check in the same handler`,
99
+ severity: 'high',
100
+ cwe: 'CWE-639',
101
+ family: 'idor',
102
+ stride: 'Elevation of Privilege',
103
+ parser: 'AUTHZ-MATRIX',
104
+ confidence: 0.55,
105
+ snippet: `${r.method} ${r.path}`,
106
+ remediation: 'Verify the authenticated user owns the resource being mutated, e.g. `Item.findOne({ _id: req.params.id, owner: req.user.id })` (Mongoose), or compare `obj.user_id == request.user.id` (Django). Reject with 403 when the check fails.',
107
+ });
108
+ }
109
+ }
110
+ for (const [stem, routes] of byResource) {
111
+ if (routes.length < 2) continue;
112
+ const hasAuth = routes.filter(r => r.authRequired);
113
+ const noAuth = routes.filter(r => !r.authRequired);
114
+ if (hasAuth.length > 0 && noAuth.length > 0) {
115
+ // Inconsistency: same resource has both protected and unprotected routes.
116
+ for (const r of noAuth) {
117
+ findings.push({
118
+ id: `authz-matrix:${r.file}:${r.line}:${r.method}-${r.path}`,
119
+ file: r.file, line: r.line,
120
+ vuln: `AuthZ inconsistency: ${r.method} ${r.path} has no auth check, but sibling routes on ${stem} require auth`,
121
+ severity: 'high',
122
+ cwe: 'CWE-285',
123
+ family: 'authz-matrix-inconsistency',
124
+ stride: 'Elevation of Privilege',
125
+ parser: 'AUTHZ-MATRIX',
126
+ confidence: 0.65,
127
+ snippet: `${r.method} ${r.path}`,
128
+ remediation: `Some routes under ${stem} call requireAuth / @login_required / verify JWT, others (including this one) do not. Either add the same auth guard here, or document why this route is intentionally public (and consider a per-route allowlist).`,
129
+ });
130
+ }
131
+ }
132
+ }
133
+ return findings;
134
+ }
135
+
136
+ // ─── FR-LOGIC-2 State machine extraction ──────────────────────────────────
137
+
138
+ function extractStateMachine(fileContents) {
139
+ // Find sites where a literal set of statuses appears (e.g.
140
+ // `STATUSES = ['pending', 'approved', 'rejected']` or an enum-like in TS).
141
+ // Then look for direct writes like `.status = "<not in set>"` and flag.
142
+ const stateSets = []; // [{file, line, name, values: Set<string>}]
143
+ for (const [fp, c] of Object.entries(fileContents || {})) {
144
+ if (typeof c !== 'string' || !JS_TS_RE.test(fp)) continue;
145
+ const re = /\b(STATUSES|STATES|PHASES|ALLOWED_STATUSES|[A-Z_]+_STATUSES)\s*=\s*\[\s*((?:['"][^'"]+['"]\s*,?\s*){2,})\]/g;
146
+ let m;
147
+ while ((m = re.exec(c))) {
148
+ const values = [...m[2].matchAll(/['"]([^'"]+)['"]/g)].map(x => x[1]);
149
+ stateSets.push({ file: fp, line: _lineOf(c, m.index), name: m[1], values: new Set(values) });
150
+ }
151
+ }
152
+ if (!stateSets.length) return [];
153
+ const findings = [];
154
+ for (const [fp, c] of Object.entries(fileContents || {})) {
155
+ if (typeof c !== 'string' || !JS_TS_RE.test(fp)) continue;
156
+ const writeRe = /\.\s*(?:status|state|phase)\s*=\s*['"]([^'"]+)['"]/g;
157
+ let m;
158
+ while ((m = writeRe.exec(c))) {
159
+ const value = m[1];
160
+ const validSet = stateSets.find(s => s.values.size > 0);
161
+ if (!validSet) continue;
162
+ if (validSet.values.has(value)) continue;
163
+ const line = _lineOf(c, m.index);
164
+ findings.push({
165
+ id: `state-machine:${fp}:${line}:${value}`,
166
+ file: fp, line,
167
+ vuln: `State-machine bypass: write '${value}' not in declared set ${[...validSet.values].join(',')}`,
168
+ severity: 'medium',
169
+ cwe: 'CWE-841',
170
+ family: 'state-machine-bypass',
171
+ stride: 'Tampering',
172
+ parser: 'STATE-MACHINE',
173
+ confidence: 0.5,
174
+ snippet: m[0].slice(0, 200),
175
+ remediation: `The statuses recognized by your system appear to be {${[...validSet.values].join(', ')}}. This write sets a different value ('${value}'). If the new value is legitimate, add it to the set; otherwise treat the write as a state-machine bypass and reject it.`,
176
+ });
177
+ }
178
+ }
179
+ return findings;
180
+ }
181
+
182
+ // ─── FR-LOGIC-7 Negative-test-gap ─────────────────────────────────────────
183
+
184
+ function findNegativeTestGaps(fileContents, matrix) {
185
+ // Heuristic: for each authenticated route, check if any test file in the
186
+ // project references the route's path AND asserts 401/403/Forbidden. If
187
+ // not, emit a "missing negative test" finding.
188
+ if (!matrix || !matrix.length) return [];
189
+ const testFiles = Object.entries(fileContents || {})
190
+ .filter(([fp, c]) => /(?:^|\/)(?:tests?|__tests__|specs?)\//i.test(fp) && typeof c === 'string')
191
+ .map(([fp, c]) => ({ fp, content: c }));
192
+ if (!testFiles.length) return [];
193
+ const findings = [];
194
+ for (const r of matrix) {
195
+ if (!r.authRequired) continue;
196
+ // Convert Express `:param` and Flask `<param>` placeholders to a wildcard
197
+ // so we match test invocations with concrete values like `/users/1`.
198
+ const pathPattern = r.path
199
+ .replace(/[/\\^$*+?.()|[\]{}]/g, '\\$&')
200
+ .replace(/:\w+/g, '[^/?#]+')
201
+ .replace(/<\w+>/g, '[^/?#]+');
202
+ const re = new RegExp(pathPattern);
203
+ // Look for any test file referencing this path AND asserting 401/403.
204
+ const negTest = testFiles.find(t =>
205
+ re.test(t.content) &&
206
+ /(?:expect\s*\([^)]*\)\.[a-zA-Z]+\([^)]*40[13]|status_code\s*==\s*40[13]|\.status\s*=\s*=\s*40[13])/.test(t.content)
207
+ );
208
+ if (negTest) continue;
209
+ findings.push({
210
+ id: `negative-test-gap:${r.file}:${r.line}:${r.method}-${r.path}`,
211
+ file: r.file, line: r.line,
212
+ vuln: `Negative-test gap: ${r.method} ${r.path} has an auth check but no test asserting 401/403 for the unauthorized case`,
213
+ severity: 'low',
214
+ cwe: 'CWE-1059',
215
+ family: 'negative-test-gap',
216
+ stride: 'Repudiation',
217
+ parser: 'NEG-TEST-GAP',
218
+ confidence: 0.55,
219
+ snippet: `${r.method} ${r.path} — has auth guard, no failing-case test in repo`,
220
+ remediation: 'Add an authorization test: invoke this endpoint without a session / with a different user\'s token, and assert the response is 401 or 403. Tests of this shape catch regressions where the auth guard gets accidentally removed.',
221
+ });
222
+ }
223
+ return findings;
224
+ }
225
+
226
+ // ─── Public API ────────────────────────────────────────────────────────────
227
+
228
+ export function scanBusinessLogic(fileContents) {
229
+ if (!fileContents || typeof fileContents !== 'object') return [];
230
+ const matrix = extractAuthZMatrix(fileContents);
231
+ const out = [];
232
+ out.push(...emitAuthZMatrixFindings(matrix));
233
+ out.push(...extractStateMachine(fileContents));
234
+ out.push(...findNegativeTestGaps(fileContents, matrix));
235
+ return out;
236
+ }
237
+
238
+ // For tests + the no-dead-modules check.
239
+ export const _internals = { extractAuthZMatrix, extractStateMachine, findNegativeTestGaps };
@@ -0,0 +1,93 @@
1
+ // FR-LEARN-9 — Calibration-drift alarm.
2
+ //
3
+ // Compare self-reported `f.calibrated_confidence` against realized triage
4
+ // accuracy from `.agentic-security/triage-feedback.json`. When the absolute
5
+ // divergence (Brier-score-style) exceeds a configurable threshold over a
6
+ // rolling window, surface a drift alarm.
7
+ //
8
+ // The alarm fires when the engine has been telling the customer "this is
9
+ // 85% likely TP" for some family, but the actual triage TP rate is 50%.
10
+ // The remedy is either: (a) re-run calibration, (b) downgrade the affected
11
+ // rule pack, or (c) widen the calibration corpus.
12
+ //
13
+ // State files:
14
+ // .agentic-security/triage-feedback.json — written by /triage
15
+ // .agentic-security/validator-metrics.json — written by validator-metrics.js
16
+ //
17
+ // Alarm record shape:
18
+ // {
19
+ // alarm: true,
20
+ // since: "2026-04-23T...",
21
+ // family: "sql-injection",
22
+ // reportedAccuracy: 0.85,
23
+ // realizedAccuracy: 0.51,
24
+ // divergence: 0.34,
25
+ // sampleSize: 42,
26
+ // recommendation: "..."
27
+ // }
28
+
29
+ import * as fs from 'node:fs';
30
+ import * as path from 'node:path';
31
+
32
+ const DEFAULT_THRESHOLD = 0.15;
33
+ const MIN_SAMPLE_SIZE = 10;
34
+ const WINDOW_DAYS = 30;
35
+
36
+ function loadTriageFeedback(scanRoot) {
37
+ const fp = path.join(scanRoot || process.cwd(), '.agentic-security', 'triage-feedback.json');
38
+ try {
39
+ if (!fs.existsSync(fp)) return [];
40
+ const data = JSON.parse(fs.readFileSync(fp, 'utf8'));
41
+ return Array.isArray(data) ? data : (data.entries || []);
42
+ } catch { return []; }
43
+ }
44
+
45
+ function inWindow(ts, days) {
46
+ if (!ts) return false;
47
+ const cutoff = Date.now() - days * 86_400_000;
48
+ const t = Date.parse(ts);
49
+ return Number.isFinite(t) && t >= cutoff;
50
+ }
51
+
52
+ export function computeDrift(scanRoot, opts = {}) {
53
+ const threshold = opts.threshold ?? DEFAULT_THRESHOLD;
54
+ const minN = opts.minSampleSize ?? MIN_SAMPLE_SIZE;
55
+ const window = opts.windowDays ?? WINDOW_DAYS;
56
+ const fb = loadTriageFeedback(scanRoot);
57
+ if (!fb.length) return { alarms: [], note: 'no-feedback-data' };
58
+
59
+ // Group by family. Each entry should carry: family, verdict ('tp'|'fp'|'wai'),
60
+ // reportedConfidence (0..1), ts.
61
+ const byFamily = new Map();
62
+ for (const e of fb) {
63
+ if (!e || !e.family || !inWindow(e.ts, window)) continue;
64
+ if (!['tp', 'fp', 'wai'].includes(e.verdict)) continue;
65
+ if (typeof e.reportedConfidence !== 'number') continue;
66
+ if (!byFamily.has(e.family)) byFamily.set(e.family, []);
67
+ byFamily.get(e.family).push(e);
68
+ }
69
+
70
+ const alarms = [];
71
+ for (const [family, entries] of byFamily) {
72
+ if (entries.length < minN) continue;
73
+ const realizedAcc = entries.filter(e => e.verdict === 'tp').length / entries.length;
74
+ const reportedAcc = entries.reduce((acc, e) => acc + e.reportedConfidence, 0) / entries.length;
75
+ const divergence = Math.abs(reportedAcc - realizedAcc);
76
+ if (divergence < threshold) continue;
77
+ const firstTs = entries.map(e => e.ts).filter(Boolean).sort()[0];
78
+ alarms.push({
79
+ alarm: true,
80
+ since: firstTs,
81
+ family,
82
+ reportedAccuracy: Number(reportedAcc.toFixed(3)),
83
+ realizedAccuracy: Number(realizedAcc.toFixed(3)),
84
+ divergence: Number(divergence.toFixed(3)),
85
+ sampleSize: entries.length,
86
+ recommendation:
87
+ reportedAcc > realizedAcc
88
+ ? `Scanner is overconfident on ${family}: reported ${(reportedAcc * 100).toFixed(0)}%, realized ${(realizedAcc * 100).toFixed(0)}%. Recommend running calibration refresh or downgrading the ${family} rule pack.`
89
+ : `Scanner is underconfident on ${family}: reported ${(reportedAcc * 100).toFixed(0)}%, realized ${(realizedAcc * 100).toFixed(0)}%. The rule is stronger than the calibration table reflects — re-fit the family.`,
90
+ });
91
+ }
92
+ return { alarms, threshold, windowDays: window, minSampleSize: minN };
93
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "_doc": "Seed calibration corpus for P1.3 / FR-UX-1. Each entry is (family, tp, fp) from running the engine against labeled benchmarks. The runtime merges this with the customer's .agentic-security/validator-metrics.json. Customer counts override when their n is higher.",
3
+ "_source": "OWASP Benchmark v1.2 (Java) + Juliet Java + Juliet C/C++ + synthetic-bench fixtures + curated NodeGoat — counts as-of v0.48.0. ~30 samples per family minimum for calibrated emit.",
4
+ "_caveat": "This is a SEED corpus, not a held-out test set. The PRD G1 target (Brier ≤ 0.10) requires a separate held-out labeled set; that work is queued for Phase 5 finalization. Calibrated values shipped now are honest empirical TP rates from this seed, with their Wilson 95% CI and N visible so consumers can judge.",
5
+ "families": {
6
+ "sql-injection": { "tp": 41, "fp": 3 },
7
+ "command-injection": { "tp": 38, "fp": 2 },
8
+ "xss": { "tp": 36, "fp": 6 },
9
+ "path-traversal": { "tp": 22, "fp": 4 },
10
+ "ssrf": { "tp": 18, "fp": 6 },
11
+ "code-injection": { "tp": 14, "fp": 1 },
12
+ "csrf": { "tp": 20, "fp": 4 },
13
+ "open-redirect": { "tp": 12, "fp": 2 },
14
+ "xxe": { "tp": 11, "fp": 1 },
15
+ "insecure-deserialization": { "tp": 9, "fp": 1 },
16
+ "weak-crypto": { "tp": 18, "fp": 0 },
17
+ "weak-rng": { "tp": 14, "fp": 1 },
18
+ "hardcoded-secret": { "tp": 22, "fp": 7 },
19
+ "host-header": { "tp": 6, "fp": 1 },
20
+ "mass-assignment": { "tp": 5, "fp": 0 },
21
+ "idor": { "tp": 9, "fp": 5 },
22
+ "jndi-injection": { "tp": 8, "fp": 0 },
23
+ "insecure-http": { "tp": 7, "fp": 1 },
24
+ "log-injection": { "tp": 6, "fp": 2 },
25
+ "vulnerable-dep": { "tp": 26, "fp": 1 }
26
+ }
27
+ }