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,913 @@
1
+ """
2
+ Dynamic Security Domain Activator v2 — Intelligent, signal-based domain activation.
3
+
4
+ Replaces static domain loading with adaptive activation based on:
5
+ - Code signals (imports, patterns, frameworks)
6
+ - Configuration signals (env vars, config files, infra)
7
+ - Runtime signals (running services, open ports)
8
+ - Historical signals (past findings, incident history)
9
+ - Project context (business domain, compliance requirements)
10
+
11
+ Only activates domains that are actually relevant, reducing scan time and noise.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import logging
17
+ import re
18
+ from dataclasses import dataclass, field
19
+ from pathlib import Path
20
+
21
+ from patchi.core import memory as mem
22
+ from patchi.core.brain.scanner import FileInfo
23
+ from patchi.core.security.domain_loader import Domain, DomainLoader
24
+
25
+ _log = logging.getLogger("patchi.security.domain_activator_v2")
26
+
27
+
28
+ @dataclass
29
+ class ActivationSignal:
30
+ """A signal that contributes to domain activation."""
31
+
32
+ source: str # "code", "config", "runtime", "history", "context"
33
+ domain: str
34
+ weight: float # 0.0 - 1.0
35
+ evidence: str # Human-readable evidence
36
+ metadata: dict = field(default_factory=dict)
37
+
38
+
39
+ @dataclass
40
+ class DomainActivationResult:
41
+ """Result of domain activation analysis."""
42
+
43
+ activated_domains: list[str]
44
+ signals: list[ActivationSignal]
45
+ domain_scores: dict[str, float]
46
+ skipped_domains: list[str]
47
+ activation_threshold: float
48
+
49
+
50
+ # ── Domain Signal Definitions ────────────────────────────────────────────────
51
+
52
+ # Each domain has a set of signals that activate it
53
+ # Signals are weighted by reliability (higher = more reliable indicator)
54
+ DOMAIN_SIGNALS: dict[str, dict[str, list[tuple[str, float, str]]]] = {
55
+ # Injection domains
56
+ "injection-sql": {
57
+ "code": [
58
+ (r"\.execute\s*\([^)]*%[sf]", 0.9, "String formatting in SQL execute"),
59
+ # Any `.execute(...)` / `.executemany(...)` built with `+` string
60
+ # concatenation — catches conn/cursor/session variants alike.
61
+ (
62
+ r"\.execute(?:many)?\s*\([^)]*['\"]\s*\+",
63
+ 0.85,
64
+ "String concatenation in SQL execute",
65
+ ),
66
+ (r"cursor\.execute\s*\([^)]*\+", 0.8, "String concatenation in cursor SQL"),
67
+ (r"SELECT.*FROM.*WHERE.*\{", 0.7, "Template-style SQL"),
68
+ (r"f['\"].*SELECT", 0.7, "F-string SQL query"),
69
+ (r"\.raw\s*\([^)]*SELECT", 0.8, "Raw SQL usage"),
70
+ ],
71
+ "config": [
72
+ (r"DATABASE_URL", 0.3, "Database connection configured"),
73
+ ],
74
+ },
75
+ "injection-nosql": {
76
+ "code": [
77
+ (r"\.find\s*\(\s*\{[^}]*\$where", 0.9, "MongoDB $where injection"),
78
+ (r"\.aggregate\s*\([^)]*\$expr", 0.7, "Aggregation with user input"),
79
+ (r"eval\s*\([^)]*db\.", 0.8, "JavaScript eval with DB"),
80
+ ],
81
+ },
82
+ "injection-command": {
83
+ "code": [
84
+ (
85
+ r"subprocess\.(run|call|Popen)\s*\([^)]*shell\s*=\s*True",
86
+ 0.9,
87
+ "Shell=True with user input",
88
+ ),
89
+ (r"os\.system\s*\(", 0.95, "os.system usage"),
90
+ (r"os\.popen\s*\(", 0.9, "os.popen usage"),
91
+ (r"commands\.getstatusoutput", 0.8, "commands module usage"),
92
+ (r"shlex\.split\s*\([^)]*input", 0.7, "shlex with user input"),
93
+ ],
94
+ },
95
+ "injection-ldap": {
96
+ "code": [
97
+ (r"ldap3.*\([^)]*\)", 0.6, "LDAP library usage"),
98
+ (r"python-ldap", 0.5, "LDAP import"),
99
+ ],
100
+ },
101
+ "injection-xpath": {
102
+ "code": [
103
+ (r"lxml.*xpath", 0.6, "XPath usage"),
104
+ (r"etree\.XPath", 0.7, "XPath evaluation"),
105
+ ],
106
+ },
107
+ "injection-template": {
108
+ "code": [
109
+ (r"jinja2\.Environment.*autoescape\s*=\s*False", 0.9, "Jinja2 autoescape disabled"),
110
+ (r"render_template_string\s*\(", 0.8, "Flask render_template_string"),
111
+ (r"Template\s*\([^)]*request\.", 0.7, "Template with request data"),
112
+ (r"mustache|handlebars|nunjucks", 0.5, "Template engine usage"),
113
+ ],
114
+ },
115
+ # XSS
116
+ "xss-reflected": {
117
+ "code": [
118
+ (r"request\.(args|query_params|GET)\[", 0.7, "Direct request param usage"),
119
+ (r"HttpResponse\s*\([^)]*request\.", 0.8, "Direct response with request data"),
120
+ (r"res\.send\s*\([^)]*req\.", 0.7, "Express res.send with req data"),
121
+ ],
122
+ },
123
+ "xss-stored": {
124
+ "code": [
125
+ (r"innerHTML\s*=", 0.8, "Direct innerHTML assignment"),
126
+ (r"dangerouslySetInnerHTML", 0.9, "React dangerouslySetInnerHTML"),
127
+ (r"v-html\s*=", 0.8, "Vue v-html directive"),
128
+ (r"\.html\s*\([^)]*user", 0.7, "jQuery .html() with user data"),
129
+ ],
130
+ },
131
+ "xss-dom": {
132
+ "code": [
133
+ (r"document\.write\s*\(", 0.9, "document.write usage"),
134
+ (r"location\.hash", 0.6, "Hash-based routing"),
135
+ (r"eval\s*\([^)]*location", 0.8, "Eval with location"),
136
+ ],
137
+ },
138
+ # Auth & Session
139
+ "authentication": {
140
+ "code": [
141
+ (r"@login_required|@auth_required", 0.8, "Auth decorator usage"),
142
+ (r"passport\.authenticate", 0.8, "Passport.js usage"),
143
+ (r"flask_login|django\.contrib\.auth", 0.7, "Auth framework"),
144
+ (r"bcrypt|argon2|scrypt|PBKDF2", 0.6, "Password hashing"),
145
+ (r"session\[|session\.get\(", 0.7, "Session usage"),
146
+ ],
147
+ "config": [
148
+ (r"SECRET_KEY|JWT_SECRET", 0.5, "Auth secret configured"),
149
+ ],
150
+ },
151
+ "authorization": {
152
+ "code": [
153
+ (r"@permission_required|@role_required", 0.8, "Permission decorators"),
154
+ (r"rbac|acl|casbin", 0.6, "Authorization library"),
155
+ (r"policy\.enforce|can\s*\(", 0.7, "Policy enforcement"),
156
+ ],
157
+ },
158
+ "session-management": {
159
+ "code": [
160
+ (r"session\.cookie_secure\s*=\s*False", 0.8, "Insecure session cookie"),
161
+ (r"session\.cookie_httponly\s*=\s*False", 0.8, "Non-HttpOnly cookie"),
162
+ (r"same_site\s*=\s*['\"]?none", 0.7, "SameSite=None"),
163
+ ],
164
+ },
165
+ "jwt-security": {
166
+ "code": [
167
+ (r"jwt\.encode|jwt\.decode", 0.7, "JWT usage"),
168
+ (r"algorithm\s*=\s*['\"]none['\"]", 0.95, "JWT alg=none"),
169
+ (r"verify\s*=\s*False", 0.9, "JWT verification disabled"),
170
+ ],
171
+ },
172
+ # Secrets
173
+ "secrets-management": {
174
+ "code": [
175
+ (
176
+ r"(api[_-]?key|secret|password|token)\s*=\s*['\"][^'\"]{16,}",
177
+ 0.9,
178
+ "Hardcoded secret",
179
+ ),
180
+ (r"os\.environ\[['\"][A-Z_]*KEY", 0.6, "Env var access"),
181
+ (r"vault|keyring|secretsmanager", 0.5, "Secret management lib"),
182
+ ],
183
+ "config": [
184
+ (r"\.env", 0.4, "Env file present"),
185
+ ],
186
+ },
187
+ # Crypto
188
+ "cryptography": {
189
+ "code": [
190
+ (r"Crypto\.Cipher|pycryptodome|cryptography\.hazmat", 0.7, "Crypto library"),
191
+ (r"MD5|SHA1|DES|RC4", 0.8, "Weak algorithm"),
192
+ (r"ECB\s*mode", 0.7, "ECB mode"),
193
+ (r"IV\s*=\s*['\"]0{16}", 0.8, "Static IV"),
194
+ ],
195
+ },
196
+ "tls-ssl": {
197
+ "code": [
198
+ (r"verify\s*=\s*False", 0.8, "SSL verify disabled"),
199
+ (r"ssl\._create_unverified_context", 0.9, "Unverified SSL context"),
200
+ (r"check_hostname\s*=\s*False", 0.8, "Hostname check disabled"),
201
+ ],
202
+ "config": [
203
+ (r"TLS|SSL|HTTPS", 0.4, "TLS config present"),
204
+ ],
205
+ },
206
+ # SSRF
207
+ "ssrf": {
208
+ "code": [
209
+ (r"requests\.(get|post|put|delete)\s*\([^)]*request\.", 0.8, "Request with user URL"),
210
+ (r"urllib\.request\.urlopen\s*\([^)]*input", 0.8, "urlopen with input"),
211
+ (r"httpx\.get\s*\([^)]*user", 0.7, "httpx with user input"),
212
+ (r"fetch\s*\([^)]*input", 0.7, "Fetch with user input"),
213
+ ],
214
+ },
215
+ # Path Traversal
216
+ "path-traversal": {
217
+ "code": [
218
+ (r"open\s*\([^)]*request\.", 0.8, "File open with request"),
219
+ (r"send_file\s*\([^)]*request\.", 0.8, "Flask send_file with request"),
220
+ (r"path\.join\s*\([^)]*\.\.", 0.7, "Path join with traversal"),
221
+ (r"os\.path\.join.*user", 0.7, "Path join with user input"),
222
+ ],
223
+ },
224
+ # Deserialization
225
+ "deserialization": {
226
+ "code": [
227
+ (r"pickle\.loads?\s*\(", 0.95, "Pickle deserialization"),
228
+ (r"marshal\.loads?\s*\(", 0.9, "Marshal deserialization"),
229
+ (r"yaml\.load\s*\([^)]*\)", 0.8, "YAML load without SafeLoader"),
230
+ (r"jsonpickle|dill|cloudpickle", 0.7, "Serialization library"),
231
+ (r"java\.io\.ObjectInputStream", 0.9, "Java deserialization"),
232
+ ],
233
+ },
234
+ # XXE
235
+ "xxe": {
236
+ "code": [
237
+ (r"etree\.parse\s*\([^)]*request", 0.8, "XML parse with request"),
238
+ (r"XMLParser\s*\([^)]*resolve_entities\s*=\s*True", 0.9, "Entity resolution enabled"),
239
+ (
240
+ r"SAXParser.*setFeature.*external-general-entities.*true",
241
+ 0.8,
242
+ "SAX external entities",
243
+ ),
244
+ ],
245
+ },
246
+ # CORS
247
+ "cors": {
248
+ "code": [
249
+ (r"CORS\s*\([^)]*origins\s*=\s*['\"]\*['\"]", 0.9, "CORS wildcard origin"),
250
+ (r"Access-Control-Allow-Origin\s*:\s*\*", 0.9, "Wildcard CORS header"),
251
+ (r"cors\.allow_all_origins\s*=\s*True", 0.8, "Allow all origins"),
252
+ ],
253
+ },
254
+ # CSRF
255
+ "csrf": {
256
+ "code": [
257
+ (r"@csrf_exempt", 0.9, "CSRF exemption"),
258
+ (r"csrf\.protect\s*=\s*False", 0.8, "CSRF disabled"),
259
+ (r"WTF_CSRF_ENABLED\s*=\s*False", 0.8, "Flask-WTF CSRF disabled"),
260
+ ],
261
+ },
262
+ # Security Headers
263
+ "security-headers": {
264
+ "code": [
265
+ (r"helmet|secure-headers", 0.5, "Security headers lib"),
266
+ (r"X-Frame-Options|CSP|HSTS", 0.6, "Security header usage"),
267
+ ],
268
+ "config": [
269
+ (r"SECURE_.*_HEADER", 0.5, "Security header config"),
270
+ ],
271
+ },
272
+ # Rate Limiting
273
+ "rate-limiting": {
274
+ "code": [
275
+ (r"flask_limiter|django_ratelimit|express-rate-limit", 0.6, "Rate limit lib"),
276
+ (r"@limiter\.limit|@ratelimit", 0.7, "Rate limit decorator"),
277
+ ],
278
+ "config": [
279
+ (r"RATE_LIMIT|RATELIMIT", 0.4, "Rate limit config"),
280
+ ],
281
+ },
282
+ # Supply Chain
283
+ "supply-chain": {
284
+ "code": [
285
+ (
286
+ r"package\.json|requirements\.txt|pyproject\.toml|Cargo\.toml|go\.mod",
287
+ 0.4,
288
+ "Dependency file",
289
+ ),
290
+ ],
291
+ "config": [
292
+ (r"dependabot|renovate|snyk", 0.5, "Dependency bot config"),
293
+ ],
294
+ },
295
+ "dependency-vulnerability": {
296
+ "code": [
297
+ (
298
+ r"package\.json|requirements\.txt|pyproject\.toml|Cargo\.toml|go\.mod",
299
+ 0.4,
300
+ "Dependency file",
301
+ ),
302
+ ],
303
+ },
304
+ # Container/Cloud
305
+ "container-security": {
306
+ "code": [
307
+ (r"Dockerfile|docker-compose\.ya?ml", 0.6, "Docker config"),
308
+ (r"FROM\s+\w+:", 0.5, "Base image"),
309
+ ],
310
+ },
311
+ "kubernetes-security": {
312
+ "code": [
313
+ (r"\.ya?ml$", 0.3, "YAML file"),
314
+ (r"apiVersion:|kind: (Deployment|Service|Pod|Ingress)", 0.7, "K8s resource"),
315
+ (r"securityContext|runAsNonRoot|readOnlyRootFilesystem", 0.6, "Security context"),
316
+ ],
317
+ },
318
+ "cloud-security": {
319
+ "code": [
320
+ (r"boto3|google-cloud|azure-mgmt", 0.5, "Cloud SDK"),
321
+ (r"terraform|cloudformation|pulumi", 0.5, "IaC"),
322
+ ],
323
+ },
324
+ # Runtime
325
+ "runtime-protection": {
326
+ "code": [
327
+ (r"falco|sysdig|tracee", 0.6, "Runtime security tool"),
328
+ ],
329
+ "config": [
330
+ (r"falco|runtime", 0.4, "Runtime config"),
331
+ ],
332
+ },
333
+ # Privacy
334
+ "privacy-gdpr": {
335
+ "code": [
336
+ (r"gdpr|personal_data|pii|data_subject", 0.6, "GDPR keywords"),
337
+ (r"anonymize|pseudonymize|consent", 0.6, "Privacy functions"),
338
+ ],
339
+ "config": [
340
+ (r"GDPR|PRIVACY|DATA_PROTECTION", 0.4, "Privacy config"),
341
+ ],
342
+ },
343
+ # Business Logic
344
+ "business-logic": {
345
+ "code": [
346
+ (r"transfer|payment|checkout|order", 0.5, "Financial keywords"),
347
+ (r"workflow|state_machine|saga", 0.5, "Workflow patterns"),
348
+ ],
349
+ },
350
+ # API Security
351
+ "api-security": {
352
+ "code": [
353
+ (r"openapi|swagger|graphql", 0.5, "API schema"),
354
+ (r"rate_limit|throttle|quota", 0.5, "API protection"),
355
+ ],
356
+ },
357
+ # Logging/Audit
358
+ "audit-logging": {
359
+ "code": [
360
+ (r"audit|auditlog|structlog", 0.5, "Audit library"),
361
+ (r"logging\.audit|logger\.audit", 0.6, "Audit logging"),
362
+ ],
363
+ },
364
+ }
365
+
366
+
367
+ # ── Signal Extraction ────────────────────────────────────────────────────────
368
+
369
+
370
+ class SignalExtractor:
371
+ """Extracts activation signals from project artifacts."""
372
+
373
+ def __init__(self, root: Path):
374
+ self.root = root
375
+ self._code_cache: dict[str, str] = {}
376
+
377
+ def extract_all_signals(
378
+ self,
379
+ file_infos: list[FileInfo],
380
+ project_context: dict,
381
+ ) -> list[ActivationSignal]:
382
+ """Extract all signals from code, config, and context."""
383
+ signals = []
384
+
385
+ # Code signals
386
+ signals.extend(self._extract_code_signals(file_infos))
387
+
388
+ # Config signals
389
+ signals.extend(self._extract_config_signals())
390
+
391
+ # Context signals (frameworks, domains, etc.)
392
+ signals.extend(self._extract_context_signals(project_context))
393
+
394
+ # History signals (past findings)
395
+ signals.extend(self._extract_history_signals())
396
+
397
+ return signals
398
+
399
+ def _extract_code_signals(self, file_infos: list[FileInfo]) -> list[ActivationSignal]:
400
+ signals = []
401
+
402
+ for fi in file_infos:
403
+ if fi.error:
404
+ continue
405
+
406
+ # Get file content (cached)
407
+ content = self._get_file_content(fi.path)
408
+ if not content:
409
+ continue
410
+
411
+ content.lower()
412
+ file_path = fi.path
413
+
414
+ # Check each domain's code signals
415
+ for domain, signal_defs in DOMAIN_SIGNALS.items():
416
+ code_signals = signal_defs.get("code", [])
417
+ for pattern, weight, description in code_signals:
418
+ if re.search(pattern, content, re.IGNORECASE):
419
+ signals.append(
420
+ ActivationSignal(
421
+ source="code",
422
+ domain=domain,
423
+ weight=weight,
424
+ evidence=f"{description} in {file_path}",
425
+ metadata={"file": file_path, "pattern": pattern},
426
+ )
427
+ )
428
+
429
+ return signals
430
+
431
+ def _extract_config_signals(self) -> list[ActivationSignal]:
432
+ signals = []
433
+
434
+ # Check common config files
435
+ config_files = [
436
+ ".env",
437
+ "config.yaml",
438
+ "config.yml",
439
+ "settings.py",
440
+ "application.properties",
441
+ "application.yml",
442
+ "docker-compose.yml",
443
+ "docker-compose.yaml",
444
+ "kubernetes/",
445
+ "k8s/",
446
+ "helm/",
447
+ "terraform/",
448
+ ".tf",
449
+ "package.json",
450
+ "requirements.txt",
451
+ "pyproject.toml",
452
+ "Cargo.toml",
453
+ "go.mod",
454
+ "pom.xml",
455
+ ]
456
+
457
+ for config_path in config_files:
458
+ full_path = self.root / config_path
459
+ if not full_path.exists():
460
+ # Try glob for directories
461
+ if config_path.endswith("/"):
462
+ matches = list(self.root.rglob(config_path[:-1] + "/*"))
463
+ if not matches:
464
+ continue
465
+ full_path = matches[0]
466
+ else:
467
+ continue
468
+
469
+ try:
470
+ if full_path.is_file():
471
+ content = full_path.read_text(encoding="utf-8", errors="ignore")
472
+ else:
473
+ continue
474
+ except Exception:
475
+ continue
476
+
477
+ content.lower()
478
+
479
+ for domain, signal_defs in DOMAIN_SIGNALS.items():
480
+ config_signals = signal_defs.get("config", [])
481
+ for pattern, weight, description in config_signals:
482
+ if re.search(pattern, content, re.IGNORECASE):
483
+ signals.append(
484
+ ActivationSignal(
485
+ source="config",
486
+ domain=domain,
487
+ weight=weight,
488
+ evidence=f"{description} in {config_path}",
489
+ metadata={"config_file": config_path, "pattern": pattern},
490
+ )
491
+ )
492
+
493
+ return signals
494
+
495
+ def _extract_context_signals(self, project_context: dict) -> list[ActivationSignal]:
496
+ signals = []
497
+
498
+ # Framework-based signals
499
+ frameworks = project_context.get("frameworks", [])
500
+ for fw in frameworks:
501
+ fw_name = fw.get("name", "").lower()
502
+
503
+ # Framework implies certain domains
504
+ if fw_name in ("django", "flask", "fastapi", "express", "spring", "gin"):
505
+ signals.append(
506
+ ActivationSignal(
507
+ source="context",
508
+ domain="api-security",
509
+ weight=0.6,
510
+ evidence=f"Web framework {fw_name} detected",
511
+ )
512
+ )
513
+ signals.append(
514
+ ActivationSignal(
515
+ source="context",
516
+ domain="authentication",
517
+ weight=0.5,
518
+ evidence=f"Web framework {fw_name} typically has auth",
519
+ )
520
+ )
521
+ signals.append(
522
+ ActivationSignal(
523
+ source="context",
524
+ domain="security-headers",
525
+ weight=0.4,
526
+ evidence=f"Web framework {fw_name} needs security headers",
527
+ )
528
+ )
529
+
530
+ if fw_name in ("react", "vue", "svelte", "angular", "next.js", "nuxt"):
531
+ signals.append(
532
+ ActivationSignal(
533
+ source="context",
534
+ domain="xss-dom",
535
+ weight=0.6,
536
+ evidence=f"Frontend framework {fw_name} - DOM XSS relevant",
537
+ )
538
+ )
539
+ signals.append(
540
+ ActivationSignal(
541
+ source="context",
542
+ domain="api-security",
543
+ weight=0.5,
544
+ evidence=f"Frontend framework {fw_name} consumes APIs",
545
+ )
546
+ )
547
+
548
+ # Component type signals
549
+ comp_type = project_context.get("component_type", "")
550
+ if comp_type:
551
+ if "frontend" in comp_type:
552
+ signals.append(
553
+ ActivationSignal(
554
+ source="context",
555
+ domain="xss-dom",
556
+ weight=0.7,
557
+ evidence="Frontend component type",
558
+ )
559
+ )
560
+ if "backend" in comp_type or "api" in comp_type:
561
+ signals.append(
562
+ ActivationSignal(
563
+ source="context",
564
+ domain="api-security",
565
+ weight=0.7,
566
+ evidence="Backend/API component type",
567
+ )
568
+ )
569
+ if "mobile" in comp_type:
570
+ signals.append(
571
+ ActivationSignal(
572
+ source="context",
573
+ domain="mobile-security",
574
+ weight=0.7,
575
+ evidence="Mobile component type",
576
+ )
577
+ )
578
+ if "infra" in comp_type or "devops" in comp_type:
579
+ signals.append(
580
+ ActivationSignal(
581
+ source="context",
582
+ domain="container-security",
583
+ weight=0.6,
584
+ evidence="Infrastructure component type",
585
+ )
586
+ )
587
+ signals.append(
588
+ ActivationSignal(
589
+ source="context",
590
+ domain="kubernetes-security",
591
+ weight=0.5,
592
+ evidence="Infrastructure component type",
593
+ )
594
+ )
595
+
596
+ # Active security domains from brain
597
+ active_domains = project_context.get("active_security_domains", [])
598
+ for domain in active_domains:
599
+ signals.append(
600
+ ActivationSignal(
601
+ source="context",
602
+ domain=domain,
603
+ weight=0.8,
604
+ evidence=f"Previously activated domain: {domain}",
605
+ )
606
+ )
607
+
608
+ # Deployment model
609
+ deploy_model = project_context.get("deployment_model", "")
610
+ if deploy_model in ("kubernetes", "k8s", "container"):
611
+ signals.append(
612
+ ActivationSignal(
613
+ source="context",
614
+ domain="kubernetes-security",
615
+ weight=0.7,
616
+ evidence=f"Deployment model: {deploy_model}",
617
+ )
618
+ )
619
+ signals.append(
620
+ ActivationSignal(
621
+ source="context",
622
+ domain="container-security",
623
+ weight=0.7,
624
+ evidence=f"Deployment model: {deploy_model}",
625
+ )
626
+ )
627
+ elif deploy_model in ("serverless", "lambda", "cloudflare", "vercel"):
628
+ signals.append(
629
+ ActivationSignal(
630
+ source="context",
631
+ domain="cloud-security",
632
+ weight=0.7,
633
+ evidence=f"Deployment model: {deploy_model}",
634
+ )
635
+ )
636
+
637
+ return signals
638
+
639
+ def _extract_history_signals(self) -> list[ActivationSignal]:
640
+ signals = []
641
+
642
+ # Check past scan results for recurring findings
643
+ scan_results = mem.get_scan_results(self.root)
644
+ domain_finding_counts: dict[str, int] = {}
645
+
646
+ for _scanner_name, data in scan_results.items():
647
+ for finding in data.get("findings", []):
648
+ if isinstance(finding, dict):
649
+ finding_type = finding.get("type", "")
650
+ # Map finding types to domains
651
+ domain = self._map_finding_to_domain(finding_type)
652
+ if domain:
653
+ domain_finding_counts[domain] = domain_finding_counts.get(domain, 0) + 1
654
+
655
+ # If a domain had findings before, it's more likely to be relevant
656
+ for domain, count in domain_finding_counts.items():
657
+ if count >= 2: # At least 2 historical findings
658
+ signals.append(
659
+ ActivationSignal(
660
+ source="history",
661
+ domain=domain,
662
+ weight=min(0.3 + count * 0.1, 0.8),
663
+ evidence=f"{count} historical findings in {domain}",
664
+ metadata={"historical_count": count},
665
+ )
666
+ )
667
+
668
+ return signals
669
+
670
+ def _map_finding_to_domain(self, finding_type: str) -> str | None:
671
+ """Map finding type to security domain."""
672
+ type_lower = finding_type.lower()
673
+
674
+ mapping = {
675
+ "sql_injection": "injection-sql",
676
+ "nosql_injection": "injection-nosql",
677
+ "command_injection": "injection-command",
678
+ "ldap_injection": "injection-ldap",
679
+ "xpath_injection": "injection-xpath",
680
+ "template_injection": "injection-template",
681
+ "xss": "xss-reflected",
682
+ "xss_reflected": "xss-reflected",
683
+ "xss_stored": "xss-stored",
684
+ "xss_dom": "xss-dom",
685
+ "csrf": "csrf",
686
+ "cors": "cors",
687
+ "ssrf": "ssrf",
688
+ "path_traversal": "path-traversal",
689
+ "deserialization": "deserialization",
690
+ "xxe": "xxe",
691
+ "jwt": "jwt-security",
692
+ "auth": "authentication",
693
+ "authz": "authorization",
694
+ "session": "session-management",
695
+ "secret": "secrets-management",
696
+ "crypto": "cryptography",
697
+ "tls": "tls-ssl",
698
+ "supply_chain": "supply-chain",
699
+ "dependency": "dependency-vulnerability",
700
+ "container": "container-security",
701
+ "kubernetes": "kubernetes-security",
702
+ "cloud": "cloud-security",
703
+ "runtime": "runtime-protection",
704
+ "privacy": "privacy-gdpr",
705
+ "business_logic": "business-logic",
706
+ "api": "api-security",
707
+ "audit": "audit-logging",
708
+ }
709
+
710
+ for key, domain in mapping.items():
711
+ if key in type_lower:
712
+ return domain
713
+ return None
714
+
715
+ def _get_file_content(self, file_path: str) -> str | None:
716
+ """Get file content with caching."""
717
+ if file_path in self._code_cache:
718
+ return self._code_cache[file_path]
719
+
720
+ full_path = self.root / file_path
721
+ if not full_path.exists():
722
+ return None
723
+
724
+ try:
725
+ content = full_path.read_text(encoding="utf-8", errors="ignore")
726
+ # Cache only smaller files to avoid memory issues
727
+ if len(content) < 100_000:
728
+ self._code_cache[file_path] = content
729
+ return content
730
+ except Exception:
731
+ return None
732
+
733
+
734
+ # ── Domain Scoring & Activation ──────────────────────────────────────────────
735
+
736
+
737
+ class DomainActivatorV2:
738
+ """Main domain activation engine."""
739
+
740
+ def __init__(
741
+ self,
742
+ root: Path,
743
+ activation_threshold: float = 0.5,
744
+ max_domains: int = 20,
745
+ domain_loader=None,
746
+ ):
747
+ self.root = root
748
+ self.activation_threshold = activation_threshold
749
+ self.max_domains = max_domains
750
+ self.extractor = SignalExtractor(root)
751
+ # Callers that preloaded the taxonomy in the background (CLI/web scan)
752
+ # inject it here so activation never blocks on a cold DomainLoader.
753
+ self.domain_loader = domain_loader or DomainLoader(root)
754
+
755
+ def activate_domains(
756
+ self,
757
+ file_infos: list[FileInfo],
758
+ project_context: dict,
759
+ forced_domains: list[str] = None,
760
+ ) -> DomainActivationResult:
761
+ """
762
+ Activate security domains based on extracted signals.
763
+
764
+ Args:
765
+ file_infos: Scanned file information
766
+ project_context: Project context from brain
767
+ forced_domains: Domains to force-activate (e.g., from user config)
768
+
769
+ Returns:
770
+ DomainActivationResult with activated domains and scoring details
771
+ """
772
+ forced_domains = forced_domains or []
773
+
774
+ # Extract all signals
775
+ signals = self.extractor.extract_all_signals(file_infos, project_context)
776
+
777
+ # Score each domain
778
+ domain_scores: dict[str, float] = {}
779
+ domain_evidence: dict[str, list[str]] = {}
780
+
781
+ for signal in signals:
782
+ domain = signal.domain
783
+ domain_scores[domain] = domain_scores.get(domain, 0) + signal.weight
784
+ if domain not in domain_evidence:
785
+ domain_evidence[domain] = []
786
+ domain_evidence[domain].append(signal.evidence)
787
+
788
+ # Add forced domains with high score
789
+ for domain in forced_domains:
790
+ domain_scores[domain] = max(domain_scores.get(domain, 0), 1.0)
791
+ if domain not in domain_evidence:
792
+ domain_evidence[domain] = []
793
+ domain_evidence[domain].append("Forced activation (user/config)")
794
+
795
+ # Filter by threshold
796
+ activated = [
797
+ domain for domain, score in domain_scores.items() if score >= self.activation_threshold
798
+ ]
799
+
800
+ # Sort by score descending
801
+ activated.sort(key=lambda d: domain_scores[d], reverse=True)
802
+
803
+ # Limit to max_domains
804
+ if len(activated) > self.max_domains:
805
+ activated[self.max_domains :]
806
+ activated = activated[: self.max_domains]
807
+ else:
808
+ pass
809
+
810
+ # Get all known domains for reporting skipped
811
+ all_known = set(domain_scores.keys())
812
+ all_known.update(self.domain_loader.list_domains())
813
+ skipped_domains = sorted(all_known - set(activated))
814
+
815
+ _log.info(f"Activated {len(activated)} domains (threshold={self.activation_threshold})")
816
+ for d in activated:
817
+ _log.debug(f" {d}: score={domain_scores[d]:.2f}, evidence={domain_evidence[d][:2]}")
818
+
819
+ return DomainActivationResult(
820
+ activated_domains=activated,
821
+ signals=signals,
822
+ domain_scores=domain_scores,
823
+ skipped_domains=skipped_domains,
824
+ activation_threshold=self.activation_threshold,
825
+ )
826
+
827
+ def get_activated_domain_objects(self, activated_domains: list[str]) -> dict[str, Domain]:
828
+ """Load full Domain objects for activated domains."""
829
+ domains = {}
830
+ for domain_id in activated_domains:
831
+ domain = self.domain_loader.get_domain(domain_id)
832
+ if domain:
833
+ domains[domain_id] = domain
834
+ else:
835
+ _log.warning(f"Activated domain not found in loader: {domain_id}")
836
+ return domains
837
+
838
+ def get_relevant_agents(self, activated_domains: list[str]) -> list[str]:
839
+ """Map activated domains to relevant security agents."""
840
+ # Domain -> agent mapping
841
+ domain_agent_map = {
842
+ "injection-sql": ["InjectionAgent", "BanditAgent", "SemgrepAgent"],
843
+ "injection-nosql": ["InjectionAgent", "SemgrepAgent"],
844
+ "injection-command": ["InjectionAgent", "BanditAgent"],
845
+ "injection-ldap": ["InjectionAgent"],
846
+ "injection-xpath": ["InjectionAgent"],
847
+ "injection-template": ["InjectionAgent", "SemgrepAgent"],
848
+ "xss-reflected": ["InjectionAgent", "SemgrepAgent", "BrowserTesterAgent"],
849
+ "xss-stored": ["InjectionAgent", "BrowserTesterAgent"],
850
+ "xss-dom": ["BrowserTesterAgent", "SemgrepAgent"],
851
+ "authentication": [
852
+ "AuthenticationAuditAgent",
853
+ "JWTSecurityAgent",
854
+ "SessionManagementAgent",
855
+ ],
856
+ "authorization": ["AuthZAgent", "BusinessLogicAgent"],
857
+ "session-management": ["SessionManagementAgent", "JWTSecurityAgent"],
858
+ "jwt-security": ["JWTSecurityAgent", "AuthenticationAuditAgent"],
859
+ "secrets-management": ["SecretScanner", "SecretsGuard", "SecretsRuntimeAgent"],
860
+ "cryptography": ["CryptoAgent", "BanditAgent"],
861
+ "tls-ssl": ["CryptoAgent", "HeaderAuditAgent"],
862
+ "ssrf": ["SSRFProtectionAgent", "NetworkAgent"],
863
+ "path-traversal": ["InjectionAgent", "SemgrepAgent"],
864
+ "deserialization": ["InjectionAgent", "BanditAgent", "SemgrepAgent"],
865
+ "xxe": ["InjectionAgent", "SemgrepAgent"],
866
+ "cors": ["CORSAuditor", "SecurityProber"],
867
+ "csrf": ["SecurityProber", "SemgrepAgent"],
868
+ "security-headers": ["HeaderAuditAgent", "SecurityConfigAgent"],
869
+ "rate-limiting": ["RateLimitAuditor", "SecurityConfigAgent"],
870
+ "supply-chain": ["SupplyChainAgent", "DependencyVulnerabilityAgent", "CVEMonitorAgent"],
871
+ "dependency-vulnerability": [
872
+ "DependencyVulnerabilityAgent",
873
+ "CVEMonitorAgent",
874
+ "OSVScannerAgent",
875
+ ],
876
+ "container-security": ["ContainerScannerAgent", "IaCScannerAgent"],
877
+ "kubernetes-security": ["KubernetesAgent", "IaCScannerAgent"],
878
+ "cloud-security": ["IaCScannerAgent", "ConfigAuditAgent"],
879
+ "runtime-protection": ["FalcoRuntimeAgent", "RuntimeValidatorAgent"],
880
+ "privacy-gdpr": ["PrivacyAgent", "SensitiveDataAgent"],
881
+ "business-logic": ["BusinessLogicAgent"],
882
+ "api-security": ["SecurityProber", "APIContractAgent"],
883
+ "audit-logging": ["HistoryAgent", "GovernanceAgent"],
884
+ }
885
+
886
+ agents = set()
887
+ for domain in activated_domains:
888
+ agents.update(domain_agent_map.get(domain, []))
889
+
890
+ # Always include core agents
891
+ core_agents = ["RedTeamAgent", "PreCheckAgent", "PlanAuditorAgent"]
892
+ agents.update(core_agents)
893
+
894
+ return sorted(agents)
895
+
896
+
897
+ # ── Convenience Function ─────────────────────────────────────────────────────
898
+
899
+
900
+ def activate_security_domains(
901
+ root: Path,
902
+ file_infos: list[FileInfo],
903
+ project_context: dict,
904
+ config: dict,
905
+ ) -> DomainActivationResult:
906
+ """Convenience function for domain activation."""
907
+ # Get activation threshold from config
908
+ threshold = config.get("security", {}).get("domain_activation_threshold", 0.5)
909
+ max_domains = config.get("security", {}).get("max_active_domains", 20)
910
+ forced = config.get("security", {}).get("forced_domains", [])
911
+
912
+ activator = DomainActivatorV2(root, threshold, max_domains)
913
+ return activator.activate_domains(file_infos, project_context, forced)