patchi 0.7.5__py3-none-any.whl

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 (496) hide show
  1. patchi/__init__.py +1 -0
  2. patchi/__main__.py +13 -0
  3. patchi/cli/__init__.py +0 -0
  4. patchi/cli/command_families.py +235 -0
  5. patchi/cli/commands/__init__.py +0 -0
  6. patchi/cli/commands/access_cmd.py +65 -0
  7. patchi/cli/commands/agent_stats_cmd.py +273 -0
  8. patchi/cli/commands/agents_cmd.py +187 -0
  9. patchi/cli/commands/ai_cmd.py +308 -0
  10. patchi/cli/commands/assure_cmd.py +538 -0
  11. patchi/cli/commands/audit_cmd.py +286 -0
  12. patchi/cli/commands/auto_cmd.py +110 -0
  13. patchi/cli/commands/blame_cmd.py +53 -0
  14. patchi/cli/commands/chains_cmd.py +327 -0
  15. patchi/cli/commands/charter_cmd.py +173 -0
  16. patchi/cli/commands/chat_cmd.py +597 -0
  17. patchi/cli/commands/check_cmd.py +232 -0
  18. patchi/cli/commands/cleanup_cmd.py +328 -0
  19. patchi/cli/commands/cockpit_cmd.py +208 -0
  20. patchi/cli/commands/commands_cmd.py +160 -0
  21. patchi/cli/commands/cross_repo_cmd.py +113 -0
  22. patchi/cli/commands/deps_cmd.py +110 -0
  23. patchi/cli/commands/dev_check_cmd.py +342 -0
  24. patchi/cli/commands/dev_cmd.py +445 -0
  25. patchi/cli/commands/doctor_cmd.py +457 -0
  26. patchi/cli/commands/findings_cmd.py +187 -0
  27. patchi/cli/commands/fix_cmd.py +648 -0
  28. patchi/cli/commands/fix_review_cmd.py +273 -0
  29. patchi/cli/commands/heatmap_cmd.py +62 -0
  30. patchi/cli/commands/help_cmd.py +159 -0
  31. patchi/cli/commands/hosted_cmd.py +980 -0
  32. patchi/cli/commands/init.py +456 -0
  33. patchi/cli/commands/key_cmd.py +571 -0
  34. patchi/cli/commands/learn_cmd.py +140 -0
  35. patchi/cli/commands/link_cmd.py +104 -0
  36. patchi/cli/commands/log_cmd.py +57 -0
  37. patchi/cli/commands/memory_cmd.py +215 -0
  38. patchi/cli/commands/model_cmd.py +207 -0
  39. patchi/cli/commands/notify_cmd.py +213 -0
  40. patchi/cli/commands/notify_prompts.py +29 -0
  41. patchi/cli/commands/patch_cmd.py +231 -0
  42. patchi/cli/commands/plan_cmd.py +128 -0
  43. patchi/cli/commands/plugins_cmd.py +307 -0
  44. patchi/cli/commands/queue_cmd.py +152 -0
  45. patchi/cli/commands/quick_cmd.py +159 -0
  46. patchi/cli/commands/ready_cmd.py +292 -0
  47. patchi/cli/commands/report_cmd.py +416 -0
  48. patchi/cli/commands/restrict_cmd.py +127 -0
  49. patchi/cli/commands/review_cmd.py +225 -0
  50. patchi/cli/commands/rules_cmd.py +233 -0
  51. patchi/cli/commands/scan_cmd.py +1908 -0
  52. patchi/cli/commands/settings_cmd.py +45 -0
  53. patchi/cli/commands/status_cmd.py +679 -0
  54. patchi/cli/commands/test_cmd.py +1052 -0
  55. patchi/cli/commands/trend_cmd.py +296 -0
  56. patchi/cli/commands/undo_cmd.py +235 -0
  57. patchi/cli/commands/update_cmd.py +345 -0
  58. patchi/cli/commands/verify_cmd.py +56 -0
  59. patchi/cli/commands/vr_cmd.py +440 -0
  60. patchi/cli/commands/watch_cmd.py +304 -0
  61. patchi/cli/commands/web_cmd.py +114 -0
  62. patchi/cli/console.py +101 -0
  63. patchi/cli/display/__init__.py +1 -0
  64. patchi/cli/display/live_audit.py +269 -0
  65. patchi/cli/display/live_progress.py +169 -0
  66. patchi/cli/framework.py +241 -0
  67. patchi/cli/logo.py +128 -0
  68. patchi/cli/main.py +222 -0
  69. patchi/cli/registry.py +1171 -0
  70. patchi/cli/style.py +68 -0
  71. patchi/cli/themes.py +187 -0
  72. patchi/cli/ux.py +444 -0
  73. patchi/core/__init__.py +0 -0
  74. patchi/core/agents/__init__.py +0 -0
  75. patchi/core/agents/api_fuzzer_agent.py +141 -0
  76. patchi/core/agents/attack_agent.py +242 -0
  77. patchi/core/agents/base.py +1012 -0
  78. patchi/core/agents/bug_predictor_agent.py +58 -0
  79. patchi/core/agents/build_tool_validator.py +150 -0
  80. patchi/core/agents/cache.py +192 -0
  81. patchi/core/agents/chaos_agent.py +133 -0
  82. patchi/core/agents/cicd_generator.py +240 -0
  83. patchi/core/agents/comment_scanner.py +280 -0
  84. patchi/core/agents/console_logging_agent.py +156 -0
  85. patchi/core/agents/coordinator.py +858 -0
  86. patchi/core/agents/core_scanner.py +269 -0
  87. patchi/core/agents/coverage_prioritizer.py +181 -0
  88. patchi/core/agents/dead_code_hygiene.py +296 -0
  89. patchi/core/agents/dead_code_scanner.py +692 -0
  90. patchi/core/agents/dependency_scanner.py +725 -0
  91. patchi/core/agents/doc_claim_agent.py +573 -0
  92. patchi/core/agents/duplicate_scanner.py +568 -0
  93. patchi/core/agents/env_scanner.py +410 -0
  94. patchi/core/agents/feature_flag_agent.py +89 -0
  95. patchi/core/agents/frontend_framework_agent.py +133 -0
  96. patchi/core/agents/goroutine_agent.py +82 -0
  97. patchi/core/agents/governor.py +1972 -0
  98. patchi/core/agents/i18n_agent.py +95 -0
  99. patchi/core/agents/license_compliance.py +247 -0
  100. patchi/core/agents/linking_agent.py +251 -0
  101. patchi/core/agents/memory_profiler_agent.py +102 -0
  102. patchi/core/agents/modernization_agent.py +84 -0
  103. patchi/core/agents/mutation_agent.py +174 -0
  104. patchi/core/agents/nl_query_agent.py +74 -0
  105. patchi/core/agents/promise_rejection_agent.py +100 -0
  106. patchi/core/agents/refactoring_agent.py +286 -0
  107. patchi/core/agents/resource_leak_agent.py +91 -0
  108. patchi/core/agents/route_graph_scanner.py +951 -0
  109. patchi/core/agents/run_agent.py +106 -0
  110. patchi/core/agents/rust_unwrap_agent.py +100 -0
  111. patchi/core/agents/sbom_generator.py +284 -0
  112. patchi/core/agents/scanners.py +67 -0
  113. patchi/core/agents/side/__init__.py +1 -0
  114. patchi/core/agents/side/build_agent.py +65 -0
  115. patchi/core/agents/side/format_agent.py +50 -0
  116. patchi/core/agents/side/install_agent.py +63 -0
  117. patchi/core/agents/side_file_scanner.py +549 -0
  118. patchi/core/agents/snapshot_drift_detector.py +146 -0
  119. patchi/core/agents/spa_route_inventory.py +203 -0
  120. patchi/core/agents/test_scanner.py +442 -0
  121. patchi/core/agents/tool_health.py +109 -0
  122. patchi/core/agents/tool_runner.py +162 -0
  123. patchi/core/agents/type_scanner.py +168 -0
  124. patchi/core/agents/ui_scanner.py +558 -0
  125. patchi/core/ai/__init__.py +20 -0
  126. patchi/core/ai/agent_audit.py +153 -0
  127. patchi/core/ai/agent_profiler.py +277 -0
  128. patchi/core/ai/client.py +499 -0
  129. patchi/core/ai/cost_tracker.py +107 -0
  130. patchi/core/ai/model_router.py +318 -0
  131. patchi/core/ai/orchestrator.py +625 -0
  132. patchi/core/ai/prompts.py +681 -0
  133. patchi/core/ai/smart.py +302 -0
  134. patchi/core/ai/tool_executor.py +24 -0
  135. patchi/core/ai/tools/__init__.py +10 -0
  136. patchi/core/ai/tools/executor.py +621 -0
  137. patchi/core/ai/tools/realize.py +1160 -0
  138. patchi/core/ai/tools/registry.py +1577 -0
  139. patchi/core/assurance/__init__.py +33 -0
  140. patchi/core/assurance/builder.py +211 -0
  141. patchi/core/assurance/graph.py +342 -0
  142. patchi/core/assurance/invariants.py +168 -0
  143. patchi/core/atomic.py +61 -0
  144. patchi/core/attackers/__init__.py +17 -0
  145. patchi/core/attackers/api_abuse.py +89 -0
  146. patchi/core/attackers/auth_bypass.py +73 -0
  147. patchi/core/attackers/base.py +91 -0
  148. patchi/core/attackers/business_logic.py +88 -0
  149. patchi/core/attackers/planner.py +108 -0
  150. patchi/core/attackers/priv_escalation.py +88 -0
  151. patchi/core/attackers/recon.py +87 -0
  152. patchi/core/brain/__init__.py +0 -0
  153. patchi/core/brain/ast_utils/__init__.py +81 -0
  154. patchi/core/brain/ast_utils/assignments.py +219 -0
  155. patchi/core/brain/ast_utils/calls.py +139 -0
  156. patchi/core/brain/ast_utils/config.py +132 -0
  157. patchi/core/brain/ast_utils/control_flow.py +74 -0
  158. patchi/core/brain/ast_utils/dead_symbols.py +205 -0
  159. patchi/core/brain/ast_utils/decorators.py +108 -0
  160. patchi/core/brain/ast_utils/helpers.py +144 -0
  161. patchi/core/brain/ast_utils/imports.py +169 -0
  162. patchi/core/brain/ast_utils/scan.py +143 -0
  163. patchi/core/brain/ast_utils/taint.py +310 -0
  164. patchi/core/brain/audit.py +379 -0
  165. patchi/core/brain/baseline.py +195 -0
  166. patchi/core/brain/blast_radius.py +312 -0
  167. patchi/core/brain/body_tags.py +144 -0
  168. patchi/core/brain/brain.py +1161 -0
  169. patchi/core/brain/brain_context.py +393 -0
  170. patchi/core/brain/brain_watcher.py +340 -0
  171. patchi/core/brain/charter.py +473 -0
  172. patchi/core/brain/classifier.py +214 -0
  173. patchi/core/brain/cockpit.py +350 -0
  174. patchi/core/brain/code_query.py +710 -0
  175. patchi/core/brain/contract.py +889 -0
  176. patchi/core/brain/contract_diff.py +155 -0
  177. patchi/core/brain/council.py +800 -0
  178. patchi/core/brain/cross_repo.py +104 -0
  179. patchi/core/brain/dead_code_tools.py +157 -0
  180. patchi/core/brain/doc_validator.py +197 -0
  181. patchi/core/brain/domain_activator.py +2183 -0
  182. patchi/core/brain/enriched_context.py +220 -0
  183. patchi/core/brain/file_corpus.py +220 -0
  184. patchi/core/brain/framework.py +953 -0
  185. patchi/core/brain/freshness.py +273 -0
  186. patchi/core/brain/git_aware.py +206 -0
  187. patchi/core/brain/heatmap.py +104 -0
  188. patchi/core/brain/ignore_parser.py +153 -0
  189. patchi/core/brain/import_graph.py +486 -0
  190. patchi/core/brain/languages.py +397 -0
  191. patchi/core/brain/layered_brain.py +419 -0
  192. patchi/core/brain/learning.py +229 -0
  193. patchi/core/brain/orphaned_endpoints.py +313 -0
  194. patchi/core/brain/personas/__init__.py +648 -0
  195. patchi/core/brain/personas/bad_user.py +165 -0
  196. patchi/core/brain/personas/base.py +349 -0
  197. patchi/core/brain/proactive.py +807 -0
  198. patchi/core/brain/project_context.py +121 -0
  199. patchi/core/brain/project_reader.py +521 -0
  200. patchi/core/brain/reasoning.py +269 -0
  201. patchi/core/brain/route_detector/__init__.py +15 -0
  202. patchi/core/brain/route_detector/base.py +31 -0
  203. patchi/core/brain/route_detector/csharp.py +209 -0
  204. patchi/core/brain/route_detector/file_based.py +81 -0
  205. patchi/core/brain/route_detector/go.py +152 -0
  206. patchi/core/brain/route_detector/java.py +195 -0
  207. patchi/core/brain/route_detector/javascript.py +197 -0
  208. patchi/core/brain/route_detector/php.py +158 -0
  209. patchi/core/brain/route_detector/python.py +193 -0
  210. patchi/core/brain/route_detector/registry.py +25 -0
  211. patchi/core/brain/route_detector/ruby.py +134 -0
  212. patchi/core/brain/route_detector/rust.py +216 -0
  213. patchi/core/brain/route_detector/swift.py +118 -0
  214. patchi/core/brain/route_mapper.py +265 -0
  215. patchi/core/brain/scanner.py +2136 -0
  216. patchi/core/brain/secrets.py +280 -0
  217. patchi/core/brain/symbol_graph.py +1955 -0
  218. patchi/core/brain/team.py +28 -0
  219. patchi/core/brain/trace_log.py +132 -0
  220. patchi/core/brain/type_checker/__init__.py +65 -0
  221. patchi/core/brain/type_checker/base.py +60 -0
  222. patchi/core/brain/type_checker/csharp.py +59 -0
  223. patchi/core/brain/type_checker/dart.py +59 -0
  224. patchi/core/brain/type_checker/go.py +81 -0
  225. patchi/core/brain/type_checker/java.py +75 -0
  226. patchi/core/brain/type_checker/kotlin.py +75 -0
  227. patchi/core/brain/type_checker/php.py +88 -0
  228. patchi/core/brain/type_checker/python.py +87 -0
  229. patchi/core/brain/type_checker/rust.py +81 -0
  230. patchi/core/brain/type_checker/swift.py +59 -0
  231. patchi/core/brain/type_checker/typescript.py +298 -0
  232. patchi/core/brain/understander.py +130 -0
  233. patchi/core/brain/verify.py +379 -0
  234. patchi/core/campaigns/__init__.py +14 -0
  235. patchi/core/campaigns/base.py +62 -0
  236. patchi/core/campaigns/data_flow_abuse.py +153 -0
  237. patchi/core/campaigns/orchestrator.py +140 -0
  238. patchi/core/campaigns/state_transitions.py +178 -0
  239. patchi/core/ci_bundle.py +104 -0
  240. patchi/core/config.py +311 -0
  241. patchi/core/constants.py +245 -0
  242. patchi/core/debug/__init__.py +25 -0
  243. patchi/core/debug/adapters/__init__.py +11 -0
  244. patchi/core/debug/adapters/codelldb.py +379 -0
  245. patchi/core/debug/adapters/node.py +222 -0
  246. patchi/core/debug/adapters/powershell.py +210 -0
  247. patchi/core/debug/adapters/python.py +244 -0
  248. patchi/core/debug/capture.py +210 -0
  249. patchi/core/debug/dap_client.py +268 -0
  250. patchi/core/detector/__init__.py +0 -0
  251. patchi/core/detector/audit.py +194 -0
  252. patchi/core/detector/bus.py +226 -0
  253. patchi/core/detector/dispatcher.py +275 -0
  254. patchi/core/detector/event.py +254 -0
  255. patchi/core/detector/sigma_engine.py +378 -0
  256. patchi/core/detector/triage.py +289 -0
  257. patchi/core/export/sarif.py +113 -0
  258. patchi/core/fix/__init__.py +17 -0
  259. patchi/core/fix/applier.py +574 -0
  260. patchi/core/fix/base.py +510 -0
  261. patchi/core/fix/code_fixer.py +169 -0
  262. patchi/core/fix/dead_code_remover.py +131 -0
  263. patchi/core/fix/fix_agents.py +775 -0
  264. patchi/core/fix/patch.py +444 -0
  265. patchi/core/fix/risk_gate.py +383 -0
  266. patchi/core/fix/security_fixer.py +93 -0
  267. patchi/core/fix/verify_loop.py +204 -0
  268. patchi/core/fuzz/__init__.py +8 -0
  269. patchi/core/fuzz/corpus.py +137 -0
  270. patchi/core/fuzz/input_fuzzer.py +256 -0
  271. patchi/core/fuzz/sequence_fuzzer.py +162 -0
  272. patchi/core/fuzz/state_fuzzer.py +236 -0
  273. patchi/core/health.py +407 -0
  274. patchi/core/hosted/__init__.py +1 -0
  275. patchi/core/hosted/anomaly.py +298 -0
  276. patchi/core/hosted/audit_log.py +102 -0
  277. patchi/core/hosted/compliance_report.py +148 -0
  278. patchi/core/hosted/ip_reputation.py +169 -0
  279. patchi/core/hosted/log_parsers.py +251 -0
  280. patchi/core/hosted/tokens.py +127 -0
  281. patchi/core/hosted/watchlist.py +164 -0
  282. patchi/core/hosted/webhooks.py +156 -0
  283. patchi/core/memory.py +437 -0
  284. patchi/core/notifications/__init__.py +18 -0
  285. patchi/core/notifications/channels.py +192 -0
  286. patchi/core/notifications/digest.py +141 -0
  287. patchi/core/notifications/escalation.py +158 -0
  288. patchi/core/notifications/notifier.py +182 -0
  289. patchi/core/notifications/quiet_hours.py +69 -0
  290. patchi/core/plugins/__init__.py +48 -0
  291. patchi/core/plugins/analyzer.py +326 -0
  292. patchi/core/plugins/analyzers/__init__.py +6 -0
  293. patchi/core/plugins/analyzers/dead_code_analyzer.py +120 -0
  294. patchi/core/plugins/analyzers/secret_scanner.py +158 -0
  295. patchi/core/plugins/analyzers/todo_scanner.py +115 -0
  296. patchi/core/plugins/registry.py +335 -0
  297. patchi/core/queue.py +375 -0
  298. patchi/core/reliability/__init__.py +13 -0
  299. patchi/core/reliability/chaos.py +163 -0
  300. patchi/core/reliability/fault_injector.py +172 -0
  301. patchi/core/reliability/idempotency.py +128 -0
  302. patchi/core/reliability/recovery.py +147 -0
  303. patchi/core/runtime/__init__.py +5 -0
  304. patchi/core/runtime/tracer.py +230 -0
  305. patchi/core/scan_bus.py +156 -0
  306. patchi/core/security/__init__.py +1 -0
  307. patchi/core/security/ai_validator.py +462 -0
  308. patchi/core/security/app_mapper.py +216 -0
  309. patchi/core/security/app_profile.py +464 -0
  310. patchi/core/security/attack_feedback.py +141 -0
  311. patchi/core/security/attack_scenarios/__init__.py +1 -0
  312. patchi/core/security/attack_tree.py +176 -0
  313. patchi/core/security/auth_audit_agent.py +344 -0
  314. patchi/core/security/authz_agent.py +602 -0
  315. patchi/core/security/auto_fix_proactive.py +620 -0
  316. patchi/core/security/auto_fixer.py +660 -0
  317. patchi/core/security/auto_ticket.py +53 -0
  318. patchi/core/security/bandit_agent.py +152 -0
  319. patchi/core/security/blast_radius.py +226 -0
  320. patchi/core/security/browser_tester.py +333 -0
  321. patchi/core/security/business_logic_agent.py +263 -0
  322. patchi/core/security/catch_block_auditor.py +395 -0
  323. patchi/core/security/cdn_cache_agent.py +357 -0
  324. patchi/core/security/chain_analyzer.py +321 -0
  325. patchi/core/security/chain_to_assurance.py +168 -0
  326. patchi/core/security/charter.py +510 -0
  327. patchi/core/security/cloud_waf_detector.py +295 -0
  328. patchi/core/security/codeql_agent.py +233 -0
  329. patchi/core/security/compliance_agent.py +348 -0
  330. patchi/core/security/confidence_gate.py +423 -0
  331. patchi/core/security/container_scanner.py +282 -0
  332. patchi/core/security/crypto_agent.py +378 -0
  333. patchi/core/security/cve_monitor.py +262 -0
  334. patchi/core/security/dast_agent.py +865 -0
  335. patchi/core/security/defectdojo.py +238 -0
  336. patchi/core/security/defenders/__init__.py +75 -0
  337. patchi/core/security/defenders/auth_middleware.py +115 -0
  338. patchi/core/security/defenders/base.py +97 -0
  339. patchi/core/security/defenders/block_ip.py +71 -0
  340. patchi/core/security/defenders/block_ws_origin.py +36 -0
  341. patchi/core/security/defenders/crypto_fix.py +118 -0
  342. patchi/core/security/defenders/enforce_rate_limit.py +108 -0
  343. patchi/core/security/defenders/fix_code.py +93 -0
  344. patchi/core/security/defenders/invalidate_session.py +40 -0
  345. patchi/core/security/defenders/patch_config.py +70 -0
  346. patchi/core/security/defenders/rotate_secret.py +54 -0
  347. patchi/core/security/defenders/suspend_account.py +41 -0
  348. patchi/core/security/defenders/update_dependency.py +160 -0
  349. patchi/core/security/defense_layer.py +217 -0
  350. patchi/core/security/dependency_vulnerability_agent.py +616 -0
  351. patchi/core/security/detection_pipeline.py +289 -0
  352. patchi/core/security/dns_security_agent.py +497 -0
  353. patchi/core/security/domain_activator_v2.py +913 -0
  354. patchi/core/security/domain_loader.py +1004 -0
  355. patchi/core/security/domains/__init__.py +0 -0
  356. patchi/core/security/email_authentication_agent.py +417 -0
  357. patchi/core/security/env_var_validator.py +304 -0
  358. patchi/core/security/evidence.py +100 -0
  359. patchi/core/security/falco_runtime_agent.py +387 -0
  360. patchi/core/security/fix-playbooks/__init__.py +0 -0
  361. patchi/core/security/gated_finding.py +85 -0
  362. patchi/core/security/git_diff_activator.py +367 -0
  363. patchi/core/security/governance.py +208 -0
  364. patchi/core/security/gradual_gate.py +34 -0
  365. patchi/core/security/history.py +315 -0
  366. patchi/core/security/iac_scanner.py +250 -0
  367. patchi/core/security/ignore_expiry.py +111 -0
  368. patchi/core/security/ignore_learner.py +416 -0
  369. patchi/core/security/injection_agent.py +319 -0
  370. patchi/core/security/insecure_randomness_agent.py +288 -0
  371. patchi/core/security/intent_analyzer.py +326 -0
  372. patchi/core/security/jwt_agent.py +453 -0
  373. patchi/core/security/kubernetes_agent.py +638 -0
  374. patchi/core/security/layer2_orchestrator.py +341 -0
  375. patchi/core/security/llm_security_agent.py +252 -0
  376. patchi/core/security/misconfig_agent.py +600 -0
  377. patchi/core/security/mobile_security_agent.py +474 -0
  378. patchi/core/security/network_agent.py +376 -0
  379. patchi/core/security/noise_filter.py +274 -0
  380. patchi/core/security/orchestrator.py +313 -0
  381. patchi/core/security/pattern_context.py +125 -0
  382. patchi/core/security/pattern_loader.py +101 -0
  383. patchi/core/security/pentest/__init__.py +4 -0
  384. patchi/core/security/pentest/base.py +58 -0
  385. patchi/core/security/pentest/dalfox_adapter.py +40 -0
  386. patchi/core/security/pentest/ffuf_adapter.py +48 -0
  387. patchi/core/security/pentest/nuclei_adapter.py +50 -0
  388. patchi/core/security/pentest/registry.py +80 -0
  389. patchi/core/security/pentest/shannon_adapter.py +107 -0
  390. patchi/core/security/pentest/sqlmap_adapter.py +56 -0
  391. patchi/core/security/pentest/zap_adapter.py +52 -0
  392. patchi/core/security/plan_auditor.py +276 -0
  393. patchi/core/security/policy_engine.py +272 -0
  394. patchi/core/security/prechecks.py +172 -0
  395. patchi/core/security/precommit_hook.py +247 -0
  396. patchi/core/security/privacy_agent.py +361 -0
  397. patchi/core/security/push_notification_agent.py +417 -0
  398. patchi/core/security/pysa_agent.py +143 -0
  399. patchi/core/security/reasoning.py +442 -0
  400. patchi/core/security/red_team_agent.py +184 -0
  401. patchi/core/security/red_team_engine.py +1142 -0
  402. patchi/core/security/remediation.py +360 -0
  403. patchi/core/security/request_interceptor.py +294 -0
  404. patchi/core/security/runtime_validator.py +245 -0
  405. patchi/core/security/saml_sso_agent.py +401 -0
  406. patchi/core/security/sast_agent.py +213 -0
  407. patchi/core/security/sast_gate.py +95 -0
  408. patchi/core/security/scheduler.py +287 -0
  409. patchi/core/security/secrets_runtime_agent.py +476 -0
  410. patchi/core/security/security_agents.py +175 -0
  411. patchi/core/security/security_config.py +551 -0
  412. patchi/core/security/security_probe.py +762 -0
  413. patchi/core/security/security_taint.py +1005 -0
  414. patchi/core/security/sensitive_data_agent.py +271 -0
  415. patchi/core/security/service_mesh_agent.py +412 -0
  416. patchi/core/security/session_management_agent.py +245 -0
  417. patchi/core/security/ssrf_agent.py +422 -0
  418. patchi/core/security/supply_chain.py +449 -0
  419. patchi/core/security/threat_model_generator.py +378 -0
  420. patchi/core/security/threat_model_updater.py +196 -0
  421. patchi/core/security/tool_adapters.py +135 -0
  422. patchi/core/security/tool_verify.py +173 -0
  423. patchi/core/security/websocket_security_agent.py +188 -0
  424. patchi/core/snapshot.py +242 -0
  425. patchi/core/tenant.py +386 -0
  426. patchi/core/testing/__init__.py +17 -0
  427. patchi/core/testing/_browser.py +147 -0
  428. patchi/core/testing/ads_agent.py +141 -0
  429. patchi/core/testing/api_contract_agent.py +450 -0
  430. patchi/core/testing/app_discovery_agent.py +641 -0
  431. patchi/core/testing/app_launcher.py +184 -0
  432. patchi/core/testing/browser_test_agent.py +516 -0
  433. patchi/core/testing/e2e_flow_agent.py +354 -0
  434. patchi/core/testing/flake_detector_agent.py +292 -0
  435. patchi/core/testing/gate.py +31 -0
  436. patchi/core/testing/live_test_runner.py +304 -0
  437. patchi/core/testing/live_v2/__init__.py +1 -0
  438. patchi/core/testing/live_v2/browser_pool.py +349 -0
  439. patchi/core/testing/live_v2/browser_test_runner.py +313 -0
  440. patchi/core/testing/live_v2/runner.py +626 -0
  441. patchi/core/testing/live_v2/screenshot_manager.py +474 -0
  442. patchi/core/testing/live_v2/stress_orchestrator.py +523 -0
  443. patchi/core/testing/live_v2/video_recorder.py +303 -0
  444. patchi/core/testing/regression_agent.py +302 -0
  445. patchi/core/testing/security_test_agent.py +253 -0
  446. patchi/core/testing/stress_test_agent.py +203 -0
  447. patchi/core/testing/test_agents.py +50 -0
  448. patchi/core/testing/test_config.py +194 -0
  449. patchi/core/testing/ui_accessibility_agent.py +324 -0
  450. patchi/core/testing/ui_button_agent.py +294 -0
  451. patchi/core/testing/ui_layout_agent.py +330 -0
  452. patchi/core/testing/unit_test_agent.py +1149 -0
  453. patchi/core/testing/visual_regression_agent.py +260 -0
  454. patchi/web/__init__.py +1 -0
  455. patchi/web/api/__init__.py +1 -0
  456. patchi/web/api/brain_map.py +114 -0
  457. patchi/web/api/charts.py +47 -0
  458. patchi/web/api/chat.py +224 -0
  459. patchi/web/api/cicd.py +576 -0
  460. patchi/web/api/dast_evidence.py +49 -0
  461. patchi/web/api/dev_check.py +320 -0
  462. patchi/web/api/fix.py +134 -0
  463. patchi/web/api/guard.py +65 -0
  464. patchi/web/api/hosted.py +128 -0
  465. patchi/web/api/hosted_v2.py +234 -0
  466. patchi/web/api/live_testing.py +699 -0
  467. patchi/web/api/scan.py +582 -0
  468. patchi/web/api/smart.py +111 -0
  469. patchi/web/api/tenant.py +117 -0
  470. patchi/web/api/tools.py +85 -0
  471. patchi/web/api_legacy.py +2401 -0
  472. patchi/web/app.py +310 -0
  473. patchi/web/events.py +483 -0
  474. patchi/web/routes/__init__.py +1 -0
  475. patchi/web/routes/assurance.py +541 -0
  476. patchi/web/routes/brain.py +36 -0
  477. patchi/web/routes/charter.py +227 -0
  478. patchi/web/routes/chat.py +23 -0
  479. patchi/web/routes/dashboard.py +558 -0
  480. patchi/web/routes/dashboard_v2.py +402 -0
  481. patchi/web/routes/findings.py +330 -0
  482. patchi/web/routes/guard.py +35 -0
  483. patchi/web/routes/history.py +126 -0
  484. patchi/web/routes/landing.py +52 -0
  485. patchi/web/routes/live_testing.py +24 -0
  486. patchi/web/routes/review.py +57 -0
  487. patchi/web/routes/self_improvement.py +110 -0
  488. patchi/web/routes/settings.py +49 -0
  489. patchi/web/routes/tokens.py +29 -0
  490. patchi/web/spawn.py +139 -0
  491. patchi/web/ws.py +141 -0
  492. patchi-0.7.5.dist-info/METADATA +315 -0
  493. patchi-0.7.5.dist-info/RECORD +496 -0
  494. patchi-0.7.5.dist-info/WHEEL +5 -0
  495. patchi-0.7.5.dist-info/licenses/LICENSE +60 -0
  496. patchi-0.7.5.dist-info/top_level.txt +1 -0
