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,275 @@
1
+ """
2
+ Dispatcher — routes detection events to the correct specialist agent.
3
+
4
+ Consumes Event objects (raw or Sigma-enriched) and decides:
5
+ 1. Which agent to dispatch to (based on technique_id, suggested_agent, confidence)
6
+ 2. Whether the action should be synchronous (block) or async (investigate)
7
+ 3. Whether to rate-limit, deduplicate, or skip
8
+
9
+ The dispatcher is the orchestration layer between detector output and agent execution.
10
+ It does NOT execute agents itself — it produces DispatchInstruction objects that
11
+ the caller (Coordinator or standalone runner) executes.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import logging
17
+ import time
18
+ from dataclasses import dataclass, field
19
+ from typing import Any
20
+
21
+ from patchi.core.agents.base import list_agents
22
+ from patchi.core.detector.event import Event, EventSeverity, TechniqueID
23
+
24
+ logger = logging.getLogger("patchi.detector.dispatcher")
25
+
26
+
27
+ class DispatchUrgency(str):
28
+ SYNC_BLOCK = "sync_block" # Block the request / stop processing immediately
29
+ SYNC_INVESTIGATE = "sync_inv" # Investigate synchronously (hold response)
30
+ ASYNC_PRIORITY = "async_pri" # High-priority async (process within seconds)
31
+ ASYNC_NORMAL = "async_normal" # Normal async queue
32
+ ASYNC_DEFERRED = "async_defer" # Low-priority, batch later
33
+ DISCARD = "discard" # Drop (below threshold)
34
+
35
+
36
+ @dataclass
37
+ class DispatchInstruction:
38
+ """What the dispatcher wants done — one instruction per event."""
39
+
40
+ event_id: str
41
+ technique_id: str
42
+ agent_name: str
43
+ urgency: str
44
+ confidence: float
45
+ reason: str
46
+ payload: dict[str, Any] = field(default_factory=dict)
47
+ created_at: float = field(default_factory=time.monotonic)
48
+
49
+
50
+ class Dispatcher:
51
+ """
52
+ Routes events to agents based on technique_id, confidence, and agent availability.
53
+
54
+ Resolution order:
55
+ 1. If event has `suggested_agent` AND confidence >= threshold → dispatch to that agent
56
+ 2. Map technique_id → agent from TechniqueID.for_agent_type() or agent registration
57
+ 3. If no mapping found and severity >= HIGH → dispatch to TriageAgent
58
+ 4. Otherwise → DISCARD (below threshold)
59
+
60
+ Urgency is determined by severity:
61
+ - CRITICAL → SYNC_BLOCK
62
+ - HIGH → SYNC_INVESTIGATE
63
+ - MEDIUM → ASYNC_PRIORITY
64
+ - LOW/INFO → ASYNC_NORMAL
65
+ """
66
+
67
+ def __init__(
68
+ self,
69
+ min_confidence: float = 0.3,
70
+ agent_whitelist: set[str] | None = None,
71
+ ):
72
+ self._min_confidence = min_confidence
73
+ self._agent_whitelist = agent_whitelist
74
+ self._agent_registry: dict[str, str] = {} # technique_id -> agent_name
75
+ self._dedup_window: dict[str, float] = {} # event_id -> timestamp (for dedup)
76
+ self._dedup_ttl_s = 300.0 # 5 minute dedup window
77
+
78
+ # Build agent registry from TechniqueID mapping + known agents
79
+ self._build_registry()
80
+
81
+ def _build_registry(self) -> None:
82
+ """Build technique_id → agent_name mapping from all registered agents."""
83
+ for agent_cls in list_agents():
84
+ name = getattr(agent_cls, "name", "") or agent_cls.__name__
85
+ for agent_type in (
86
+ "injection",
87
+ "auth",
88
+ "authz",
89
+ "dependency",
90
+ "secret",
91
+ "network",
92
+ "runtime",
93
+ "crypto",
94
+ "compliance",
95
+ "triage",
96
+ ):
97
+ if agent_type.lower() in name.lower():
98
+ tid = TechniqueID.for_agent_type(agent_type)
99
+ self._agent_registry[tid.value] = name
100
+
101
+ # Hard-coded fallbacks for agents that match by technique rather than name
102
+ fallbacks = {
103
+ TechniqueID.INJECTION.value: "InjectionAgent",
104
+ TechniqueID.BRUTE_FORCE.value: "AuthenticationAuditAgent",
105
+ TechniqueID.SECRETS_FROM_STORE.value: "SecretScanner",
106
+ TechniqueID.RESOURCE_DEVELOPMENT.value: "DependencyVulnerabilityAgent",
107
+ TechniqueID.TRAFFIC_SIGNALING.value: "NetworkAgent",
108
+ TechniqueID.EXECUTION.value: "RuntimeValidatorAgent",
109
+ TechniqueID.DEFENSE_EVASION.value: "CryptoAgent",
110
+ TechniqueID.UNKNOWN.value: "TriageAgent",
111
+ }
112
+ self._agent_registry.update(fallbacks)
113
+
114
+ # ── Core dispatch logic ──────────────────────────────────────────────────
115
+
116
+ def dispatch(self, event: Event, sigma_matches: list | None = None) -> DispatchInstruction:
117
+ """
118
+ Create a dispatch instruction for a single event.
119
+
120
+ Uses both the raw event and any Sigma rule matches to determine routing.
121
+ """
122
+ event_id = event.event_id
123
+
124
+ # Dedup check
125
+ if event_id in self._dedup_window:
126
+ elapsed = time.monotonic() - self._dedup_window[event_id]
127
+ if elapsed < self._dedup_ttl_s:
128
+ return DispatchInstruction(
129
+ event_id=event_id,
130
+ technique_id="",
131
+ agent_name="",
132
+ urgency=DispatchUrgency.DISCARD,
133
+ confidence=0.0,
134
+ reason="Deduplicated (same event_id within window)",
135
+ )
136
+
137
+ self._dedup_window[event_id] = time.monotonic()
138
+
139
+ # Step 1: Extract the best technique_id and agent hint
140
+ technique_id, confidence, suggested_agent = self._resolve_routing(event, sigma_matches)
141
+
142
+ if technique_id == TechniqueID.NONE.value:
143
+ return DispatchInstruction(
144
+ event_id=event_id,
145
+ technique_id=technique_id,
146
+ agent_name="",
147
+ urgency=DispatchUrgency.DISCARD,
148
+ confidence=0.0,
149
+ reason="No technique mapping (info/heartbeat)",
150
+ )
151
+
152
+ # Step 2: Find the agent to dispatch to
153
+ agent_name = self._resolve_agent(technique_id, suggested_agent)
154
+
155
+ if not agent_name:
156
+ if event.severity.sort_key() <= EventSeverity.LOW.sort_key():
157
+ return DispatchInstruction(
158
+ event_id=event_id,
159
+ technique_id=technique_id,
160
+ agent_name="",
161
+ urgency=DispatchUrgency.DISCARD,
162
+ confidence=confidence,
163
+ reason=f"No agent found for technique {technique_id} and severity too low",
164
+ )
165
+ agent_name = "TriageAgent"
166
+
167
+ # Step 3: Apply whitelist filter
168
+ if self._agent_whitelist and agent_name not in self._agent_whitelist:
169
+ return DispatchInstruction(
170
+ event_id=event_id,
171
+ technique_id=technique_id,
172
+ agent_name="",
173
+ urgency=DispatchUrgency.DISCARD,
174
+ confidence=confidence,
175
+ reason=f"Agent {agent_name} not in whitelist",
176
+ )
177
+
178
+ # Step 4: Determine urgency
179
+ urgency = self._resolve_urgency(event.severity, confidence)
180
+
181
+ # Build reason string
182
+ reason_parts = []
183
+ if suggested_agent:
184
+ reason_parts.append(f"suggested={suggested_agent}")
185
+ reason_parts.append(f"confidence={confidence:.2f}")
186
+ reason_parts.append(f"severity={event.severity.value}")
187
+ if sigma_matches:
188
+ reason_parts.append(f"sigma_rules={len(sigma_matches)}")
189
+
190
+ return DispatchInstruction(
191
+ event_id=event_id,
192
+ technique_id=technique_id,
193
+ agent_name=agent_name,
194
+ urgency=urgency,
195
+ confidence=confidence,
196
+ reason="; ".join(reason_parts),
197
+ payload=event.payload,
198
+ )
199
+
200
+ def dispatch_batch(self, events: list[Event]) -> list[DispatchInstruction]:
201
+ """Dispatch a batch of events. Deduplicates by event_id."""
202
+ seen: set[str] = set()
203
+ results: list[DispatchInstruction] = []
204
+ for event in events:
205
+ if event.event_id in seen:
206
+ continue
207
+ seen.add(event.event_id)
208
+ instruction = self.dispatch(event)
209
+ if instruction.urgency != DispatchUrgency.DISCARD:
210
+ results.append(instruction)
211
+ return results
212
+
213
+ # ── Helpers ───────────────────────────────────────────────────────────────
214
+
215
+ def _resolve_routing(
216
+ self,
217
+ event: Event,
218
+ sigma_matches: list | None,
219
+ ) -> tuple[str, float, str]:
220
+ """Determine the best technique_id, confidence, and suggested_agent."""
221
+ tid = (
222
+ event.technique_id.value
223
+ if isinstance(event.technique_id, TechniqueID)
224
+ else event.technique_id
225
+ )
226
+ confidence = event.confidence
227
+ suggested_agent = event.suggested_agent or ""
228
+
229
+ # If Sigma matched, boost confidence and use Sigma's technique_id
230
+ if sigma_matches:
231
+ best = sigma_matches[0]
232
+ tid = (
233
+ best.technique_id.value
234
+ if isinstance(best.technique_id, TechniqueID)
235
+ else str(best.technique_id)
236
+ )
237
+ confidence = max(confidence, best.confidence)
238
+ if best.target_agent:
239
+ suggested_agent = best.target_agent
240
+
241
+ return tid, confidence, suggested_agent
242
+
243
+ def _resolve_agent(self, technique_id: str, suggested_agent: str | None) -> str:
244
+ """Find the agent to handle this technique, with fallback logic."""
245
+ if suggested_agent:
246
+ return suggested_agent
247
+ return self._agent_registry.get(technique_id, "")
248
+
249
+ def _resolve_urgency(self, severity: EventSeverity, confidence: float) -> str:
250
+ """Map severity + confidence to urgency level."""
251
+ if severity == EventSeverity.CRITICAL:
252
+ if confidence >= 0.7:
253
+ return DispatchUrgency.SYNC_BLOCK
254
+ return DispatchUrgency.SYNC_INVESTIGATE
255
+ if severity == EventSeverity.HIGH:
256
+ if confidence >= 0.5:
257
+ return DispatchUrgency.SYNC_INVESTIGATE
258
+ return DispatchUrgency.ASYNC_PRIORITY
259
+ if severity == EventSeverity.MEDIUM:
260
+ return (
261
+ DispatchUrgency.ASYNC_PRIORITY
262
+ if confidence >= 0.5
263
+ else DispatchUrgency.ASYNC_NORMAL
264
+ )
265
+ return DispatchUrgency.ASYNC_NORMAL
266
+
267
+ def stats(self) -> dict:
268
+ return {
269
+ "agent_registry_size": len(self._agent_registry),
270
+ "dedup_window_size": len(self._dedup_window),
271
+ "known_techniques": list(self._agent_registry.keys()),
272
+ }
273
+
274
+ def clear_dedup(self) -> None:
275
+ self._dedup_window.clear()
@@ -0,0 +1,254 @@
1
+ """
2
+ Event schema — the universal envelope for everything Patchi detects, routes, or acts on.
3
+
4
+ Every signal source (HTTP traffic, logs, syscalls, git diffs, agent findings) normalizes
5
+ into this schema before reaching the dispatcher. Every action, override, and audit log
6
+ entry also uses it. This is the single contract between all pipeline stages.
7
+
8
+ MITRE ATT&CK v14 technique IDs are used as the primary classification axis because:
9
+ - They're stable, versioned, and community-maintained (not invented here)
10
+ - They map 1:1 to the specialist agent roster
11
+ - Atomic Red Team tests emit known technique_ids, making end-to-end validation tractable
12
+ - OWASP/CWE mappings are secondary labels (we maintain the bidirectional map)
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import uuid
18
+ from dataclasses import dataclass, field
19
+ from datetime import UTC, datetime
20
+ from enum import StrEnum
21
+ from typing import Any
22
+
23
+ # ── Event severity (extends the Finding severity model) ──────────────────────
24
+
25
+
26
+ class EventSeverity(StrEnum):
27
+ DEBUG = "debug" # No alerting, diagnostics only
28
+ INFO = "info" # Informational, never alerts
29
+ LOW = "low" # Digest-only, no immediate action
30
+ MEDIUM = "medium" # Worthy of investigation
31
+ HIGH = "high" # Requires prompt attention
32
+ CRITICAL = "critical" # Immediate action required
33
+
34
+ def sort_key(self) -> int:
35
+ return {
36
+ "debug": 5,
37
+ "info": 4,
38
+ "low": 3,
39
+ "medium": 2,
40
+ "high": 1,
41
+ "critical": 0,
42
+ }[self.value]
43
+
44
+ def to_enum(self) -> str:
45
+ return self.value
46
+
47
+
48
+ # ── Event source types ──────────────────────────────────────────────────────
49
+
50
+
51
+ class EventSource(StrEnum):
52
+ """Where the event originated. Each maps to a signal source type."""
53
+
54
+ AGENT = "agent" # A Patchi agent produced a finding
55
+ HTTP_TRAFFIC = "http_traffic" # Ingress/egress HTTP request
56
+ LOG_LINE = "log_line" # Application or access log
57
+ SYSCALL = "syscall" # Host/container syscall (Falco)
58
+ NETWORK_FLOW = "network_flow" # Network protocol event (Suricata/Zeek)
59
+ DEPENDENCY_SCAN = "dependency" # Dependency manifest / CVE scan
60
+ SECRET_SCAN = "secret" # Secret scanning hit
61
+ GIT_EVENT = "git_event" # Commit, PR, diff, push
62
+ CONFIG_CHANGE = "config" # Config file or env var change
63
+ HEARTBEAT = "heartbeat" # System health / uptime check
64
+ USER_ACTION = "user_action" # Explicit user command or override
65
+ ANOMALY = "anomaly" # Detected by the anomaly layer
66
+
67
+
68
+ # ── MITRE ATT&CK technique / tactic IDs ──────────────────────────────────────
69
+
70
+
71
+ # Selected ATT&CK technique IDs relevant to Patchi's agent roster.
72
+ # v14+ stable IDs; see https://attack.mitre.org/techniques/enterprise/
73
+
74
+
75
+ class TechniqueID(StrEnum):
76
+ INITIAL_ACCESS = "T1190" # Exploit Public-Facing Application
77
+ EXECUTION = "T1203" # Exploitation for Client Execution
78
+ PERSISTENCE = "T1505" # Server Software Component
79
+ DEFENSE_EVASION = "T1562" # Impair Defenses
80
+ CREDENTIAL_ACCESS = "T1552" # Unsecured Credentials
81
+ DISCOVERY = "T1046" # Network Service Discovery
82
+ COLLECTION = "T1567" # Exfiltration Over Web Service
83
+ COMMAND_AND_CONTROL = "T1071" # Application Layer Protocol
84
+ EXFILTRATION = "T1048" # Exfiltration Over Alternative Protocol
85
+ IMPACT = "T1499" # Endpoint Denial of Service
86
+ RESOURCE_DEVELOPMENT = "T1583" # Acquire Infrastructure
87
+ INJECTION = "T1055" # Process Injection
88
+ VALID_ACCOUNTS = "T1078" # Valid Accounts
89
+ BRUTE_FORCE = "T1110" # Brute Force
90
+ INPUT_CAPTURE = "T1056" # Input Capture
91
+ MODIFY_AUTH_PROCESS = "T1556" # Modify Authentication Process
92
+ STEAL_APPLICATION_ACCESS = "T1528" # Steal Application Access Token
93
+ SECRETS_FROM_STORE = "T1552.001" # Credentials in Files / Keychains
94
+ EXPLOIT_PUBLIC_APP = "T1190" # Exploit Public-Facing Application
95
+ MAN_IN_MIDDLE = "T1557" # Adversary-in-the-Middle
96
+ TRAFFIC_SIGNALING = "T1573" # Encrypted Channel
97
+ PROXY = "T1090" # Proxy
98
+ UNKNOWN = "T9999" # Unclassified / needs triage
99
+ NONE = "T0000" # No technique mapping (info/heartbeat)
100
+
101
+ @classmethod
102
+ def for_agent_type(cls, agent_type: str) -> TechniqueID:
103
+ mapping = {
104
+ "injection": cls.INJECTION,
105
+ "auth": cls.BRUTE_FORCE,
106
+ "authz": cls.VALID_ACCOUNTS,
107
+ "dependency": cls.RESOURCE_DEVELOPMENT,
108
+ "secret": cls.SECRETS_FROM_STORE,
109
+ "network": cls.TRAFFIC_SIGNALING,
110
+ "runtime": cls.EXECUTION,
111
+ "triage": cls.UNKNOWN,
112
+ "crypto": cls.DEFENSE_EVASION,
113
+ "compliance": cls.IMPACT,
114
+ }
115
+ return mapping.get(agent_type, cls.UNKNOWN)
116
+
117
+ @classmethod
118
+ def display_name(cls, tid: str) -> str:
119
+ names = {
120
+ "T1190": "Exploit Public-Facing Application",
121
+ "T1203": "Exploitation for Client Execution",
122
+ "T1505": "Persistent Server Software Component",
123
+ "T1562": "Impair Defenses",
124
+ "T1552": "Unsecured Credentials",
125
+ "T1046": "Network Service Discovery",
126
+ "T1567": "Exfiltration Over Web Service",
127
+ "T1071": "Application Layer Protocol C2",
128
+ "T1048": "Exfiltration Over Alternative Protocol",
129
+ "T1499": "Endpoint Denial of Service",
130
+ "T1583": "Acquire Infrastructure",
131
+ "T1055": "Process Injection",
132
+ "T1078": "Valid Accounts",
133
+ "T1110": "Brute Force",
134
+ "T1056": "Input Capture",
135
+ "T1556": "Modify Authentication Process",
136
+ "T1528": "Steal Application Access Token",
137
+ "T1552.001": "Credentials from Files",
138
+ "T1557": "Adversary-in-the-Middle",
139
+ "T1573": "Encrypted Channel",
140
+ "T1090": "Proxy",
141
+ "T9999": "Unclassified",
142
+ "T0000": "None",
143
+ }
144
+ return names.get(tid, "Unknown Technique")
145
+
146
+
147
+ # ── The event envelope ───────────────────────────────────────────────────────
148
+
149
+
150
+ @dataclass
151
+ class Event:
152
+ """
153
+ Universal event envelope.
154
+
155
+ Every signal in the system normalizes to this shape before entering the pipeline.
156
+ Immutable after creation (consumers should not mutate).
157
+
158
+ The `id` is a deterministic hash of the event's key fields so the same external
159
+ signal always produces the same event ID (useful for deduplication). Override with
160
+ `override_id` for synthetic events.
161
+ """
162
+
163
+ source: EventSource
164
+ technique_id: TechniqueID | str
165
+
166
+ # Core payload
167
+ summary: str # One-line human-readable description
168
+ severity: EventSeverity = EventSeverity.INFO
169
+ payload: dict[str, Any] = field(default_factory=dict) # Signal-specific data
170
+
171
+ # Routing hints — populated by the detector, consumed by the dispatcher
172
+ suggested_agent: str | None = None # e.g. "InjectionAgent"
173
+ confidence: float = 0.5 # 0.0–1.0, how sure the detector is
174
+
175
+ # Enrichment
176
+ agent_name: str | None = None # Which agent produced this (if from an agent)
177
+ finding_id: str | None = None # Link back to a specific Finding if applicable
178
+ cwe_ids: list[str] = field(default_factory=list)
179
+ owasp_category: str = ""
180
+
181
+ # Metadata
182
+ timestamp: str = field(default_factory=lambda: datetime.now(UTC).isoformat())
183
+ event_id: str = field(default_factory=lambda: uuid.uuid4().hex[:16])
184
+ source_details: dict[str, str] = field(default_factory=dict) # host, container, file, repo
185
+
186
+ # Audit chain
187
+ parent_event_id: str | None = None # Which event triggered this one
188
+
189
+ def to_dict(self) -> dict:
190
+ return {
191
+ "event_id": self.event_id,
192
+ "source": self.source.value,
193
+ "technique_id": self.technique_id.value
194
+ if isinstance(self.technique_id, TechniqueID)
195
+ else self.technique_id,
196
+ "summary": self.summary,
197
+ "severity": self.severity.value,
198
+ "payload": self.payload,
199
+ "suggested_agent": self.suggested_agent,
200
+ "confidence": self.confidence,
201
+ "agent_name": self.agent_name,
202
+ "finding_id": self.finding_id,
203
+ "cwe_ids": self.cwe_ids,
204
+ "owasp_category": self.owasp_category,
205
+ "timestamp": self.timestamp,
206
+ "source_details": self.source_details,
207
+ "parent_event_id": self.parent_event_id,
208
+ }
209
+
210
+ @property
211
+ def technique_display(self) -> str:
212
+ tid = (
213
+ self.technique_id.value
214
+ if isinstance(self.technique_id, TechniqueID)
215
+ else self.technique_id
216
+ )
217
+ return f"{tid} — {TechniqueID.display_name(tid)}"
218
+
219
+
220
+ # ── EventBatch (for bulk processing) ─────────────────────────────────────────
221
+
222
+
223
+ @dataclass
224
+ class EventBatch:
225
+ """A time-windowed batch of events for bulk dispatch/audit."""
226
+
227
+ events: list[Event] = field(default_factory=list)
228
+ batch_id: str = field(default_factory=lambda: uuid.uuid4().hex[:16])
229
+ created: str = field(default_factory=lambda: datetime.now(UTC).isoformat())
230
+
231
+ def by_severity(self) -> dict[str, list[Event]]:
232
+ groups: dict[str, list[Event]] = {}
233
+ for ev in self.events:
234
+ groups.setdefault(ev.severity.value, []).append(ev)
235
+ return groups
236
+
237
+ def by_technique(self) -> dict[str, list[Event]]:
238
+ groups: dict[str, list[Event]] = {}
239
+ for ev in self.events:
240
+ tid = (
241
+ ev.technique_id.value
242
+ if isinstance(ev.technique_id, TechniqueID)
243
+ else ev.technique_id
244
+ )
245
+ groups.setdefault(tid, []).append(ev)
246
+ return groups
247
+
248
+ def to_dict(self) -> dict:
249
+ return {
250
+ "batch_id": self.batch_id,
251
+ "created": self.created,
252
+ "count": len(self.events),
253
+ "events": [e.to_dict() for e in self.events],
254
+ }