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,50 @@
1
+ """Nuclei adapter — CVE/templates scan."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import logging
7
+ import shutil
8
+ import subprocess
9
+ import time
10
+ from pathlib import Path
11
+
12
+ from patchi.core.security.pentest.base import PentestResult, PentestTool
13
+
14
+ _log = logging.getLogger("patchi.pentest.nuclei")
15
+
16
+
17
+ class NucleiTool(PentestTool):
18
+ name = "nuclei"
19
+ binary = "nuclei"
20
+ docker_image = "projectdiscovery/nuclei:latest"
21
+
22
+ def run(self, target: str, safe_mode: bool = True, workspace: Path | None = None, extra: dict | None = None) -> PentestResult:
23
+ extra = extra or {}
24
+ templates = extra.get("templates", "cves") # cves|exposures|misconfiguration
25
+ t0 = time.monotonic()
26
+ if shutil.which("nuclei"):
27
+ cmd = ["nuclei", "-u", target, "-t", templates, "-jsonl", "-silent", "-nc"]
28
+ if safe_mode:
29
+ cmd += ["-rate-limit", "10"]
30
+ def runner(c): return subprocess.run(c, capture_output=True, text=True, timeout=300)
31
+ elif shutil.which("docker"):
32
+ cmd = ["docker", "run", "--rm", "projectdiscovery/nuclei:latest", "-u", target, "-t", templates, "-jsonl", "-silent", "-nc"]
33
+ def runner(c): return subprocess.run(c, capture_output=True, text=True, timeout=300)
34
+ else:
35
+ return PentestResult(tool=self.name, success=False, error="nuclei not available (install nuclei or docker)")
36
+ try:
37
+ proc = runner(cmd)
38
+ dur = int((time.monotonic() - t0) * 1000)
39
+ findings: list[dict] = []
40
+ for line in proc.stdout.splitlines()[:200]:
41
+ try:
42
+ j = json.loads(line)
43
+ findings.append({"template": j.get("templateID",""), "name": j.get("info",{}).get("name",""), "severity": j.get("info",{}).get("severity",""), "host": j.get("host",""), "matcher": j.get("matcher-name","")})
44
+ except Exception:
45
+ continue
46
+ return PentestResult(tool=self.name, success=True, evidence=f"nuclei {len(findings)} findings templates={templates}", findings=findings, raw_output=proc.stdout[:8000], duration_ms=dur)
47
+ except subprocess.TimeoutExpired:
48
+ return PentestResult(tool=self.name, success=False, error="nuclei timeout 5m", duration_ms=int((time.monotonic()-t0)*1000))
49
+ except Exception as exc: # noqa: BLE001
50
+ return PentestResult(tool=self.name, success=False, error=str(exc), duration_ms=int((time.monotonic()-t0)*1000))
@@ -0,0 +1,80 @@
1
+ """Pentest registry — which real tools are available and AI choice."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from pathlib import Path
7
+ from typing import Any
8
+
9
+ from patchi.core.security.pentest.base import PentestResult
10
+ from patchi.core.security.pentest.dalfox_adapter import DalfoxTool
11
+ from patchi.core.security.pentest.ffuf_adapter import FfufTool
12
+ from patchi.core.security.pentest.nuclei_adapter import NucleiTool
13
+ from patchi.core.security.pentest.shannon_adapter import ShannonTool
14
+ from patchi.core.security.pentest.sqlmap_adapter import SqlmapTool
15
+ from patchi.core.security.pentest.zap_adapter import ZapTool
16
+
17
+ _log = logging.getLogger("patchi.pentest.registry")
18
+
19
+
20
+ class PentestRegistry:
21
+ def __init__(self) -> None:
22
+ self._tools = {
23
+ "nuclei": NucleiTool(),
24
+ "sqlmap": SqlmapTool(),
25
+ "dalfox": DalfoxTool(),
26
+ "ffuf": FfufTool(),
27
+ "zap": ZapTool(),
28
+ "shannon": ShannonTool(),
29
+ }
30
+
31
+ def list(self) -> list[str]:
32
+ return list(self._tools.keys())
33
+
34
+ def available(self) -> dict[str, bool]:
35
+ return {k: v.is_available() for k, v in self._tools.items()}
36
+
37
+ def get(self, name: str):
38
+ return self._tools.get(name)
39
+
40
+ def run(self, name: str, target: str, safe_mode: bool = True, workspace: Path | None = None, extra: dict | None = None) -> PentestResult:
41
+ tool = self.get(name)
42
+ if not tool:
43
+ return PentestResult(tool=name, success=False, error=f"unknown tool {name}")
44
+ if not tool.is_available():
45
+ return PentestResult(tool=name, success=False, error=f"{name} not available (install {tool.binary} or docker)")
46
+ return tool.run(target, safe_mode=safe_mode, workspace=workspace, extra=extra or {})
47
+
48
+ def ai_pick(self, context: dict[str, Any], available_only: bool = True) -> list[dict[str, Any]]:
49
+ """One LLM call to pick 1-3 tools. Fallback heuristic if offline."""
50
+ import os
51
+
52
+ if os.environ.get("PATCHI_OFFLINE"):
53
+ # heuristic: sqli→sqlmap, xss→dalfox, cve→nuclei
54
+ domains = " ".join(context.get("active_domains", [])).lower()
55
+ picks: list[dict] = []
56
+ if "sql" in domains or "injection" in domains:
57
+ picks.append({"tool": "sqlmap", "reason": "injection domain active"})
58
+ if "xss" in domains:
59
+ picks.append({"tool": "dalfox", "reason": "xss domain"})
60
+ if not picks or "cve" in domains:
61
+ picks.append({"tool": "nuclei", "reason": "cve surface"})
62
+ if context.get("target_url") and context.get("use_shannon"):
63
+ picks = [{"tool": "shannon", "reason": "full chain requested"}] + picks
64
+ return picks[:2]
65
+ try:
66
+ from patchi.core.ai.client import call_ai_structured
67
+
68
+ cfg = context.get("config", {})
69
+ avail = self.available()
70
+ prompt = f"CONTEXT: routes={context.get('routes', [])[:5]} domains={context.get('active_domains', [])} target={context.get('target_url','-')} available={avail}\nPick 1-3 tools from {list(self._tools.keys())} with reason. Need live target for dalfox/nuclei/sqlmap/zap/shannon. No target→only code_scan. Return JSON {{\"picks\":[{{\"tool\":\"nuclei\",\"reason\":\"\"}}]}}"
71
+ system = "You are Patchi Red Team Lead. Pick minimal real tools to prove exploitable findings. Prefer nuclei for CVE, sqlmap for SQLi, dalfox for XSS, shannon for full authenticated chain. No exploit, no report."
72
+ data = call_ai_structured(cfg, system, prompt, max_tokens=400, temperature=0.2)
73
+ if isinstance(data, dict) and isinstance(data.get("picks"), list):
74
+ return [p for p in data["picks"] if isinstance(p, dict) and p.get("tool") in self._tools][:3]
75
+ except Exception as exc: # noqa: BLE001
76
+ _log.debug("ai_pick fallback: %s", exc)
77
+ # LLM failed — honor shannon intent if target+flag present
78
+ if context.get("use_shannon") and context.get("target_url"):
79
+ return [{"tool": "shannon", "reason": "fallback shannon requested"}]
80
+ return [{"tool": "nuclei", "reason": "fallback"}]
@@ -0,0 +1,107 @@
1
+ """Shannon adapter — Option 2: external npx, not vendored (AGPL-clean)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import logging
7
+ import shutil
8
+ import subprocess
9
+ import time
10
+ import uuid
11
+ from pathlib import Path
12
+
13
+ from patchi.core.security.pentest.base import PentestResult, PentestTool
14
+
15
+ _log = logging.getLogger("patchi.pentest.shannon")
16
+
17
+
18
+ class ShannonTool(PentestTool):
19
+ name = "shannon"
20
+ binary = "npx"
21
+ docker_image = "ghcr.io/keygraphhq/shannon-worker:latest"
22
+ requires_target = True
23
+
24
+ def is_available(self) -> bool:
25
+ if not shutil.which("npx"):
26
+ return False
27
+ if not shutil.which("docker"):
28
+ _log.debug("shannon: docker not found — still available via npx")
29
+ return True
30
+
31
+ def run(
32
+ self,
33
+ target: str,
34
+ safe_mode: bool = True,
35
+ workspace: Path | None = None,
36
+ extra: dict | None = None,
37
+ ) -> PentestResult:
38
+ extra = extra or {}
39
+ repo_root = extra.get("repo_root")
40
+ if not repo_root:
41
+ return PentestResult(tool=self.name, success=False, error="repo_root required for shannon")
42
+ repo_root = Path(repo_root)
43
+ ws = workspace or (repo_root / ".patchi" / "shannon" / uuid.uuid4().hex[:8])
44
+ ws.mkdir(parents=True, exist_ok=True)
45
+ t0 = time.monotonic()
46
+ # SAFETY: shannon mutates target — never run without explicit target + non-prod
47
+ if not target or target.strip() in ("", "http://", "https://"):
48
+ return PentestResult(tool=self.name, success=False, error="target URL required")
49
+ cmd = [
50
+ "npx",
51
+ "@keygraph/shannon@latest",
52
+ "start",
53
+ "-u",
54
+ target,
55
+ "-r",
56
+ str(repo_root),
57
+ "--workspace",
58
+ str(ws),
59
+ "--format",
60
+ "sarif",
61
+ ]
62
+ if safe_mode:
63
+ cmd += ["--safe-mode"]
64
+ _log.info("shannon: %s", " ".join(cmd))
65
+ try:
66
+ proc = subprocess.run(
67
+ cmd,
68
+ capture_output=True,
69
+ text=True,
70
+ timeout=5400, # 90m max (shannon 1-1.5h docs)
71
+ )
72
+ dur = int((time.monotonic() - t0) * 1000)
73
+ raw = (proc.stdout + proc.stderr)[:20000]
74
+ # Parse SARIF
75
+ findings: list[dict] = []
76
+ sarif_files = list(ws.rglob("*.sarif")) + list(ws.rglob("*.json"))
77
+ for sf in sarif_files:
78
+ try:
79
+ data = json.loads(sf.read_text(encoding="utf-8"))
80
+ # SARIF 2.1.0: runs[].results[]
81
+ for run in data.get("runs", []):
82
+ for res in run.get("results", []):
83
+ findings.append(
84
+ {
85
+ "ruleId": res.get("ruleId", ""),
86
+ "message": res.get("message", {}).get("text", "")[:400],
87
+ "level": res.get("level", "warning"),
88
+ "locations": res.get("locations", [])[:2],
89
+ }
90
+ )
91
+ except Exception as exc: # noqa: BLE001
92
+ _log.debug("shannon sarif parse %s failed: %s", sf, exc)
93
+ ok = proc.returncode == 0
94
+ ev = f"shannon {'ok' if ok else 'fail'} rc={proc.returncode} findings={len(findings)} ws={ws}"
95
+ return PentestResult(
96
+ tool=self.name,
97
+ success=ok,
98
+ evidence=ev,
99
+ findings=findings[:50],
100
+ raw_output=raw[:12000],
101
+ duration_ms=dur,
102
+ error="" if ok else raw[:500],
103
+ )
104
+ except subprocess.TimeoutExpired:
105
+ return PentestResult(tool=self.name, success=False, error="shannon timeout 90m", duration_ms=int((time.monotonic() - t0) * 1000))
106
+ except Exception as exc: # noqa: BLE001
107
+ return PentestResult(tool=self.name, success=False, error=str(exc), duration_ms=int((time.monotonic() - t0) * 1000))
@@ -0,0 +1,56 @@
1
+ """Sqlmap adapter — SQLi confirmation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ import shutil
7
+ import subprocess
8
+ import time
9
+ from pathlib import Path
10
+
11
+ from patchi.core.security.pentest.base import PentestResult, PentestTool
12
+
13
+ _log = logging.getLogger("patchi.pentest.sqlmap")
14
+
15
+
16
+ class SqlmapTool(PentestTool):
17
+ name = "sqlmap"
18
+ binary = "sqlmap"
19
+ docker_image = "paoloo/sqlmap:latest"
20
+
21
+ def run(self, target: str, safe_mode: bool = True, workspace: Path | None = None, extra: dict | None = None) -> PentestResult:
22
+ extra = extra or {}
23
+ data = extra.get("data", "") # POST body if needed
24
+ t0 = time.monotonic()
25
+ # Build base command
26
+ base = ["sqlmap", "-u", target, "--batch", "--risk", "1", "--level", "1", "--parse-errors"]
27
+ if safe_mode:
28
+ base += ["--technique", "BEUS"] # no destructive DROP etc, handled by sqlmap risk 1
29
+ if data:
30
+ base += ["--data", data]
31
+ if shutil.which("sqlmap"):
32
+ cmd = base
33
+ elif shutil.which("docker"):
34
+ cmd = ["docker", "run", "--rm", "paoloo/sqlmap:latest"] + base[1:]
35
+ else:
36
+ return PentestResult(tool=self.name, success=False, error="sqlmap not available")
37
+ try:
38
+ proc = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
39
+ dur = int((time.monotonic()-t0)*1000)
40
+ out = proc.stdout + proc.stderr
41
+ findings=[]
42
+ # Simple signal: sqlmap reports "is vulnerable" or lists DBMS
43
+ low = out.lower()
44
+ if "is vulnerable" in low or "parameter" in low and "vulnerable" in low:
45
+ findings.append({"type": "sqli", "target": target, "evidence": "sqlmap reports vulnerable"})
46
+ if "back-end dbms" in low:
47
+ # extract line
48
+ for line in out.splitlines():
49
+ if "back-end dbms" in line.lower():
50
+ findings.append({"type": "dbms-fingerprint", "detail": line.strip()[:200]})
51
+ break
52
+ return PentestResult(tool=self.name, success=True, evidence=f"sqlmap {len(findings)} signals", findings=findings, raw_output=out[:8000], duration_ms=dur)
53
+ except subprocess.TimeoutExpired:
54
+ return PentestResult(tool=self.name, success=False, error="sqlmap timeout 5m", duration_ms=int((time.monotonic()-t0)*1000))
55
+ except Exception as exc: # noqa: BLE001
56
+ return PentestResult(tool=self.name, success=False, error=str(exc), duration_ms=int((time.monotonic()-t0)*1000))
@@ -0,0 +1,52 @@
1
+ """ZAP adapter — active scan via zap-api (optional, needs daemon)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ import time
7
+ from pathlib import Path
8
+
9
+ from patchi.core.security.pentest.base import PentestResult, PentestTool
10
+
11
+ _log = logging.getLogger("patchi.pentest.zap")
12
+
13
+
14
+ class ZapTool(PentestTool):
15
+ name = "zap"
16
+ binary = "zap.sh"
17
+ docker_image = "ghcr.io/zaproxy/zaproxy:stable"
18
+
19
+ def is_available(self) -> bool:
20
+ # ZAP needs daemon at http://127.0.0.1:8090 or docker
21
+ try:
22
+ import urllib.request
23
+ urllib.request.urlopen("http://127.0.0.1:8090", timeout=2)
24
+ return True
25
+ except Exception as _exc:
26
+ _log.debug("zap daemon not reachable: %s", _exc)
27
+ return super().is_available()
28
+
29
+ def run(self, target: str, safe_mode: bool = True, workspace: Path | None = None, extra: dict | None = None) -> PentestResult:
30
+ t0 = time.monotonic()
31
+ try:
32
+ import json as _json
33
+ import urllib.request
34
+ # Quick spider + active scan via ZAP API if daemon
35
+ base = "http://127.0.0.1:8090/JSON"
36
+ # open URL
37
+ urllib.request.urlopen(f"{base}/core/action/accessUrl/?url={target}", timeout=10).read()
38
+ time.sleep(2)
39
+ # active scan
40
+ scan = _json.loads(urllib.request.urlopen(f"{base}/ascan/action/scan/?url={target}&recurse=true", timeout=30).read())
41
+ scan_id = scan.get("scan")
42
+ # poll
43
+ for _ in range(30):
44
+ time.sleep(5)
45
+ st = _json.loads(urllib.request.urlopen(f"{base}/ascan/view/status/?scanId={scan_id}", timeout=10).read())
46
+ if int(st.get("status", 0)) >= 100:
47
+ break
48
+ alerts = _json.loads(urllib.request.urlopen(f"{base}/core/view/alerts/?baseurl={target}", timeout=10).read())
49
+ findings = [{"alert": a.get("alert",""), "risk": a.get("risk",""), "url": a.get("url","")} for a in alerts.get("alerts", [])[:20]]
50
+ return PentestResult(tool=self.name, success=True, evidence=f"zap {len(findings)} alerts", findings=findings, duration_ms=int((time.monotonic()-t0)*1000))
51
+ except Exception as exc: # noqa: BLE001
52
+ return PentestResult(tool=self.name, success=False, error=f"zap not available: {exc}", duration_ms=int((time.monotonic()-t0)*1000))
@@ -0,0 +1,276 @@
1
+ """
2
+ Plan Auditor Agent — audits a Patchi checkout against build plan specs.
3
+
4
+ Only runs when the scanned root actually contains the Patchi package (a
5
+ `patchi/core/security` tree); against any third-party project the checks are
6
+ meaningless — every Patchi-internal path it references is absent by
7
+ construction, so emitting them would fabricate CRITICAL/HIGH findings.
8
+
9
+ Checks that:
10
+ - All agents registered in BUILD_PLAN.md are actually registered
11
+ - All CLI commands exist and are wired up
12
+ - All security scan types work
13
+ - All test files exist for new modules
14
+ - Health score components are wired correctly
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import re
20
+ from pathlib import Path
21
+
22
+ from patchi.core.agents.base import (
23
+ AgentGroup,
24
+ AgentInput,
25
+ AgentResult,
26
+ BaseAgent,
27
+ Finding,
28
+ Severity,
29
+ list_agents,
30
+ register,
31
+ )
32
+
33
+
34
+ @register
35
+ class PlanAuditorAgent(BaseAgent):
36
+ """Audits codebase against BUILD_PLAN.md specifications."""
37
+
38
+ name = "PlanAuditorAgent"
39
+ group = AgentGroup.GUARD
40
+ timeout = 60
41
+
42
+ def _run(self, inp: AgentInput, result: AgentResult) -> None:
43
+ root = inp.root
44
+
45
+ # This agent audits PATCHI ITSELF against its build plan (registered
46
+ # agents, CLI wiring, Patchi-internal modules). Every check below is
47
+ # meaningless when the scanned project is not a Patchi checkout — the
48
+ # paths it references simply cannot exist in a third-party codebase,
49
+ # so emitting them would fabricate CRITICAL/HIGH findings about a
50
+ # project it isn't auditing. Skip unless a Patchi package is present.
51
+ if not (root / "patchi" / "core" / "security").is_dir():
52
+ return
53
+
54
+ # Check 1: All security agents registered
55
+ self._check_security_agents(root, result)
56
+
57
+ # Check 2: All CLI commands wired
58
+ self._check_cli_commands(root, result)
59
+
60
+ # Check 3: New modules exist
61
+ self._check_new_modules(root, result)
62
+
63
+ # Check 4: Test coverage for new modules
64
+ self._check_test_coverage(root, result)
65
+
66
+ # Check 5: Health score wiring
67
+ self._check_health_wiring(root, result)
68
+
69
+ def _check_security_agents(self, root: Path, result: AgentResult) -> None:
70
+ """Verify all registered security agents are imported in security_agents.py."""
71
+ expected_agents = [
72
+ a.name
73
+ for a in list_agents()
74
+ if a.group == AgentGroup.SECURITY or a.name == "PlanAuditorAgent"
75
+ ]
76
+
77
+ sec_file = root / "patchi" / "core" / "security" / "security_agents.py"
78
+ if not sec_file.exists():
79
+ result.add_finding(
80
+ Finding(
81
+ agent=self.name,
82
+ type="plan_audit",
83
+ severity=Severity.CRITICAL,
84
+ file="patchi/core/security/security_agents.py",
85
+ message="security_agents.py not found",
86
+ )
87
+ )
88
+ return
89
+
90
+ try:
91
+ content = sec_file.read_text(encoding="utf-8")
92
+ except OSError:
93
+ return
94
+ for agent_name in expected_agents:
95
+ if agent_name not in content:
96
+ result.add_finding(
97
+ Finding(
98
+ agent=self.name,
99
+ type="plan_audit",
100
+ severity=Severity.HIGH,
101
+ file="patchi/core/security/security_agents.py",
102
+ message=f"Agent '{agent_name}' not imported in security_agents.py",
103
+ )
104
+ )
105
+
106
+ # Check __all__ includes all agents
107
+ all_match = re.search(r"__all__\s*=\s*\[(.*?)\]", content, re.DOTALL)
108
+ if all_match:
109
+ all_text = all_match.group(1)
110
+ for agent_name in expected_agents:
111
+ if f"'{agent_name}'" not in all_text and f'"{agent_name}"' not in all_text:
112
+ result.add_finding(
113
+ Finding(
114
+ agent=self.name,
115
+ type="plan_audit",
116
+ severity=Severity.MEDIUM,
117
+ file="patchi/core/security/security_agents.py",
118
+ message=f"Agent '{agent_name}' not in __all__",
119
+ )
120
+ )
121
+
122
+ def _check_cli_commands(self, root: Path, result: AgentResult) -> None:
123
+ """Verify all CLI commands exist."""
124
+ expected_commands = [
125
+ "init",
126
+ "scan",
127
+ "status",
128
+ "fix",
129
+ "review",
130
+ "patch",
131
+ "undo",
132
+ "queue",
133
+ "memory",
134
+ "key",
135
+ "agents",
136
+ "watch",
137
+ "mode",
138
+ "restrict",
139
+ "notify",
140
+ "web",
141
+ "doctor",
142
+ "model",
143
+ "report",
144
+ "hosted",
145
+ "test",
146
+ "security",
147
+ "explain",
148
+ "blast",
149
+ "trend",
150
+ "audit",
151
+ "learn",
152
+ "chat",
153
+ "settings",
154
+ "access",
155
+ "ai",
156
+ "help",
157
+ ]
158
+
159
+ cmd_dir = root / "patchi" / "cli" / "commands"
160
+ if not cmd_dir.exists():
161
+ result.add_finding(
162
+ Finding(
163
+ agent=self.name,
164
+ type="plan_audit",
165
+ severity=Severity.CRITICAL,
166
+ file="patchi/cli/commands/",
167
+ message="Commands directory not found",
168
+ )
169
+ )
170
+ return
171
+
172
+ existing = {f.stem for f in cmd_dir.glob("*.py")}
173
+ for cmd in expected_commands:
174
+ expected_file = f"{cmd}_cmd" if cmd != "init" else "init"
175
+ if expected_file not in existing:
176
+ result.add_finding(
177
+ Finding(
178
+ agent=self.name,
179
+ type="plan_audit",
180
+ severity=Severity.HIGH,
181
+ file=f"patchi/cli/commands/{expected_file}.py",
182
+ message=f"CLI command '{cmd}' not found",
183
+ )
184
+ )
185
+
186
+ def _check_new_modules(self, root: Path, result: AgentResult) -> None:
187
+ """Verify new security modules exist."""
188
+ expected_modules = [
189
+ "orchestrator.py",
190
+ "secrets_guard.py",
191
+ "supply_chain.py",
192
+ "iac_scanner.py",
193
+ "policy_engine.py",
194
+ "cve_monitor.py",
195
+ "red_team_agent.py",
196
+ ]
197
+
198
+ sec_dir = root / "patchi" / "core" / "security"
199
+ for mod in expected_modules:
200
+ if not (sec_dir / mod).exists():
201
+ result.add_finding(
202
+ Finding(
203
+ agent=self.name,
204
+ type="plan_audit",
205
+ severity=Severity.HIGH,
206
+ file=f"patchi/core/security/{mod}",
207
+ message=f"New module '{mod}' not found",
208
+ )
209
+ )
210
+
211
+ # Check security_test_agent
212
+ test_dir = root / "patchi" / "core" / "testing"
213
+ if not (test_dir / "security_test_agent.py").exists():
214
+ result.add_finding(
215
+ Finding(
216
+ agent=self.name,
217
+ type="plan_audit",
218
+ severity=Severity.HIGH,
219
+ file="patchi/core/testing/security_test_agent.py",
220
+ message="SecurityTestAgent module not found",
221
+ )
222
+ )
223
+
224
+ def _check_test_coverage(self, root: Path, result: AgentResult) -> None:
225
+ """Verify tests exist for new modules."""
226
+ test_file = root / "tests" / "test_new_security_agents.py"
227
+ if not test_file.exists():
228
+ result.add_finding(
229
+ Finding(
230
+ agent=self.name,
231
+ type="plan_audit",
232
+ severity=Severity.HIGH,
233
+ file="tests/test_new_security_agents.py",
234
+ message="New security agent tests not found",
235
+ )
236
+ )
237
+ else:
238
+ try:
239
+ content = test_file.read_text(encoding="utf-8")
240
+ except OSError:
241
+ return
242
+ test_count = content.count("def test_")
243
+ if test_count < 20:
244
+ result.add_finding(
245
+ Finding(
246
+ agent=self.name,
247
+ type="plan_audit",
248
+ severity=Severity.MEDIUM,
249
+ file="tests/test_new_security_agents.py",
250
+ message=f"Only {test_count} tests in new security agent test file (expected 20+)",
251
+ )
252
+ )
253
+
254
+ def _check_health_wiring(self, root: Path, result: AgentResult) -> None:
255
+ """Verify health score references new agents."""
256
+ health_file = root / "patchi" / "core" / "health.py"
257
+ if not health_file.exists():
258
+ return
259
+
260
+ try:
261
+ content = health_file.read_text(encoding="utf-8")
262
+ except OSError:
263
+ return
264
+ # Check that security agents list includes new agents
265
+ new_agents = ["InjectionAgent", "AuthZAgent", "CryptoAgent", "NetworkAgent", "PrivacyAgent"]
266
+ for agent in new_agents:
267
+ if agent not in content:
268
+ result.add_finding(
269
+ Finding(
270
+ agent=self.name,
271
+ type="plan_audit",
272
+ severity=Severity.MEDIUM,
273
+ file="patchi/core/health.py",
274
+ message=f"Health score doesn't reference '{agent}'",
275
+ )
276
+ )