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,313 @@
1
+ """
2
+ Security Orchestrator — deduplication, correlation, and composite risk scoring.
3
+
4
+ Receives raw AgentResult objects from all security agents and produces
5
+ a unified SecurityReport with deduplicated, correlated findings.
6
+
7
+ Deterministic first, AI second.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import json
13
+ import logging
14
+ from dataclasses import dataclass, field
15
+ from pathlib import Path
16
+
17
+ from patchi.core.agents.base import AgentResult, Finding
18
+ from patchi.core.security.chain_analyzer import Chain, ChainAnalyzer
19
+ from patchi.core.security.intent_analyzer import IntentAnalyzer, IntentReport
20
+
21
+ _log = logging.getLogger("patchi.core.security.orchestrator")
22
+
23
+
24
+ # ── Security Report ───────────────────────────────────────────────────────────
25
+
26
+
27
+ @dataclass
28
+ class CorrelatedFinding:
29
+ """A deduplicated finding, possibly confirmed by multiple agents."""
30
+
31
+ finding: Finding
32
+ confirmed_by: list[str] = field(default_factory=list)
33
+ composite_score: float = 0.0
34
+ owasp_category: str = ""
35
+ cwe_ids: list[str] = field(default_factory=list)
36
+
37
+ def to_dict(self) -> dict:
38
+ d = self.finding.to_dict()
39
+ d["confirmed_by"] = self.confirmed_by
40
+ d["composite_score"] = self.composite_score
41
+ d["owasp_category"] = self.owasp_category
42
+ d["cwe_ids"] = self.cwe_ids
43
+ return d
44
+
45
+
46
+ @dataclass
47
+ class SecurityReport:
48
+ """Unified output from all security agents after orchestration."""
49
+
50
+ findings: list[CorrelatedFinding] = field(default_factory=list)
51
+ total_findings: int = 0
52
+ by_severity: dict[str, int] = field(default_factory=dict)
53
+ by_owasp: dict[str, int] = field(default_factory=dict)
54
+ agents_run: list[str] = field(default_factory=list)
55
+ correlation_count: int = 0
56
+ chains: list[Chain] = field(default_factory=list)
57
+ intent_report: IntentReport | None = None
58
+ charter_violations: list[dict] = field(default_factory=list)
59
+
60
+ def to_dict(self) -> dict:
61
+ d = {
62
+ "total_findings": self.total_findings,
63
+ "by_severity": self.by_severity,
64
+ "by_owasp": self.by_owasp,
65
+ "agents_run": self.agents_run,
66
+ "correlation_count": self.correlation_count,
67
+ "findings": [f.to_dict() for f in self.findings],
68
+ "chains": [
69
+ {
70
+ "steps": [
71
+ {
72
+ "file": n.finding.file,
73
+ "line": n.finding.line,
74
+ "type": n.finding.type,
75
+ "label": lbl,
76
+ }
77
+ for n, lbl in zip(c.nodes, c.labels, strict=False)
78
+ ],
79
+ "total_severity": c.total_severity,
80
+ }
81
+ for c in self.chains
82
+ ],
83
+ }
84
+ if self.intent_report:
85
+ d["intent_report"] = self.intent_report.to_dict()
86
+ if self.charter_violations:
87
+ d["charter_violations"] = self.charter_violations
88
+ return d
89
+
90
+
91
+ # ── OWASP Top 10 2021 mapping (CWE ranges) ──────────────────────────────────
92
+
93
+ _OWASP_MAPPING: dict | None = None
94
+ _OWASP_BY_CWE: dict[int, str] | None = None
95
+ _OWASP_KEYWORD_FALLBACK: dict[str, list[str]] | None = None
96
+
97
+ _MAPPING_FILE = Path(__file__).parent / "owasp_mapping.json"
98
+
99
+
100
+ def _load_owasp_mapping() -> dict:
101
+ """Load OWASP mapping from JSON file (cached)."""
102
+ global _OWASP_MAPPING, _OWASP_BY_CWE, _OWASP_KEYWORD_FALLBACK
103
+ if _OWASP_MAPPING is not None:
104
+ return _OWASP_MAPPING
105
+
106
+ with open(_MAPPING_FILE, encoding="utf-8") as f:
107
+ data = json.load(f)
108
+
109
+ # Build lookup: CWE number → full category name (e.g. "A01:2021 Broken Access Control")
110
+ by_cwe: dict[int, str] = {}
111
+ for cat in data["categories"]:
112
+ name = f"{cat['code']}:2021 {cat['name']}"
113
+ for cwe in cat["cwes"]:
114
+ by_cwe[cwe] = name
115
+
116
+ _OWASP_MAPPING = data
117
+ _OWASP_BY_CWE = by_cwe
118
+ _OWASP_KEYWORD_FALLBACK = data.get("keyword_fallback", {})
119
+ return data
120
+
121
+
122
+ def _classify_owasp(finding: Finding) -> str:
123
+ """Map a finding to an OWASP Top 10 category via CWE ID or keyword fallback."""
124
+ _load_owasp_mapping()
125
+ cwe_str = finding.cwe.upper().replace("CWE-", "").strip()
126
+ if cwe_str.isdigit() and _OWASP_BY_CWE is not None:
127
+ cwe_num = int(cwe_str)
128
+ if cwe_num in _OWASP_BY_CWE:
129
+ return _OWASP_BY_CWE[cwe_num]
130
+ # Fallback by finding type keywords
131
+ ftype = finding.type.lower()
132
+ msg = finding.message.lower()
133
+ combined = ftype + msg
134
+ if _OWASP_KEYWORD_FALLBACK is not None:
135
+ for code, keywords in _OWASP_KEYWORD_FALLBACK.items():
136
+ if any(w in combined for w in keywords):
137
+ # Map code back to full name via the mapping data
138
+ for cat in _OWASP_MAPPING["categories"]:
139
+ if cat["code"] == code:
140
+ return f"{code}:2021 {cat['name']}"
141
+ return "A05:2021 Security Misconfiguration"
142
+
143
+
144
+ # ── Composite risk score ─────────────────────────────────────────────────────
145
+
146
+ _SEV_WEIGHTS = {"critical": 10.0, "high": 7.0, "medium": 4.0, "low": 1.5, "info": 0.0}
147
+
148
+
149
+ def _composite_score(finding: Finding, confirm_count: int) -> float:
150
+ """0-100 score: severity + confirmation bonus + confidence."""
151
+ base = _SEV_WEIGHTS.get(finding.severity.value, 0.0)
152
+ confirm_bonus = min(confirm_count * 3.0, 15.0) # up to +15 for multi-agent confirmation
153
+ return min(100.0, base * 10.0 + confirm_bonus)
154
+
155
+
156
+ # ── Orchestrator ─────────────────────────────────────────────────────────────
157
+
158
+
159
+ class SecurityOrchestrator:
160
+ """
161
+ Takes raw AgentResult list, deduplicates, correlates, scores.
162
+
163
+ Usage:
164
+ orch = SecurityOrchestrator()
165
+ report = orch.correlate(agent_results)
166
+ """
167
+
168
+ def correlate(self, results: list[AgentResult]) -> SecurityReport:
169
+ all_findings: list[Finding] = []
170
+ agents_run = []
171
+
172
+ for r in results:
173
+ agents_run.append(r.agent_name)
174
+ all_findings.extend(r.findings)
175
+
176
+ # Step 1: Group by (file, type, cwe) with line-tolerance window (LIMIT-08)
177
+ LINE_TOLERANCE = 5
178
+ groups: dict[tuple, list[Finding]] = {}
179
+ used: set[tuple] = set()
180
+ # CWE secondary index: different tools name the same weakness
181
+ # differently ("B608" vs "patchi-sql-injection-..."), but the CWE is
182
+ # the semantic join. Same file + same CWE within tolerance => merge.
183
+ cwe_index: dict[tuple[str, str], tuple] = {}
184
+ for f in sorted(all_findings, key=lambda x: (x.file, x.line)):
185
+ cwe_key = f.cwe.upper().replace("CWE-", "").strip() if f.cwe else ""
186
+ # Try exact match first
187
+ exact_key = (f.file, f.line, f.type, cwe_key)
188
+ if exact_key not in used:
189
+ # Try line-tolerance match within same file + type + cwe
190
+ matched = False
191
+ for tolerance_line in range(
192
+ max(1, f.line - LINE_TOLERANCE), f.line + LINE_TOLERANCE + 1
193
+ ):
194
+ tol_key = (f.file, tolerance_line, f.type, cwe_key)
195
+ if tol_key in used:
196
+ matched = True
197
+ break
198
+ if not matched and cwe_key and f.line > 0:
199
+ # Cross-tool join on (file, cwe) proximity
200
+ gkey = cwe_index.get((f.file, cwe_key))
201
+ if gkey is not None and abs(gkey[1] - f.line) <= LINE_TOLERANCE:
202
+ groups[gkey].append(f)
203
+ matched = True
204
+ if not matched:
205
+ key = (f.file, f.line, f.type, cwe_key)
206
+ used.add(key)
207
+ groups[key] = [f]
208
+ if cwe_key and f.line > 0:
209
+ existing = cwe_index.get((f.file, cwe_key))
210
+ if existing is None or abs(existing[1] - f.line) <= LINE_TOLERANCE:
211
+ cwe_index[(f.file, cwe_key)] = key
212
+ else:
213
+ # Add to existing tolerance group
214
+ for gkey in groups:
215
+ if (
216
+ gkey[0] == f.file
217
+ and gkey[2] == f.type
218
+ and gkey[3] == cwe_key
219
+ and abs(gkey[1] - f.line) <= LINE_TOLERANCE
220
+ ):
221
+ groups[gkey].append(f)
222
+ break
223
+ else:
224
+ groups[exact_key].append(f)
225
+
226
+ correlated: list[CorrelatedFinding] = []
227
+
228
+ for _key, group in groups.items():
229
+ primary = group[0]
230
+ confirmed_by = list({f.agent for f in group})
231
+ owasp = _classify_owasp(primary)
232
+ cwe_ids = list({f.cwe for f in group if f.cwe})
233
+
234
+ score = _composite_score(primary, len(confirmed_by))
235
+
236
+ # Take highest severity from the group
237
+ best = max(group, key=lambda f: f.severity.sort_key())
238
+
239
+ correlated.append(
240
+ CorrelatedFinding(
241
+ finding=best,
242
+ confirmed_by=confirmed_by,
243
+ composite_score=score,
244
+ owasp_category=owasp,
245
+ cwe_ids=cwe_ids,
246
+ )
247
+ )
248
+
249
+ # Sort by composite score descending (most dangerous first)
250
+ correlated.sort(key=lambda c: (-c.composite_score, c.finding.file, c.finding.line))
251
+
252
+ # Build summary
253
+ by_sev: dict[str, int] = {}
254
+ by_owasp: dict[str, int] = {}
255
+ for c in correlated:
256
+ sev = c.finding.severity.value
257
+ by_sev[sev] = by_sev.get(sev, 0) + 1
258
+ by_owasp[c.owasp_category] = by_owasp.get(c.owasp_category, 0) + 1
259
+
260
+ # ── Chain analysis (cross-file exploit chains) ──────────────────────
261
+ chains: list[Chain] = []
262
+ import_edges: dict[str, set[str]] = {}
263
+ try:
264
+ from patchi.core.brain.import_graph import build_import_graph
265
+
266
+ ig = build_import_graph(Path("."))
267
+ import_edges = dict(ig.edges)
268
+ except Exception as _exc:
269
+ _log.warning('correlate failed: %s', _exc)
270
+ try:
271
+ chain_analyzer = ChainAnalyzer(all_findings, import_edges=import_edges)
272
+ chains = chain_analyzer.find_chains(max_chains=20)
273
+ except Exception as _exc:
274
+ _log.warning('correlate failed: %s', _exc)
275
+
276
+ # ── Intent analysis (route ↔ code gap detection) ──────────────────────
277
+ intent_report: IntentReport | None = None
278
+ try:
279
+ intent_analyzer = IntentAnalyzer()
280
+ intent_report = intent_analyzer.analyze_root(Path("."))
281
+ except Exception as _exc:
282
+ _log.warning('correlate failed: %s', _exc)
283
+
284
+ # ── Charter drift detection ───────────────────────────────────────────
285
+ charter_violations: list[dict] = []
286
+ try:
287
+ from patchi.core.security.charter import (
288
+ check_all_violations,
289
+ load_charter,
290
+ )
291
+ charter = load_charter(Path("."))
292
+ if charter.rules:
293
+ flat_edges: list[tuple[str, str]] = []
294
+ for src, dsts in import_edges.items():
295
+ for dst in dsts:
296
+ flat_edges.append((src, dst))
297
+ charter_violations = [
298
+ v.to_dict() for v in check_all_violations(charter, flat_edges)
299
+ ]
300
+ except Exception as _exc:
301
+ _log.warning('correlate failed: %s', _exc)
302
+
303
+ return SecurityReport(
304
+ findings=correlated,
305
+ total_findings=len(correlated),
306
+ by_severity=by_sev,
307
+ by_owasp=by_owasp,
308
+ agents_run=agents_run,
309
+ correlation_count=sum(1 for c in correlated if len(c.confirmed_by) > 1),
310
+ chains=chains,
311
+ intent_report=intent_report,
312
+ charter_violations=charter_violations,
313
+ )
@@ -0,0 +1,125 @@
1
+ """
2
+ Pattern-context suppression for security agents.
3
+
4
+ Detects when a flagged line lives inside:
5
+ - A regex definition (re.compile, _PATTERNS list)
6
+ - A docstring or multi-line comment block
7
+ - A string literal in a variable named *pattern*, *regex*, *payload*, etc.
8
+ - A test fixture file (tests/ or test_*.py)
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import re
14
+ from pathlib import Path
15
+
16
+
17
+ def _try_read(file_path: str) -> str | None:
18
+ """Try reading a file, resolving relative paths against CWD."""
19
+ p = Path(file_path)
20
+ if not p.is_absolute():
21
+ p = Path.cwd() / file_path
22
+ if not p.is_file():
23
+ return None
24
+ try:
25
+ return p.read_text(encoding="utf-8", errors="ignore")
26
+ except OSError:
27
+ return None
28
+
29
+
30
+ def is_pattern_context(file_path: str, line_num: int) -> bool:
31
+ """
32
+ Return True if line_num in file_path is inside a pattern definition,
33
+ docstring, or test fixture — meaning a security finding there is a
34
+ self-referential false positive.
35
+ """
36
+ if line_num < 1:
37
+ return False
38
+
39
+ content = _try_read(file_path)
40
+ if content is None:
41
+ return False
42
+
43
+ lines = content.splitlines()
44
+ if line_num > len(lines):
45
+ return False
46
+
47
+ target_line = lines[line_num - 1].strip()
48
+
49
+ # ── Test file check ───────────────────────────────────────────────────
50
+ fp = file_path.replace("\\", "/")
51
+ if "/tests/" in fp or fp.split("/")[-1].startswith("test_"):
52
+ return True
53
+
54
+ # ── Variable name check ───────────────────────────────────────────────
55
+ assign_match = re.match(r"(\w+)\s*[:=]", target_line)
56
+ if assign_match:
57
+ var_name = assign_match.group(1).lower()
58
+ if any(
59
+ kw in var_name
60
+ for kw in (
61
+ "pattern",
62
+ "regex",
63
+ "payload",
64
+ "explanation",
65
+ "template",
66
+ "descriptions",
67
+ "protocol",
68
+ "metadata",
69
+ "indicator",
70
+ )
71
+ ):
72
+ return True
73
+
74
+ # ── re.compile() check ────────────────────────────────────────────────
75
+ if "re.compile(" in target_line or "re.match(" in target_line:
76
+ return True
77
+
78
+ # ── Triple-quote / docstring check ────────────────────────────────────
79
+ in_triple = False
80
+ tq_char = None
81
+ bracket_depth = 0
82
+
83
+ for i in range(line_num - 1):
84
+ line = lines[i].strip()
85
+ for tq in ('"""', "'''"):
86
+ count = line.count(tq)
87
+ if count % 2 == 1:
88
+ if in_triple and tq_char == tq:
89
+ in_triple = False
90
+ elif not in_triple:
91
+ in_triple = True
92
+ tq_char = tq
93
+ if in_triple:
94
+ return True
95
+ for ch in line:
96
+ if ch in "({[":
97
+ bracket_depth += 1
98
+ elif ch in ")}]":
99
+ bracket_depth -= 1
100
+
101
+ # ── Pattern block in list/dict literal ─────────────────────────────────
102
+ if bracket_depth > 0:
103
+ start = max(0, line_num - 8)
104
+ end = min(len(lines), line_num + 4)
105
+ block = "\n".join(lines[start:end])
106
+ if re.search(r're\.compile\(|r["\']|\\[dDwWsSbB]|\[\^', block):
107
+ return True
108
+
109
+ return False
110
+
111
+
112
+ def suppress_findings(result) -> int:
113
+ """
114
+ Remove pattern-context false positives from an AgentResult in place.
115
+ Returns the count of findings removed.
116
+ """
117
+ from patchi.core.agents.base import AgentResult
118
+
119
+ if not isinstance(result, AgentResult):
120
+ return 0
121
+ original = len(result.findings)
122
+ result.findings = [
123
+ f for f in result.findings if not is_pattern_context(f.file, f.line if f.line else 0)
124
+ ]
125
+ return original - len(result.findings)
@@ -0,0 +1,101 @@
1
+ """
2
+ Pattern loader for security agents.
3
+
4
+ Loads sink/source patterns from YAML config and merges them with
5
+ agent-specific defaults. Agents call `load_patterns()` once at
6
+ startup and use the returned dict.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import re
12
+ from pathlib import Path
13
+ from typing import Any
14
+
15
+ import yaml
16
+
17
+ _PATTERNS_CACHE: dict[str, Any] | None = None
18
+ _PATTERNS_PATH = Path(__file__).parent / "security_patterns.yaml"
19
+
20
+
21
+ def load_patterns(path: Path | None = None) -> dict[str, Any]:
22
+ """Load and cache security patterns from YAML config."""
23
+ global _PATTERNS_CACHE
24
+ if _PATTERNS_CACHE is not None and path is None:
25
+ return _PATTERNS_CACHE
26
+
27
+ config_path = path or _PATTERNS_PATH
28
+ if not config_path.exists():
29
+ return {}
30
+
31
+ with open(config_path, encoding="utf-8") as f:
32
+ data = yaml.safe_load(f) or {}
33
+
34
+ _PATTERNS_CACHE = data
35
+ return data
36
+
37
+
38
+ def get_injection_sinks(patterns: dict | None = None) -> dict[str, dict[str, list[str]]]:
39
+ """Get injection sinks grouped by type and language."""
40
+ p = patterns or load_patterns()
41
+ return p.get("injection", {})
42
+
43
+
44
+ def get_ssrf_patterns(patterns: dict | None = None) -> dict[str, Any]:
45
+ """Get SSRF detection patterns."""
46
+ p = patterns or load_patterns()
47
+ return p.get("ssrf", {})
48
+
49
+
50
+ def get_ssrf_protocol_patterns(patterns: dict | None = None) -> list[tuple[re.Pattern, str, str]]:
51
+ """Get compiled SSRF protocol regex patterns with severity and message."""
52
+ ssrf = get_ssrf_patterns(patterns)
53
+ result = []
54
+ for entry in ssrf.get("protocol_patterns", []):
55
+ try:
56
+ compiled = re.compile(entry["pattern"], re.IGNORECASE)
57
+ result.append((compiled, entry["severity"], entry["message"]))
58
+ except re.error:
59
+ continue
60
+ return result
61
+
62
+
63
+ def get_ssrf_metadata_patterns(patterns: dict | None = None) -> list[tuple[re.Pattern, str, str]]:
64
+ """Get compiled SSRF metadata endpoint regex patterns."""
65
+ ssrf = get_ssrf_patterns(patterns)
66
+ result = []
67
+ for entry in ssrf.get("metadata_patterns", []):
68
+ try:
69
+ compiled = re.compile(entry["pattern"], re.IGNORECASE)
70
+ result.append((compiled, entry["severity"], entry["message"]))
71
+ except re.error:
72
+ continue
73
+ return result
74
+
75
+
76
+ def get_ssrf_internal_network_patterns(
77
+ patterns: dict | None = None,
78
+ ) -> list[tuple[re.Pattern, str, str]]:
79
+ """Get compiled SSRF internal network regex patterns."""
80
+ ssrf = get_ssrf_patterns(patterns)
81
+ result = []
82
+ for entry in ssrf.get("internal_network", []):
83
+ try:
84
+ compiled = re.compile(entry["pattern"])
85
+ result.append((compiled, entry["severity"], entry["message"]))
86
+ except re.error:
87
+ continue
88
+ return result
89
+
90
+
91
+ def get_catch_noop_patterns(patterns: dict | None = None) -> list[re.Pattern]:
92
+ """Get compiled catch block no-op regex patterns."""
93
+ p = patterns or load_patterns()
94
+ cb = p.get("catch_block", {})
95
+ result = []
96
+ for pat_str in cb.get("noop_patterns", []):
97
+ try:
98
+ result.append(re.compile(pat_str))
99
+ except re.error:
100
+ continue
101
+ return result
@@ -0,0 +1,4 @@
1
+ """Pentest tool adapters — real DAST engines for Patchi."""
2
+
3
+ from patchi.core.security.pentest.base import PentestTool # noqa: F401
4
+ from patchi.core.security.pentest.registry import PentestRegistry # noqa: F401
@@ -0,0 +1,58 @@
1
+ """PentestTool base — interface every real DAST adapter implements."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ import shutil
7
+ import subprocess
8
+ from abc import ABC, abstractmethod
9
+ from dataclasses import dataclass, field
10
+ from pathlib import Path
11
+
12
+ _log = logging.getLogger("patchi.pentest.base")
13
+
14
+
15
+ @dataclass
16
+ class PentestResult:
17
+ tool: str
18
+ success: bool
19
+ evidence: str = ""
20
+ findings: list[dict] = field(default_factory=list)
21
+ raw_output: str = ""
22
+ duration_ms: int = 0
23
+ error: str = ""
24
+
25
+
26
+ class PentestTool(ABC):
27
+ """Abstract base for real exploit tools."""
28
+
29
+ name: str = "base"
30
+ binary: str = "" # e.g. "nuclei" or "npx"
31
+ docker_image: str = "" # fallback
32
+ requires_target: bool = True
33
+
34
+ def is_available(self) -> bool:
35
+ if self.binary and shutil.which(self.binary):
36
+ return True
37
+ if self.docker_image and shutil.which("docker"):
38
+ try:
39
+ out = subprocess.run(
40
+ ["docker", "image", "inspect", self.docker_image],
41
+ capture_output=True,
42
+ timeout=5,
43
+ )
44
+ if out.returncode == 0:
45
+ return True
46
+ except Exception as exc: # noqa: BLE001
47
+ _log.debug("%s docker check failed: %s", self.name, exc)
48
+ return False
49
+
50
+ @abstractmethod
51
+ def run(
52
+ self,
53
+ target: str,
54
+ safe_mode: bool = True,
55
+ workspace: Path | None = None,
56
+ extra: dict | None = None,
57
+ ) -> PentestResult:
58
+ """Execute tool against target. Never raises — returns PentestResult."""
@@ -0,0 +1,40 @@
1
+ """Dalfox adapter — XSS (reflected/stored)."""
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.dalfox")
14
+
15
+
16
+ class DalfoxTool(PentestTool):
17
+ name = "dalfox"
18
+ binary = "dalfox"
19
+ docker_image = "hahwul/dalfox:latest"
20
+
21
+ def run(self, target: str, safe_mode: bool = True, workspace: Path | None = None, extra: dict | None = None) -> PentestResult:
22
+ t0 = time.monotonic()
23
+ base = ["dalfox", "url", target, "--silence", "--format", "json"]
24
+ if safe_mode:
25
+ base += ["--skip-bav"]
26
+ cmd = base if shutil.which("dalfox") else (["docker","run","--rm","hahwul/dalfox:latest","url",target,"--silence","--format","json"] if shutil.which("docker") else None)
27
+ if not cmd:
28
+ return PentestResult(tool=self.name, success=False, error="dalfox not available")
29
+ try:
30
+ proc = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
31
+ out = proc.stdout + proc.stderr
32
+ findings=[]
33
+ for line in out.splitlines()[:100]:
34
+ if "poc" in line.lower() or "vuln" in line.lower():
35
+ findings.append({"type":"xss","detail": line[:300]})
36
+ return PentestResult(tool=self.name, success=True, evidence=f"dalfox {len(findings)} poc", findings=findings, raw_output=out[:8000], duration_ms=int((time.monotonic()-t0)*1000))
37
+ except subprocess.TimeoutExpired:
38
+ return PentestResult(tool=self.name, success=False, error="dalfox timeout 5m", duration_ms=int((time.monotonic()-t0)*1000))
39
+ except Exception as exc: # noqa: BLE001
40
+ return PentestResult(tool=self.name, success=False, error=str(exc), duration_ms=int((time.monotonic()-t0)*1000))
@@ -0,0 +1,48 @@
1
+ """FFuF adapter — discovery/fuzz."""
2
+ from __future__ import annotations
3
+
4
+ import json
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.ffuf")
14
+
15
+
16
+ class FfufTool(PentestTool):
17
+ name = "ffuf"
18
+ binary = "ffuf"
19
+ docker_image = "ffuf/ffuf: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
+ wordlist = extra.get("wordlist", "/usr/share/wordlists/common.txt")
24
+ t0 = time.monotonic()
25
+ # ffuf target expected as https://host/FUZZ
26
+ if "FUZZ" not in target:
27
+ target = target.rstrip("/") + "/FUZZ"
28
+ base = ["ffuf", "-u", target, "-w", wordlist, "-json", "-s"]
29
+ if safe_mode:
30
+ base += ["-rate", "10"]
31
+ cmd = base if shutil.which("ffuf") else (["docker","run","--rm","ffuf/ffuf:latest","-u",target,"-w",wordlist,"-json","-s"] if shutil.which("docker") else None)
32
+ if not cmd:
33
+ return PentestResult(tool=self.name, success=False, error="ffuf not available")
34
+ try:
35
+ proc = subprocess.run(cmd, capture_output=True, text=True, timeout=180)
36
+ dur = int((time.monotonic()-t0)*1000)
37
+ findings=[]
38
+ try:
39
+ data = json.loads(proc.stdout)
40
+ for r in data.get("results", [])[:30]:
41
+ findings.append({"url": r.get("url",""), "status": r.get("status"), "length": r.get("length")})
42
+ except Exception as _exc:
43
+ _log.debug("ffuf json parse skipped: %s", _exc)
44
+ return PentestResult(tool=self.name, success=True, evidence=f"ffuf {len(findings)} hits", findings=findings, raw_output=proc.stdout[:6000], duration_ms=dur)
45
+ except subprocess.TimeoutExpired:
46
+ return PentestResult(tool=self.name, success=False, error="ffuf timeout 3m", duration_ms=int((time.monotonic()-t0)*1000))
47
+ except Exception as exc: # noqa: BLE001
48
+ return PentestResult(tool=self.name, success=False, error=str(exc), duration_ms=int((time.monotonic()-t0)*1000))