@@ -0,0 +1,378 @@
1
+ """
2
+ Threat Model Generator — Runtime generator from YAML attack scenarios.
3
+
4
+ Reads attack scenario YAML files and generates executable threat models
5
+ for the current project based on:
6
+ - Detected frameworks and patterns
7
+ - Route analysis
8
+ - Authentication mechanisms
9
+ - Data flow patterns
10
+
11
+ Output: ThreatModel with prioritized attack scenarios for the project.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import logging
17
+ from dataclasses import dataclass, field
18
+ from pathlib import Path
19
+
20
+ _log = logging.getLogger("patchi.security.threat_model")
21
+
22
+
23
+ @dataclass
24
+ class AttackScenario:
25
+ """A single attack scenario from YAML."""
26
+
27
+ id: str
28
+ name: str
29
+ category: str
30
+ severity: str
31
+ cwe: str
32
+ owasp: str
33
+ description: str
34
+ prerequisites: list[str] = field(default_factory=list)
35
+ attack_steps: list[dict] = field(default_factory=list)
36
+ detection_signatures: list[str] = field(default_factory=list)
37
+ remediation_playbook: str = ""
38
+ tags: list[str] = field(default_factory=list)
39
+ applicable: bool = False
40
+ relevance_score: float = 0.0
41
+
42
+
43
+ @dataclass
44
+ class ThreatModel:
45
+ """Generated threat model for the project."""
46
+
47
+ project_root: str
48
+ total_scenarios: int = 0
49
+ applicable_scenarios: int = 0
50
+ scenarios: list[AttackScenario] = field(default_factory=list)
51
+ by_severity: dict[str, int] = field(default_factory=dict)
52
+ by_category: dict[str, int] = field(default_factory=dict)
53
+ recommendations: list[str] = field(default_factory=list)
54
+
55
+ def to_dict(self) -> dict:
56
+ return {
57
+ "project_root": self.project_root,
58
+ "total_scenarios": self.total_scenarios,
59
+ "applicable_scenarios": self.applicable_scenarios,
60
+ "by_severity": self.by_severity,
61
+ "by_category": self.by_category,
62
+ "scenarios": [
63
+ {
64
+ "id": s.id,
65
+ "name": s.name,
66
+ "category": s.category,
67
+ "severity": s.severity,
68
+ "cwe": s.cwe,
69
+ "description": s.description,
70
+ "applicable": s.applicable,
71
+ "relevance_score": s.relevance_score,
72
+ "tags": s.tags,
73
+ }
74
+ for s in self.scenarios
75
+ ],
76
+ "recommendations": self.recommendations,
77
+ }
78
+
79
+
80
+ # ── Category Detection Rules ─────────────────────────────────────────────────
81
+
82
+ # Maps YAML scenario categories to code signals that indicate applicability
83
+ _CATEGORY_SIGNALS: dict[str, list[tuple[str, float]]] = {
84
+ "authentication": [
85
+ ("login", 0.8),
86
+ ("auth", 0.9),
87
+ ("session", 0.7),
88
+ ("jwt", 0.9),
89
+ ("cookie", 0.6),
90
+ ("password", 0.8),
91
+ ("bcrypt", 0.8),
92
+ ("argon2", 0.8),
93
+ ],
94
+ "authorization": [
95
+ ("permission", 0.8),
96
+ ("role", 0.7),
97
+ ("admin", 0.8),
98
+ ("access_control", 0.9),
99
+ ("guard", 0.6),
100
+ ("protect", 0.5),
101
+ ],
102
+ "injection": [
103
+ ("execute", 0.7),
104
+ ("query", 0.6),
105
+ ("raw_sql", 0.9),
106
+ ("cursor", 0.7),
107
+ ("database", 0.5),
108
+ ("orm", 0.4),
109
+ ("subprocess", 0.8),
110
+ ("shell", 0.8),
111
+ ("eval", 0.9),
112
+ ],
113
+ "xss": [
114
+ ("render", 0.5),
115
+ ("template", 0.5),
116
+ ("innerHTML", 0.9),
117
+ ("dangerouslySetInnerHTML", 0.9),
118
+ ("markup", 0.6),
119
+ ("html", 0.4),
120
+ ("response", 0.4),
121
+ ],
122
+ "csrf": [
123
+ ("form", 0.5),
124
+ ("post", 0.4),
125
+ ("state_change", 0.8),
126
+ ("mutate", 0.7),
127
+ ("PUT", 0.6),
128
+ ("DELETE", 0.6),
129
+ ],
130
+ "secrets": [
131
+ ("api_key", 0.9),
132
+ ("secret", 0.8),
133
+ ("token", 0.7),
134
+ ("password", 0.7),
135
+ ("credential", 0.8),
136
+ ("env", 0.5),
137
+ ],
138
+ "dependencies": [
139
+ ("requirements", 0.7),
140
+ ("package.json", 0.7),
141
+ ("Cargo.toml", 0.7),
142
+ ("go.mod", 0.7),
143
+ ("Gemfile", 0.7),
144
+ ("composer.json", 0.7),
145
+ ],
146
+ "configuration": [
147
+ ("config", 0.5),
148
+ ("settings", 0.5),
149
+ ("debug", 0.8),
150
+ ("verbose", 0.6),
151
+ ("cors", 0.8),
152
+ ("ssl", 0.7),
153
+ ],
154
+ "crypto": [
155
+ ("encrypt", 0.8),
156
+ ("decrypt", 0.8),
157
+ ("hash", 0.7),
158
+ ("md5", 0.9),
159
+ ("sha1", 0.8),
160
+ ("aes", 0.7),
161
+ ("rsa", 0.7),
162
+ ],
163
+ "network": [
164
+ ("http", 0.5),
165
+ ("request", 0.4),
166
+ ("fetch", 0.4),
167
+ ("axios", 0.5),
168
+ ("websocket", 0.7),
169
+ ("ssrf", 0.9),
170
+ ],
171
+ }
172
+
173
+
174
+ class ThreatModelGenerator:
175
+ """Generates threat models from YAML scenarios + project context."""
176
+
177
+ def __init__(self, root: Path):
178
+ self.root = root
179
+ self._scenarios_dir = Path(__file__).parent / "attack_scenarios"
180
+
181
+ def generate(self) -> ThreatModel:
182
+ """Generate a threat model for the current project."""
183
+ model = ThreatModel(project_root=str(self.root))
184
+
185
+ # Load all YAML scenarios
186
+ all_scenarios = self._load_scenarios()
187
+ model.total_scenarios = len(all_scenarios)
188
+
189
+ # Get project context
190
+ project_signals = self._collect_project_signals()
191
+
192
+ # Score and filter scenarios
193
+ for scenario in all_scenarios:
194
+ score = self._score_scenario(scenario, project_signals)
195
+ scenario.relevance_score = score
196
+ scenario.applicable = score > 0.1
197
+ model.scenarios.append(scenario)
198
+
199
+ # Sort by relevance
200
+ model.scenarios.sort(key=lambda s: (-s.relevance_score, s.severity))
201
+
202
+ # Compute summaries
203
+ model.applicable_scenarios = sum(1 for s in model.scenarios if s.applicable)
204
+ for s in model.scenarios:
205
+ if s.applicable:
206
+ model.by_severity[s.severity] = model.by_severity.get(s.severity, 0) + 1
207
+ model.by_category[s.category] = model.by_category.get(s.category, 0) + 1
208
+
209
+ # Generate recommendations
210
+ model.recommendations = self._generate_recommendations(model)
211
+
212
+ _log.info(
213
+ "Threat model: %d/%d scenarios applicable",
214
+ model.applicable_scenarios,
215
+ model.total_scenarios,
216
+ )
217
+ return model
218
+
219
+ def _load_scenarios(self) -> list[AttackScenario]:
220
+ """Load all attack scenarios from YAML files."""
221
+ scenarios = []
222
+ if not self._scenarios_dir.exists():
223
+ return scenarios
224
+
225
+ for yaml_file in self._scenarios_dir.glob("*.yaml"):
226
+ try:
227
+ import yaml
228
+
229
+ with open(yaml_file, encoding="utf-8") as f:
230
+ data = yaml.safe_load(f)
231
+ if not data or "scenarios" not in data:
232
+ continue
233
+ for s in data["scenarios"]:
234
+ scenarios.append(
235
+ AttackScenario(
236
+ id=s.get("id", "unknown"),
237
+ name=s.get("name", "Unknown"),
238
+ category=s.get("category", "unknown"),
239
+ severity=s.get("severity", "medium"),
240
+ cwe=s.get("cwe", ""),
241
+ owasp=s.get("owasp", ""),
242
+ description=s.get("description", ""),
243
+ prerequisites=s.get("prerequisites", []),
244
+ attack_steps=s.get("attack_steps", []),
245
+ detection_signatures=s.get("detection_signatures", []),
246
+ remediation_playbook=s.get("remediation_playbook", ""),
247
+ tags=s.get("tags", []),
248
+ )
249
+ )
250
+ except Exception as e:
251
+ _log.warning("Failed to load %s: %s", yaml_file.name, e)
252
+ return scenarios
253
+
254
+ def _collect_project_signals(self) -> dict[str, float]:
255
+ """Collect signals from the project to determine applicability."""
256
+ signals: dict[str, float] = {}
257
+
258
+ # Scan source files for keywords
259
+ source_files = []
260
+ for ext in ("*.py", "*.js", "*.ts", "*.jsx", "*.tsx", "*.go", "*.java", "*.rb"):
261
+ source_files.extend(self.root.rglob(ext))
262
+
263
+ # Limit to 500 files for performance
264
+ source_files = source_files[:500]
265
+
266
+ # Collect keyword frequency
267
+ keyword_counts: dict[str, int] = {}
268
+ for fp in source_files:
269
+ # Skip vendor/node_modules/venv
270
+ parts = fp.relative_to(self.root).parts
271
+ if any(
272
+ p.startswith(".") or p in ("node_modules", "__pycache__", ".venv", "venv")
273
+ for p in parts
274
+ ):
275
+ continue
276
+ try:
277
+ content = fp.read_text(encoding="utf-8", errors="ignore").lower()
278
+ for _category, kws in _CATEGORY_SIGNALS.items():
279
+ for kw, _ in kws:
280
+ if kw.lower() in content:
281
+ keyword_counts[kw] = keyword_counts.get(kw, 0) + 1
282
+ except Exception as _exc:
283
+ _log.warning('_collect_project_signals failed: %s', _exc)
284
+
285
+ # Convert counts to scores (0-1)
286
+ max_count = max(keyword_counts.values()) if keyword_counts else 1
287
+ for kw, count in keyword_counts.items():
288
+ signals[kw] = min(count / max(max_count, 1), 1.0)
289
+
290
+ # Check for dependency files
291
+ dep_files = [
292
+ "requirements.txt",
293
+ "package.json",
294
+ "Cargo.toml",
295
+ "go.mod",
296
+ "Gemfile",
297
+ "composer.json",
298
+ "Pipfile",
299
+ ]
300
+ for df in dep_files:
301
+ if (self.root / df).exists():
302
+ signals[df] = 1.0
303
+
304
+ # Check for config files
305
+ config_files = [
306
+ "config.py",
307
+ "settings.py",
308
+ ".env",
309
+ "config.yaml",
310
+ "config.json",
311
+ "application.properties",
312
+ ]
313
+ for cf in config_files:
314
+ if list(self.root.rglob(cf)):
315
+ signals[cf] = 1.0
316
+
317
+ return signals
318
+
319
+ def _score_scenario(self, scenario: AttackScenario, signals: dict[str, float]) -> float:
320
+ """Score a scenario based on project signals."""
321
+ category_signals = _CATEGORY_SIGNALS.get(scenario.category, [])
322
+ if not category_signals:
323
+ return 0.2 # Default low relevance for unknown categories
324
+
325
+ score = 0.0
326
+ for kw, weight in category_signals:
327
+ signal_value = signals.get(kw, 0.0)
328
+ score += signal_value * weight
329
+
330
+ # Normalize to 0-1
331
+ max_possible = sum(w for _, w in category_signals)
332
+ if max_possible > 0:
333
+ score = min(score / max_possible, 1.0)
334
+
335
+ # Boost for CWE tags matching project patterns
336
+ if scenario.cwe:
337
+ cwe_num = scenario.cwe.replace("CWE-", "")
338
+ # SQL injection CWEs get boost if project uses SQL
339
+ if cwe_num in ("89", "90") and signals.get("execute", 0) > 0.5:
340
+ score = min(score * 1.5, 1.0)
341
+ # XSS CWEs get boost if project renders HTML
342
+ if cwe_num in ("79", "80") and signals.get("render", 0) > 0.3:
343
+ score = min(score * 1.3, 1.0)
344
+
345
+ return score
346
+
347
+ def _generate_recommendations(self, model: ThreatModel) -> list[str]:
348
+ """Generate security recommendations based on the threat model."""
349
+ recs = []
350
+
351
+ # High-severity applicable scenarios
352
+ high_sev = [
353
+ s for s in model.scenarios if s.applicable and s.severity in ("critical", "high")
354
+ ]
355
+ if high_sev:
356
+ recs.append(
357
+ f"Address {len(high_sev)} high/critical scenarios: "
358
+ + ", ".join(s.name for s in high_sev[:3])
359
+ )
360
+
361
+ # Category gaps
362
+ if "secrets" in model.by_category:
363
+ recs.append("Review secret management — multiple secret-related scenarios apply")
364
+
365
+ if "injection" in model.by_category:
366
+ recs.append("Validate all user inputs — injection scenarios detected")
367
+
368
+ if "auth" in str(model.by_category):
369
+ recs.append("Audit authentication flows — auth scenarios are relevant")
370
+
371
+ # Dependency warnings
372
+ dep_scenarios = [
373
+ s for s in model.scenarios if s.category == "dependencies" and s.applicable
374
+ ]
375
+ if dep_scenarios:
376
+ recs.append("Run dependency vulnerability scan — project has dependency files")
377
+
378
+ return recs
@@ -0,0 +1,196 @@
1
+ """
2
+ Threat Model Updater — refines threat model from actual scan findings.
3
+
4
+ After a scan completes, this module:
5
+ 1. Boosts scenarios that match confirmed findings
6
+ 2. Adds new scenarios for finding types not in the YAML library
7
+ 3. Deprioritizes scenarios that produced no findings (false-alarm decay)
8
+ 4. Persists the updated model for the next scan cycle
9
+
10
+ The result is a threat model that adapts to the real risk profile of the
11
+ project rather than relying solely on static YAML definitions.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import json
17
+ import logging
18
+ from pathlib import Path
19
+ from typing import Any
20
+
21
+ from patchi.core.security.threat_model_generator import (
22
+ AttackScenario,
23
+ ThreatModel,
24
+ ThreatModelGenerator,
25
+ )
26
+
27
+ _log = logging.getLogger("patchi.security.threat_model_updater")
28
+
29
+ # How much to boost a scenario's score when a finding matches its category
30
+ _FINDING_BOOST = 0.15
31
+ # Maximum boosted score
32
+ _MAX_SCORE = 1.0
33
+ # Decay factor for scenarios with zero findings (per scan cycle)
34
+ _ZERO_FINDING_DECAY = 0.05
35
+
36
+
37
+ def update_threat_model(
38
+ root: Path,
39
+ findings: list[dict[str, Any]],
40
+ ) -> ThreatModel:
41
+ """Regenerate the threat model and adjust scores based on scan findings.
42
+
43
+ Returns the updated ThreatModel.
44
+ """
45
+ gen = ThreatModelGenerator(root)
46
+ model = gen.generate()
47
+
48
+ # Index findings by category
49
+ category_counts: dict[str, int] = {}
50
+ cwe_counts: dict[str, int] = {}
51
+ finding_types: set[str] = set()
52
+ for f in findings:
53
+ cat = _categorize_finding(f)
54
+ category_counts[cat] = category_counts.get(cat, 0) + 1
55
+ cwe = f.get("cwe", "")
56
+ if cwe:
57
+ cwe_counts[cwe] = cwe_counts.get(cwe, 0) + 1
58
+ finding_types.add(f.get("type", f.get("control_id", "")))
59
+
60
+ # Boost scenarios matching actual findings
61
+ for scenario in model.scenarios:
62
+ if not scenario.applicable:
63
+ continue
64
+ cat_count = category_counts.get(scenario.category, 0)
65
+ if cat_count > 0:
66
+ boost = min(cat_count * _FINDING_BOOST, _MAX_SCORE - scenario.relevance_score)
67
+ scenario.relevance_score = min(scenario.relevance_score + boost, _MAX_SCORE)
68
+ _log.debug(
69
+ "Boosted %s by %.2f (category=%s, findings=%d)",
70
+ scenario.id,
71
+ boost,
72
+ scenario.category,
73
+ cat_count,
74
+ )
75
+ else:
76
+ # Decay scenarios that produced zero findings
77
+ scenario.relevance_score = max(scenario.relevance_score - _ZERO_FINDING_DECAY, 0.0)
78
+
79
+ # Extra boost for CWE matches
80
+ if scenario.cwe and cwe_counts.get(scenario.cwe, 0) > 0:
81
+ scenario.relevance_score = min(scenario.relevance_score + 0.2, _MAX_SCORE)
82
+
83
+ # Re-sort by updated relevance
84
+ model.scenarios.sort(key=lambda s: (-s.relevance_score, s.severity))
85
+
86
+ # Recompute summaries
87
+ model.applicable_scenarios = sum(1 for s in model.scenarios if s.applicable)
88
+ model.by_severity.clear()
89
+ model.by_category.clear()
90
+ for s in model.scenarios:
91
+ if s.applicable:
92
+ model.by_severity[s.severity] = model.by_severity.get(s.severity, 0) + 1
93
+ model.by_category[s.category] = model.by_category.get(s.category, 0) + 1
94
+
95
+ # Add discovered scenarios for finding types not in the YAML library
96
+ _add_discovered_scenarios(model, finding_types, category_counts)
97
+
98
+ # Update recommendations
99
+ model.recommendations = _generate_updated_recommendations(model, category_counts)
100
+
101
+ # Persist
102
+ tm_path = root / ".patchi" / "threat_model.json"
103
+ tm_path.parent.mkdir(parents=True, exist_ok=True)
104
+ tm_path.write_text(json.dumps(model.to_dict(), indent=2), encoding="utf-8")
105
+ _log.info(
106
+ "Threat model updated: %d scenarios, %d applicable, %d categories",
107
+ model.total_scenarios,
108
+ model.applicable_scenarios,
109
+ len(model.by_category),
110
+ )
111
+ return model
112
+
113
+
114
+ def _categorize_finding(finding: dict[str, Any]) -> str:
115
+ """Map a finding to a threat model category."""
116
+ ftype = (finding.get("type", "") or finding.get("control_id", "")).lower()
117
+ categories = {
118
+ "authentication": ("auth", "login", "session", "password", "jwt", "cookie"),
119
+ "authorization": ("permission", "role", "admin", "access", "privilege"),
120
+ "injection": ("sql", "injection", "command", "eval", "exec", "shell", "xss"),
121
+ "secrets": ("secret", "credential", "api_key", "token", "hardcoded"),
122
+ "crypto": ("hash", "md5", "sha1", "encrypt", "decrypt", "cipher"),
123
+ "configuration": ("config", "debug", "cors", "ssl", "tls", "verbose"),
124
+ "network": ("ssrf", "redirect", "request", "fetch", "url"),
125
+ "dependencies": ("dependency", "vulnerability", "outdated", "cve"),
126
+ }
127
+ for cat, keywords in categories.items():
128
+ if any(kw in ftype for kw in keywords):
129
+ return cat
130
+ return "other"
131
+
132
+
133
+ def _add_discovered_scenarios(
134
+ model: ThreatModel,
135
+ finding_types: set[str],
136
+ category_counts: dict[str, int],
137
+ ) -> None:
138
+ """Add synthetic scenarios for finding types not covered by YAML library."""
139
+ existing_ids = {s.id for s in model.scenarios}
140
+ discovered = 0
141
+
142
+ for ftype in finding_types:
143
+ if not ftype or ftype in existing_ids:
144
+ continue
145
+ cat = _categorize_finding({"type": ftype})
146
+ if cat == "other":
147
+ continue
148
+ # Only add if we have multiple findings of this type
149
+ if category_counts.get(cat, 0) < 2:
150
+ continue
151
+ synthetic_id = f"DISCOVERED-{ftype.upper()[:20]}"
152
+ if synthetic_id in existing_ids:
153
+ continue
154
+ scenario = AttackScenario(
155
+ id=synthetic_id,
156
+ name=f"Discovered: {ftype}",
157
+ category=cat,
158
+ severity="medium",
159
+ cwe="",
160
+ owasp="",
161
+ description=f"Auto-discovered from {category_counts[cat]} scan findings of type '{ftype}'",
162
+ tags=["discovered", "auto"],
163
+ applicable=True,
164
+ relevance_score=min(0.3 + category_counts.get(cat, 0) * 0.05, 0.8),
165
+ )
166
+ model.scenarios.append(scenario)
167
+ existing_ids.add(synthetic_id)
168
+ discovered += 1
169
+
170
+ model.total_scenarios += discovered
171
+ if discovered:
172
+ _log.info("Added %d discovered scenarios from scan findings", discovered)
173
+
174
+
175
+ def _generate_updated_recommendations(
176
+ model: ThreatModel,
177
+ category_counts: dict[str, int],
178
+ ) -> list[str]:
179
+ """Generate recommendations factoring in actual findings."""
180
+ recs = list(model.recommendations)
181
+
182
+ # Top-finding categories
183
+ top_cats = sorted(category_counts.items(), key=lambda x: -x[1])[:3]
184
+ for cat, count in top_cats:
185
+ if count >= 3:
186
+ recs.append(f"High volume of {cat} findings ({count}) — prioritize remediation")
187
+
188
+ # Categories with no coverage
189
+ covered = set(model.by_category.keys())
190
+ for cat in category_counts:
191
+ if cat not in covered and category_counts[cat] >= 2:
192
+ recs.append(
193
+ f"Add threat scenarios for '{cat}' — {category_counts[cat]} findings detected"
194
+ )
195
+
196
+ return recs[:10]