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,476 @@
1
+ """Secrets runtime management agent — validates secrets handling, rotation, and hardcoding."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ import re
7
+ import shutil
8
+ import subprocess
9
+
10
+ from ..agents.base import (
11
+ AgentDomain,
12
+ AgentGroup,
13
+ AgentInput,
14
+ AgentResult,
15
+ AgentStatus,
16
+ BaseAgent,
17
+ Finding,
18
+ Severity,
19
+ make_finding,
20
+ register,
21
+ safe_rglob,
22
+ )
23
+ from ..brain.trace_log import trace_agent
24
+
25
+ # Secrets provider detection patterns
26
+ SECRETS_PROVIDERS = {
27
+ "hashicorp_vault": {
28
+ "import": r"import\s+hvac|from\s+hvac\b|import\s+vault|from\s+vault\b|vault\.client|vault\.Vault",
29
+ "config": r"VAULT_ADDR|VAULT_TOKEN|vault\.addr|vault\.token|VaultClient",
30
+ "lease": r"lease|renew|revoke|ttl|max_ttl",
31
+ },
32
+ "aws_secrets_manager": {
33
+ "import": r"import\s+boto3|from\s+boto3\b|aws.*secrets|secretsmanager",
34
+ "client": r"boto3\.client.*secretsmanager|SecretsManagerClient|get_secret_value",
35
+ "rotation": r"rotation|rotate.*secret|SecretRotation",
36
+ },
37
+ "aws_ssm": {
38
+ "import": r"import\s+boto3|from\s+boto3\b",
39
+ "client": r"ssm\.|SSM|get_parameter|ParameterStore",
40
+ },
41
+ "azure_key_vault": {
42
+ "import": r"from\s+azure\.keyvault|import\s+azure\.keyvault|KeyVaultClient",
43
+ "config": r"AZURE_KEYVAULT|KeyVaultSecret|vault_url",
44
+ },
45
+ "gcp_secret_manager": {
46
+ "import": r"from\s+google\.cloud.*secretmanager|import\s+google\.cloud.*secretmanager",
47
+ "client": r"SecretManagerServiceClient|access_secret_version",
48
+ },
49
+ "dotenv": {
50
+ "import": r"from\s+dotenv|import\s+dotenv|load_dotenv|dotenv_values",
51
+ "usage": r"os\.environ|os\.getenv|ENV\[|environ\[",
52
+ },
53
+ }
54
+
55
+ # Hardcoded secret patterns (merged from SecretsGuard)
56
+ HARDCODED_SECRET_PATTERNS = [
57
+ (r"(?:password|passwd|pwd)\s*=\s*[\"'][^\"']+[\"']", "Hardcoded password", Severity.CRITICAL),
58
+ (
59
+ r"(?:api[_-]?key|apikey)\s*=\s*[\"'][A-Za-z0-9_\-]{16,}[\"']",
60
+ "Hardcoded API key",
61
+ Severity.CRITICAL,
62
+ ),
63
+ (
64
+ r"(?:secret|token)\s*=\s*[\"'][A-Za-z0-9_\-]{16,}[\"']",
65
+ "Hardcoded secret/token",
66
+ Severity.CRITICAL,
67
+ ),
68
+ (
69
+ r"(?:aws[_-]?access[_-]?key[_-]?id)\s*=\s*[\"']AKIA[0-9A-Z]{16}[\"']",
70
+ "Hardcoded AWS access key",
71
+ Severity.CRITICAL,
72
+ ),
73
+ (
74
+ r"(?:aws[_-]?secret[_-]?access[_-]?key)\s*=\s*[\"'][0-9a-zA-Z/+=]{40}[\"']",
75
+ "Hardcoded AWS secret key",
76
+ Severity.CRITICAL,
77
+ ),
78
+ (r"(?:private[_-]?key)\s*=\s*[\"']-----BEGIN", "Hardcoded private key", Severity.CRITICAL),
79
+ (
80
+ r"(?:connection[_-]?string|dsn)\s*=\s*[\"'][^\"']*(?:password|pwd)=[^\"']+[\"']",
81
+ "Hardcoded connection string with password",
82
+ Severity.HIGH,
83
+ ),
84
+ # Extended patterns from SecretsGuard
85
+ (
86
+ re.compile(
87
+ r'(?:api[_-]?key|apikey|secret[_-]?key|access[_-]?key)\s*[:=]\s*["\'][A-Za-z0-9+/=_-]{16,}["\']',
88
+ re.I,
89
+ ),
90
+ Severity.CRITICAL,
91
+ "Hardcoded API key (extended)",
92
+ ),
93
+ (re.compile(r'(?:password|passwd|pwd)\s*[:=]\s*["\'][^"\']{4,}["\']', re.I), Severity.CRITICAL, "Hardcoded password (extended)"),
94
+ (re.compile(r'(?:secret|client[_-]?secret)\s*[:=]\s*["\'][A-Za-z0-9+/=_-]{8,}["\']', re.I), Severity.CRITICAL, "Hardcoded secret (extended)"),
95
+ (re.compile(r"AKIA[0-9A-Z]{16}"), Severity.CRITICAL, "AWS Access Key ID"),
96
+ (re.compile(r'["\']AIza[0-9A-Za-z_-]{35}["\']'), Severity.CRITICAL, "GCP API key"),
97
+ (re.compile(r"-----BEGIN\s+(?:RSA\s+)?PRIVATE\s+KEY-----", re.I), Severity.CRITICAL, "Private key"),
98
+ (re.compile(r"(?:mongodb|mysql|postgres|redis|amqp|smtp)://[^:]+:[^@]+@", re.I), Severity.HIGH, "Connection string with credentials"),
99
+ (re.compile(r'DATABASE_URL\s*[:=]\s*["\'][^"\']+:.*@', re.I), Severity.HIGH, "DATABASE_URL with password"),
100
+ ]
101
+
102
+ # Core dump and memory protection patterns
103
+ COREDUMP_PATTERNS = [
104
+ (r"core.*dump|coredump|CORE_PATTERN", "Core dump configuration"),
105
+ (r"ulimit.*-c|RLIMIT_CORE", "Core dump size limit"),
106
+ (r"/proc/sys/kernel/core_pattern", "Core dump pattern"),
107
+ ]
108
+
109
+ CONTROL_IDS = {
110
+ "SECRETS-01": ("Hardcoded credentials in source", Severity.CRITICAL),
111
+ "SECRETS-02": ("Secrets in environment variables", Severity.MEDIUM),
112
+ "SECRETS-03": ("Missing lease renewal", Severity.HIGH),
113
+ "SECRETS-04": ("Secrets provider detected", Severity.INFO),
114
+ "SECRETS-05": ("Core dump exposure risk", Severity.HIGH),
115
+ "SECRETS-06": ("Secrets caching TTL misconfigured", Severity.MEDIUM),
116
+ }
117
+
118
+ # Config/CI file patterns from SecretsGuard
119
+ CONFIG_CI_PATTERNS = [
120
+ ".env*",
121
+ "*.yml",
122
+ "*.yaml",
123
+ "*.toml",
124
+ "*.ini",
125
+ "*.cfg",
126
+ "Dockerfile*",
127
+ "docker-compose*.yml",
128
+ "*.conf",
129
+ "**/.github/**",
130
+ "**/.gitlab-ci*",
131
+ "**/Jenkinsfile*",
132
+ ]
133
+
134
+
135
+ def scan_code_for_secrets(code: str, file_path: str) -> list[dict]:
136
+ """Scan a code string for secrets. Returns list of dicts with line, severity, message."""
137
+ findings = []
138
+ lines = code.splitlines()
139
+ for i, line in enumerate(lines, 1):
140
+ for pattern in HARDCODED_SECRET_PATTERNS:
141
+ if isinstance(pattern, tuple):
142
+ # Handle two formats: (regex, desc, severity) and (regex, severity, desc)
143
+ if len(pattern) == 3:
144
+ p0, p1, p2 = pattern
145
+ # If p1 is Severity enum, format is (regex, severity, desc)
146
+ if isinstance(p1, Severity):
147
+ regex, severity, desc = p0, p1, p2
148
+ else:
149
+ # Format is (regex, desc, severity)
150
+ regex, desc, severity = p0, p1, p2
151
+ else:
152
+ continue
153
+
154
+ if isinstance(regex, str):
155
+ regex = re.compile(regex, re.I)
156
+ if regex.search(line):
157
+ findings.append(
158
+ {
159
+ "line": i,
160
+ "severity": severity.value,
161
+ "message": desc,
162
+ "evidence": line.strip()[:100],
163
+ "file": file_path,
164
+ }
165
+ )
166
+ return findings
167
+
168
+
169
+ def gate_check_proposed_code(proposed_code: str, file_path: str) -> tuple[bool, list[dict]]:
170
+ """Check if proposed code introduces new secrets. Returns (safe, findings)."""
171
+ findings = scan_code_for_secrets(proposed_code, file_path)
172
+ return len(findings) == 0, findings
173
+
174
+
175
+ _log = logging.getLogger("patchi.security.secrets_runtime_agent")
176
+
177
+
178
+ @register
179
+ class SecretsRuntimeAgent(BaseAgent):
180
+ name = "SecretsRuntimeAgent"
181
+ group = AgentGroup.SECURITY
182
+ domain = AgentDomain.SECURITY
183
+ description = "Secrets runtime management: lease renewal, file-mount vs env var, dynamic secrets, caching TTL"
184
+
185
+ def _run(self, inp: AgentInput, result: AgentResult) -> None:
186
+ with trace_agent(self.name, inp.root) as trace:
187
+ findings: list[Finding] = []
188
+ files_scanned = 0
189
+
190
+ # Phase 1: Static analysis of source code
191
+ for fpath in safe_rglob(inp.root, "*.py"):
192
+ files_scanned += 1
193
+ try:
194
+ content = fpath.read_text(encoding="utf-8", errors="replace")
195
+ except Exception as e:
196
+ _log.warning("SecretsRuntimeAgent._run failed: %s", e)
197
+ continue
198
+
199
+ rel = str(fpath.relative_to(inp.root))
200
+
201
+ # Detect hardcoded secrets
202
+ for pattern, desc, severity in HARDCODED_SECRET_PATTERNS:
203
+ for m in re.finditer(pattern, content, re.IGNORECASE):
204
+ line_no = content[: m.start()].count("\n") + 1
205
+ # Skip if it's in a test file or comment
206
+ if re.search(
207
+ r"#.*" + re.escape(m.group()),
208
+ content.split("\n")[line_no - 1 : line_no][0],
209
+ ):
210
+ continue
211
+ findings.append(
212
+ make_finding(
213
+ severity,
214
+ rel,
215
+ line_no,
216
+ f"SECRETS-01: {desc}",
217
+ f"Found {desc.lower()} in source code. Secrets should never be hardcoded.",
218
+ code_snippet=m.group()[:80]
219
+ + ("..." if len(m.group()) > 80 else ""),
220
+ suggestion="Use a secrets manager or environment variables with proper secret rotation",
221
+ cwe="CWE-798",
222
+ control_id="SECRETS-01",
223
+ )
224
+ )
225
+
226
+ # Detect secrets provider usage
227
+ provider_found = None
228
+ for provider_name, provider_patterns in SECRETS_PROVIDERS.items():
229
+ for _pattern_key, pattern in provider_patterns.items():
230
+ if re.search(pattern, content, re.IGNORECASE):
231
+ provider_found = provider_name
232
+ break
233
+ if provider_found:
234
+ break
235
+
236
+ if provider_found:
237
+ findings.append(
238
+ make_finding(
239
+ Severity.INFO,
240
+ rel,
241
+ 0,
242
+ f"SECRETS-04: Secrets provider detected: {provider_found}",
243
+ f"Code uses {provider_found} for secrets management. Verify proper configuration.",
244
+ suggestion=f"Review {provider_name} configuration for security best practices",
245
+ control_id="SECRETS-04",
246
+ )
247
+ )
248
+
249
+ # Check for secrets in environment variables (os.environ usage)
250
+ env_usage = re.findall(r"os\.environ(?:\.get|\.pop|\[)|os\.getenv\(", content)
251
+ if env_usage:
252
+ # Check if secrets-related env vars are accessed
253
+ for m in re.finditer(r"os\.environ(?:\.get|\.pop|\[)|os\.getenv\(", content):
254
+ line_no = content[: m.start()].count("\n") + 1
255
+ line_text = content.split("\n")[line_no - 1 : line_no][0]
256
+ # Check if the env var name contains secret-related keywords
257
+ secret_keywords = [
258
+ "password",
259
+ "secret",
260
+ "token",
261
+ "key",
262
+ "credential",
263
+ "auth",
264
+ ]
265
+ if any(kw in line_text.lower() for kw in secret_keywords):
266
+ findings.append(
267
+ make_finding(
268
+ Severity.MEDIUM,
269
+ rel,
270
+ line_no,
271
+ "SECRETS-02: Secret accessed via environment variable",
272
+ "Secrets in environment variables may be exposed via /proc, logs, or crash dumps. Consider file mounts.",
273
+ code_snippet=line_text.strip()[:100],
274
+ suggestion="Use file-mounted secrets (e.g., Kubernetes secrets, Vault file backend) instead of env vars for sensitive values",
275
+ control_id="SECRETS-02",
276
+ )
277
+ )
278
+
279
+ # Check for lease renewal / TTL handling
280
+ has_vault = re.search(
281
+ r"hvac|vault\.client|VAULT_ADDR|VaultClient", content, re.IGNORECASE
282
+ )
283
+ if has_vault:
284
+ has_lease = re.search(r"lease|renew|revoke|ttl|max_ttl", content, re.IGNORECASE)
285
+ if not has_lease:
286
+ findings.append(
287
+ make_finding(
288
+ Severity.HIGH,
289
+ rel,
290
+ 0,
291
+ "SECRETS-03: Missing Vault lease renewal",
292
+ "Vault client detected without lease renewal logic. Secrets may expire and cause outages.",
293
+ suggestion="Implement lease renewal and automatic re-authentication",
294
+ control_id="SECRETS-03",
295
+ )
296
+ )
297
+
298
+ # Check for caching TTL
299
+ has_cache = re.search(
300
+ r"cache|lru_cache|functools\.cache|TTLCache|cachetools", content, re.IGNORECASE
301
+ )
302
+ if has_cache and has_vault:
303
+ has_ttl = re.search(
304
+ r"ttl|expire|timeout|max_age|maxsize", content, re.IGNORECASE
305
+ )
306
+ if not has_ttl:
307
+ findings.append(
308
+ make_finding(
309
+ Severity.MEDIUM,
310
+ rel,
311
+ 0,
312
+ "SECRETS-06: Secret caching without TTL",
313
+ "Secrets may be cached without expiration. Stale secrets could be used after rotation.",
314
+ suggestion="Set appropriate TTL on cached secrets and invalidate on rotation events",
315
+ control_id="SECRETS-06",
316
+ )
317
+ )
318
+
319
+ # Check for core dump exposure
320
+ for pattern, desc in COREDUMP_PATTERNS:
321
+ if re.search(pattern, content, re.IGNORECASE):
322
+ # Check if secrets exist in the same file
323
+ has_secrets = re.search(
324
+ r"password|secret|token|key|credential",
325
+ content,
326
+ re.IGNORECASE,
327
+ )
328
+ if has_secrets:
329
+ findings.append(
330
+ make_finding(
331
+ Severity.HIGH,
332
+ rel,
333
+ 0,
334
+ "SECRETS-05: Core dump may expose secrets",
335
+ f"Core dump configuration found ({desc}) in code that handles secrets. Secrets may leak in core dumps.",
336
+ suggestion="Disable core dumps or ensure secrets are not in memory during crash-prone operations",
337
+ cwe="CWE-244",
338
+ control_id="SECRETS-05",
339
+ )
340
+ )
341
+
342
+ # Phase 2: Check configuration files (extended from SecretsGuard)
343
+ scanned = 0
344
+ for pattern in CONFIG_CI_PATTERNS:
345
+ for fpath in safe_rglob(inp.root, pattern):
346
+ if not fpath.is_file():
347
+ continue
348
+ files_scanned += 1
349
+ try:
350
+ content = fpath.read_text(encoding="utf-8", errors="replace")
351
+ except OSError:
352
+ continue
353
+ rel = fpath.relative_to(inp.root).as_posix()
354
+ scanned += 1
355
+ secrets = scan_code_for_secrets(content, rel)
356
+ for s in secrets:
357
+ findings.append(
358
+ make_finding(
359
+ Severity(s["severity"]),
360
+ s["file"],
361
+ s["line"],
362
+ s["message"],
363
+ code_snippet=s["evidence"],
364
+ cwe="CWE-798",
365
+ control_id="SECRETS-01",
366
+ )
367
+ )
368
+
369
+ # Phase 3: Try gitleaks for hardcoded secrets detection
370
+ gitleaks_path = shutil.which("gitleaks")
371
+ if gitleaks_path:
372
+ try:
373
+ proc = subprocess.run(
374
+ [
375
+ gitleaks_path,
376
+ "detect",
377
+ "--source",
378
+ str(inp.root),
379
+ "--report-format",
380
+ "json",
381
+ "--no-banner",
382
+ ],
383
+ capture_output=True,
384
+ text=True,
385
+ timeout=120,
386
+ )
387
+ if proc.stdout:
388
+ import json
389
+
390
+ try:
391
+ data = json.loads(proc.stdout)
392
+ for item in data:
393
+ findings.append(
394
+ make_finding(
395
+ Severity.CRITICAL,
396
+ item.get("File", ""),
397
+ item.get("StartLine", 0),
398
+ "SECRETS-01: Hardcoded secret (gitleaks)",
399
+ f"Gitleaks detected: {item.get('Description', 'Unknown secret')}",
400
+ code_snippet=item.get("Match", "")[:100],
401
+ suggestion="Remove hardcoded secret and use secrets manager",
402
+ cwe="CWE-798",
403
+ control_id="SECRETS-01",
404
+ )
405
+ )
406
+ except json.JSONDecodeError:
407
+ pass
408
+ except (subprocess.TimeoutExpired, Exception):
409
+ pass
410
+
411
+ # Phase 4: Try semgrep for secrets patterns
412
+ semgrep_path = shutil.which("semgrep")
413
+ if semgrep_path:
414
+ from .security_config import _semgrep_config_value
415
+ semgrep_config = _semgrep_config_value(inp.root)
416
+ if not semgrep_config:
417
+ semgrep_path = None # offline + no local pack -> skip
418
+ if semgrep_path:
419
+ try:
420
+ proc = subprocess.run(
421
+ [
422
+ semgrep_path,
423
+ "--config",
424
+ semgrep_config,
425
+ "--include",
426
+ "*.py",
427
+ "--json",
428
+ "--quiet",
429
+ "--max-target-bytes",
430
+ "100000",
431
+ str(inp.root),
432
+ ],
433
+ capture_output=True,
434
+ text=True,
435
+ timeout=120,
436
+ )
437
+ if proc.returncode == 0 and proc.stdout:
438
+ import json
439
+
440
+ try:
441
+ data = json.loads(proc.stdout)
442
+ for r in data.get("results", []):
443
+ rule_id = r.get("check_id", "")
444
+ if any(
445
+ kw in rule_id.lower()
446
+ for kw in [
447
+ "secret",
448
+ "password",
449
+ "credential",
450
+ "hardcoded",
451
+ "key",
452
+ ]
453
+ ):
454
+ findings.append(
455
+ make_finding(
456
+ Severity.HIGH,
457
+ r.get("path", ""),
458
+ r.get("start", {}).get("line", 0),
459
+ f"Semgrep: {rule_id}",
460
+ r.get("extra", {}).get("message", ""),
461
+ code_snippet=r.get("extra", {}).get("lines", ""),
462
+ suggestion="Review semgrep finding for secrets security impact",
463
+ )
464
+ )
465
+ except json.JSONDecodeError:
466
+ pass
467
+ except (subprocess.TimeoutExpired, Exception):
468
+ pass
469
+
470
+ result.findings = findings
471
+ result.files_scanned = files_scanned
472
+ result.status = AgentStatus.DONE
473
+ trace.findings = len(findings)
474
+ trace.files_scanned = files_scanned
475
+
476
+ return
@@ -0,0 +1,175 @@
1
+ """
2
+ Security agents for Patchi.
3
+
4
+ All 54 agents are loaded lazily via _LazyAgent to ensure one failed import
5
+ does not crash the entire security module.
6
+
7
+ Tool wrappers:
8
+ - Bandit (Apache-2.0) — pip install bandit
9
+ - Semgrep CE (LGPL-2.1) — pip install semgrep
10
+ - Gitleaks (MIT) — binary from github.com/gitleaks/gitleaks/releases
11
+ - OSV-Scanner (Apache 2.0) — binary from github.com/google/osv-scanner/releases
12
+ - httpx (BSD) — pip install httpx (for dynamic CORS + header probes)
13
+ - Pysa (MIT) — pip install pyre-check fb-sapp
14
+ - CodeQL (Restricted) — binary from github.com/github/codeql-cli-binaries/releases
15
+
16
+ DO NOT use TruffleHog in hosted SaaS mode (AGPL-3.0).
17
+ Gitleaks is the correct secret scanner here (MIT).
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ import importlib
23
+ import logging
24
+
25
+ _log = logging.getLogger("patchi.security.agents")
26
+
27
+ _AGENT_MODULES: dict[str, str] = {
28
+ "AppMapperAgent": ".app_mapper",
29
+ "AuthenticationAuditAgent": ".auth_audit_agent",
30
+ "AuthZAgent": ".authz_agent",
31
+ "BanditAgent": ".bandit_agent",
32
+ "BlastRadiusAgent": ".blast_radius",
33
+ "BrowserTesterAgent": ".browser_tester",
34
+ "BusinessLogicAgent": ".business_logic_agent",
35
+ "CDNCacheSecurityAgent": ".cdn_cache_agent",
36
+ "CloudWAFDetector": ".cloud_waf_detector",
37
+ "ComplianceAgent": ".compliance_agent",
38
+ "CatchBlockAuditor": ".catch_block_auditor",
39
+ "ConfigAuditAgent": ".security_config",
40
+ "ContainerScannerAgent": ".container_scanner",
41
+ "CORSAuditor": ".security_probe",
42
+ "CodeqlAgent": ".codeql_agent",
43
+ "CryptoAgent": ".crypto_agent",
44
+ "CVEMonitorAgent": ".cve_monitor",
45
+ "DependencyCVEChecker": ".security_probe",
46
+ "DependencyVulnerabilityAgent": ".dependency_vulnerability_agent", "DASTAgent": ".dast_agent",
47
+ "DNSSecurityAgent": ".dns_security_agent",
48
+ "EmailAuthenticationAgent": ".email_authentication_agent",
49
+ "EvidenceAgent": ".evidence",
50
+ "FalcoRuntimeAgent": ".falco_runtime_agent",
51
+ "EnvVarValidator": ".env_var_validator",
52
+ "GovernanceAgent": ".governance",
53
+ "HeaderAuditAgent": ".security_config",
54
+ "HistoryAgent": ".history",
55
+ "IaCScannerAgent": ".iac_scanner",
56
+ "InsecureRandomnessAgent": ".insecure_randomness_agent",
57
+ "InjectionAgent": ".injection_agent",
58
+ "SemgrepAgent": ".sast_agent",
59
+ "JWTSecurityAgent": ".jwt_agent",
60
+ "KubernetesAgent": ".kubernetes_agent",
61
+ "LLMSecurityAgent": ".llm_security_agent",
62
+ "MisconfigAgent": ".misconfig_agent",
63
+ "MobileSecurityAgent": ".mobile_security_agent",
64
+ "NetworkAgent": ".network_agent",
65
+ "PlanAuditorAgent": ".plan_auditor",
66
+ "PolicyEngineAgent": ".policy_engine",
67
+ "PreCheckAgent": ".prechecks",
68
+ "PrivacyAgent": ".privacy_agent",
69
+ "PushNotificationAgent": ".push_notification_agent",
70
+ "PysaAgent": ".pysa_agent",
71
+ "RateLimitAuditor": ".security_config",
72
+ "RedTeamAgent": ".red_team_agent",
73
+ "RedTeamEngineAgent": ".red_team_engine",
74
+ "RuntimeValidatorAgent": ".runtime_validator",
75
+ "SamlSSOAgent": ".saml_sso_agent",
76
+ "SecretsRuntimeAgent": ".secrets_runtime_agent",
77
+ "SecretScanner": ".security_taint",
78
+ "SecurityProber": ".security_probe",
79
+ "SensitiveDataAgent": ".sensitive_data_agent",
80
+ "ServiceMeshAgent": ".service_mesh_agent",
81
+ "SessionManagementAgent": ".session_management_agent",
82
+ "SSRFProtectionAgent": ".ssrf_agent",
83
+ "SupplyChainAgent": ".supply_chain",
84
+ "TaintAnalyzer": ".security_taint",
85
+ "WebSocketSecurityAgent": ".websocket_security_agent",
86
+ }
87
+
88
+
89
+ class _LazyAgent:
90
+ """Lazily imports and caches a single agent class."""
91
+
92
+ def __init__(self, name: str, module_path: str):
93
+ self._name = name
94
+ self._module_path = module_path
95
+ self._cls = None
96
+ self._error = None
97
+
98
+ def get(self):
99
+ if self._cls is not None:
100
+ return self._cls
101
+ if self._error is not None:
102
+ return None
103
+ try:
104
+ mod = importlib.import_module(self._module_path, __package__)
105
+ self._cls = getattr(mod, self._name, None)
106
+ if self._cls is None:
107
+ self._error = f"Class {self._name} not found in {self._module_path}"
108
+ _log.warning(self._error)
109
+ return self._cls
110
+ except Exception as e:
111
+ self._error = f"Failed to import {self._name} from {self._module_path}: {e}"
112
+ _log.warning(self._error)
113
+ return None
114
+
115
+
116
+ def _make_proxy(name: str) -> _LazyAgent:
117
+ """Create a lazy proxy and register it in this module's globals."""
118
+ return _LazyAgent(name, _AGENT_MODULES[name])
119
+
120
+
121
+ class _LazyModuleProxy:
122
+ """Module-level __getattr__ that lazily resolves agent imports."""
123
+
124
+ def __init__(self):
125
+ self._agents: dict[str, _LazyAgent] = {name: _make_proxy(name) for name in _AGENT_MODULES}
126
+
127
+ def __getattr__(self, name: str):
128
+ if name in self._agents:
129
+ cls = self._agents[name].get()
130
+ if cls is None:
131
+ msg = f"Security agent {name} failed to import (see logs)"
132
+ raise ImportError(msg)
133
+ return cls
134
+ msg = f"module {__name__!r} has no attribute {name!r}"
135
+ raise AttributeError(msg)
136
+
137
+ def __dir__(self):
138
+ return list(self._agents.keys())
139
+
140
+
141
+ _proxy = _LazyModuleProxy()
142
+
143
+ __all__ = sorted(_AGENT_MODULES.keys())
144
+
145
+
146
+ def __getattr__(name: str):
147
+ return getattr(_proxy, name)
148
+
149
+
150
+ # ── Eager registration pass ────────────────────────────────────────────────────
151
+ # The @register decorators only fire when each agent's module is actually
152
+ # imported. Without this pass, `import security_agents` would register ZERO
153
+ # agents (the lazy proxy defers everything), so Coordinator.run_group(SECURITY),
154
+ # `p security`, and `p agents list security` would silently run nothing.
155
+ # Each proxy.get() is individually exception-safe — one broken module logs a
156
+ # warning and is skipped, exactly the resilience the lazy design promised.
157
+ _registered = 0
158
+ _failures = 0
159
+ for _agent_name in sorted(_AGENT_MODULES):
160
+ try:
161
+ _cls = _proxy._agents[_agent_name].get()
162
+ if _cls is None:
163
+ _failures += 1
164
+ else:
165
+ _registered += 1
166
+ except Exception as e: # defensive: never let one agent block the rest
167
+ _failures += 1
168
+ _log.warning("security_agents eager registration failed for %s: %s", _agent_name, e)
169
+ if _failures:
170
+ _log.warning(
171
+ "security_agents: registered %d/%d agents (%d failed)",
172
+ _registered,
173
+ len(_AGENT_MODULES),
174
+ _failures,
175
+ )