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,417 @@
1
+ """
2
+ PushNotificationAgent — push notification security.
3
+
4
+ Detects push notification security issues:
5
+ - Hardcoded FCM server keys or APNs private keys
6
+ - Push token handling vulnerabilities (storage, transmission)
7
+ - Sensitive data in notification payloads
8
+ - Missing token rotation/invalidation
9
+ - Insecure push notification endpoints
10
+ - Missing push notification authentication
11
+
12
+ Uses static file analysis and optional external tools (gitleaks).
13
+ Does NOT call AI.
14
+ Does NOT write to disk.
15
+ Does NOT touch the queue.
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import logging
21
+ import re
22
+ import shutil
23
+ import subprocess
24
+ from pathlib import Path
25
+
26
+ from ..agents.base import (
27
+ AgentDomain,
28
+ AgentGroup,
29
+ AgentInput,
30
+ AgentResult,
31
+ AgentStatus,
32
+ BaseAgent,
33
+ Finding,
34
+ Severity,
35
+ make_finding,
36
+ register,
37
+ safe_rglob,
38
+ )
39
+ from ..brain.trace_log import trace_agent
40
+
41
+ _SOURCE_EXTENSIONS = {
42
+ "*.py",
43
+ "*.js",
44
+ "*.jsx",
45
+ "*.ts",
46
+ "*.tsx",
47
+ "*.java",
48
+ "*.php",
49
+ "*.rb",
50
+ "*.go",
51
+ "*.rs",
52
+ "*.cs",
53
+ }
54
+
55
+ _FCM_KEY_PATTERNS = [
56
+ (
57
+ re.compile(
58
+ r"(?:FCM[_-]?(?:SERVER[_-]?KEY|KEY|SECRET|TOKEN|API[_-]?KEY))\s*[:=]\s*['\"][A-Za-z0-9_\-]{20,}['\"]",
59
+ re.IGNORECASE,
60
+ ),
61
+ "PUSH-01: Hardcoded FCM Server Key",
62
+ ),
63
+ (
64
+ re.compile(
65
+ r"(?:server[_-]?key|fcm[_-]?key)\s*[:=]\s*['\"][A-Za-z0-9_\-]{20,}['\"]", re.IGNORECASE
66
+ ),
67
+ "PUSH-01: Hardcoded FCM Key",
68
+ ),
69
+ (
70
+ re.compile(r"AAAA[A-Za-z0-9_-]{7}:[A-Za-z0-9_-]{140}", re.IGNORECASE),
71
+ "PUSH-01: Exposed FCM Server Key",
72
+ ),
73
+ ]
74
+
75
+ _APNS_KEY_PATTERNS = [
76
+ (
77
+ re.compile(
78
+ r"(?:APNS[_-]?(?:KEY|TOKEN|SECRET|PRIVATE[_-]?KEY|CERT))\s*[:=]\s*['\"][^'\"]{10,}['\"]",
79
+ re.IGNORECASE,
80
+ ),
81
+ "PUSH-02: Hardcoded APNs Credential",
82
+ ),
83
+ (
84
+ re.compile(
85
+ r"(?:aps[_-]?env|apns[_-]?environment)\s*[:=]\s*['\"]?(?:production|development|sandbox)['\"]?",
86
+ re.IGNORECASE,
87
+ ),
88
+ "PUSH-03: APNs Environment Hardcoded",
89
+ ),
90
+ (
91
+ re.compile(r"-----BEGIN (?:RSA )?PRIVATE KEY-----"),
92
+ "PUSH-02: Embedded Private Key in Source",
93
+ ),
94
+ ]
95
+
96
+ _PUSH_TOKEN_PATTERNS = [
97
+ (
98
+ re.compile(
99
+ r"(?:device[_-]?token|push[_-]?token|fcm[_-]?token|apns[_-]?token)\s*[:=]\s*['\"][A-Za-z0-9_\-]{20,}['\"]",
100
+ re.IGNORECASE,
101
+ ),
102
+ "PUSH-04: Hardcoded Push Token",
103
+ ),
104
+ (
105
+ re.compile(
106
+ r"(?:token|device_token)\s*\.\s*(?:save|store|persist|write|insert)", re.IGNORECASE
107
+ ),
108
+ "PUSH-05: Push Token Stored Without Encryption",
109
+ ),
110
+ ]
111
+
112
+ _SENSITIVE_PAYLOAD_PATTERNS = [
113
+ (
114
+ re.compile(
115
+ r"(?:password|secret|credit.?card|ssn|social.?security|api.?key|private.?key)\s*[:=]",
116
+ re.IGNORECASE,
117
+ ),
118
+ "PUSH-06: Sensitive Data in Notification Payload",
119
+ ),
120
+ (
121
+ re.compile(
122
+ r"(?:notification|message|alert|payload|body)\s*(?:\.|\[).*(?:password|secret|token|key|credential)",
123
+ re.IGNORECASE,
124
+ ),
125
+ "PUSH-06: Sensitive Data in Notification Body",
126
+ ),
127
+ ]
128
+
129
+ _NOTIFICATION_FRAMEWORKS = [
130
+ re.compile(r"\bfirebase[_-]?admin\b|\bfcm\b|FirebaseMessaging", re.IGNORECASE),
131
+ re.compile(r"\bapns[_-]?client\b|\bpyapns\b|APNs", re.IGNORECASE),
132
+ re.compile(r"\bpush[_-]?notification\b|\bpush[_-]?service\b", re.IGNORECASE),
133
+ re.compile(r"\bOneSignal\b|\bonesignal\b", re.IGNORECASE),
134
+ re.compile(r"\bexpo[_-]?server[_-]?sdk\b|\bexpo[_-]?notifications\b", re.IGNORECASE),
135
+ ]
136
+
137
+
138
+ _log = logging.getLogger("patchi.security.push_notification_agent")
139
+
140
+
141
+ @register
142
+ class PushNotificationAgent(BaseAgent):
143
+ """Agent for detecting push notification security issues."""
144
+
145
+ group = AgentGroup.SECURITY
146
+ domain = AgentDomain.SECURITY
147
+ name = "PushNotificationAgent"
148
+ description = "Push notification security: FCM/APNs credential storage, token handling, payload minimization"
149
+
150
+ def _run(self, inp: AgentInput, result: AgentResult) -> None:
151
+ findings: list[Finding] = []
152
+ files_scanned = 0
153
+
154
+ with trace_agent(self.name, inp.root) as trace:
155
+ # ── Static analysis ────────────────────────────────────────────
156
+ for pattern in _SOURCE_EXTENSIONS:
157
+ for fp in safe_rglob(inp.root, pattern):
158
+ if fp.is_file():
159
+ files_scanned += 1
160
+ findings.extend(self._scan_file(fp, inp.root))
161
+
162
+ findings.extend(self._scan_push_config(inp))
163
+ findings.extend(self._scan_token_storage(inp))
164
+
165
+ # ── External tool: gitleaks ────────────────────────────────────
166
+ if shutil.which("gitleaks"):
167
+ findings.extend(self._run_gitleaks(inp))
168
+
169
+ trace.findings = len(findings)
170
+ trace.files_scanned = files_scanned
171
+
172
+ result.status = AgentStatus.SUCCEEDED
173
+ result.findings = findings
174
+ result.data.update(
175
+ {
176
+ "push_findings": len(findings),
177
+ "gitleaks_available": shutil.which("gitleaks") is not None,
178
+ }
179
+ )
180
+ return
181
+
182
+ # ── Source file scan ───────────────────────────────────────────────────
183
+
184
+ def _scan_file(self, fp: Path, root: Path) -> list[Finding]:
185
+ findings: list[Finding] = []
186
+ rel = fp.relative_to(root).as_posix()
187
+ try:
188
+ content = fp.read_text(encoding="utf-8", errors="replace")
189
+ lines = content.splitlines()
190
+
191
+ # FCM keys
192
+ for i, line in enumerate(lines, 1):
193
+ for rx, title in _FCM_KEY_PATTERNS:
194
+ if rx.search(line):
195
+ findings.append(
196
+ make_finding(
197
+ severity=Severity.CRITICAL,
198
+ file=rel,
199
+ line_start=i,
200
+ title=title,
201
+ description="Hardcoded FCM server key found. Anyone with this key can send push notifications.",
202
+ evidence=line.strip()[:120],
203
+ suggestion="Store FCM server keys in environment variables or a secrets manager.",
204
+ )
205
+ )
206
+
207
+ # APNs keys
208
+ for i, line in enumerate(lines, 1):
209
+ for rx, title in _APNS_KEY_PATTERNS:
210
+ if rx.search(line):
211
+ findings.append(
212
+ make_finding(
213
+ severity=Severity.CRITICAL,
214
+ file=rel,
215
+ line_start=i,
216
+ title=title,
217
+ description="Hardcoded APNs credential or private key found.",
218
+ evidence=line.strip()[:120],
219
+ suggestion="Store APNs credentials securely and load from environment variables.",
220
+ )
221
+ )
222
+
223
+ # Push tokens
224
+ for i, line in enumerate(lines, 1):
225
+ for rx, title in _PUSH_TOKEN_PATTERNS:
226
+ if rx.search(line):
227
+ findings.append(
228
+ make_finding(
229
+ severity=Severity.HIGH,
230
+ file=rel,
231
+ line_start=i,
232
+ title=title,
233
+ description="Push token handling issue detected.",
234
+ evidence=line.strip()[:120],
235
+ suggestion="Rotate push tokens regularly and encrypt at rest.",
236
+ )
237
+ )
238
+
239
+ # Sensitive data in payloads
240
+ in_notification_block = False
241
+ for i, line in enumerate(lines, 1):
242
+ if re.search(
243
+ r"(?:notification|message|payload|alert|body)\s*[{(]", line, re.IGNORECASE
244
+ ):
245
+ in_notification_block = True
246
+ if in_notification_block:
247
+ for rx, title in _SENSITIVE_PAYLOAD_PATTERNS:
248
+ if rx.search(line):
249
+ findings.append(
250
+ make_finding(
251
+ severity=Severity.HIGH,
252
+ file=rel,
253
+ line_start=i,
254
+ title=title,
255
+ description="Sensitive data detected in notification payload.",
256
+ evidence=line.strip()[:120],
257
+ suggestion="Minimize notification payloads; never include PII or secrets.",
258
+ )
259
+ )
260
+ if re.search(r"[})]", line):
261
+ in_notification_block = False
262
+
263
+ except Exception as e:
264
+ _log.warning("PushNotificationAgent._scan_file failed: %s", e)
265
+ return findings
266
+
267
+ # ── Push config scan ───────────────────────────────────────────────────
268
+
269
+ def _scan_push_config(self, inp: AgentInput) -> list[Finding]:
270
+ findings: list[Finding] = []
271
+ config_patterns = ("*.yaml", "*.yml", "*.json", "*.toml", "*.tf", "*.env*")
272
+ for pattern in config_patterns:
273
+ for fp in safe_rglob(inp.root, pattern):
274
+ if not fp.is_file():
275
+ continue
276
+ rel = fp.relative_to(inp.root).as_posix()
277
+ try:
278
+ content = fp.read_text(encoding="utf-8", errors="replace")
279
+ content_lower = content.lower()
280
+ # Check if file is push-related
281
+ is_push_config = any(
282
+ kw in content_lower
283
+ for kw in ("fcm", "firebase", "apns", "push", "notification")
284
+ )
285
+ if not is_push_config:
286
+ continue
287
+
288
+ # Check for credentials in config
289
+ for rx, title in _FCM_KEY_PATTERNS:
290
+ if rx.search(content):
291
+ findings.append(
292
+ make_finding(
293
+ severity=Severity.CRITICAL,
294
+ file=rel,
295
+ line_start=0,
296
+ title=title,
297
+ description="FCM server key found in configuration file.",
298
+ suggestion="Use environment variables or a secrets manager instead of config files.",
299
+ )
300
+ )
301
+
302
+ for rx, title in _APNS_KEY_PATTERNS:
303
+ if rx.search(content):
304
+ findings.append(
305
+ make_finding(
306
+ severity=Severity.CRITICAL,
307
+ file=rel,
308
+ line_start=0,
309
+ title=title,
310
+ description="APNs credential found in configuration file.",
311
+ suggestion="Load APNs credentials from a secure vault.",
312
+ )
313
+ )
314
+
315
+ except Exception as e:
316
+ _log.warning("PushNotificationAgent._scan_push_config failed: %s", e)
317
+ return findings
318
+
319
+ # ── Token storage patterns ─────────────────────────────────────────────
320
+
321
+ def _scan_token_storage(self, inp: AgentInput) -> list[Finding]:
322
+ findings: list[Finding] = []
323
+ for pattern in ("*.py", "*.js", "*.ts", "*.java", "*.go", "*.rb"):
324
+ for fp in safe_rglob(inp.root, pattern):
325
+ if not fp.is_file():
326
+ continue
327
+ rel = fp.relative_to(inp.root).as_posix()
328
+ try:
329
+ content = fp.read_text(encoding="utf-8", errors="replace")
330
+ lines = content.splitlines()
331
+ for i, line in enumerate(lines, 1):
332
+ # Token stored in plaintext database field
333
+ if re.search(
334
+ r"(?:device_token|push_token|fcm_token|apns_token)\s*.*(?:VARCHAR|TEXT|STRING|str)",
335
+ line,
336
+ re.IGNORECASE,
337
+ ):
338
+ findings.append(
339
+ make_finding(
340
+ severity=Severity.MEDIUM,
341
+ file=rel,
342
+ line_start=i,
343
+ title="PUSH-05: Push Token Stored Without Encryption",
344
+ description="Push token stored in a plaintext database column.",
345
+ evidence=line.strip()[:120],
346
+ suggestion="Encrypt push tokens at rest or store hashed.",
347
+ )
348
+ )
349
+ # Token logged
350
+ if re.search(
351
+ r"(?:log|print|debug|console).*(?:token|device_token|push_token)",
352
+ line,
353
+ re.IGNORECASE,
354
+ ):
355
+ findings.append(
356
+ make_finding(
357
+ severity=Severity.MEDIUM,
358
+ file=rel,
359
+ line_start=i,
360
+ title="PUSH-04: Push Token Logged",
361
+ description="Push token may be written to logs.",
362
+ evidence=line.strip()[:120],
363
+ suggestion="Never log push tokens; they can be used to impersonate users.",
364
+ )
365
+ )
366
+ except Exception as e:
367
+ _log.warning("PushNotificationAgent._scan_token_storage failed: %s", e)
368
+ return findings
369
+
370
+ # ── External tool: gitleaks ────────────────────────────────────────────
371
+
372
+ def _run_gitleaks(self, inp: AgentInput) -> list[Finding]:
373
+ """Run gitleaks to detect hardcoded push notification secrets."""
374
+ findings: list[Finding] = []
375
+ try:
376
+ proc = subprocess.run(
377
+ [
378
+ "gitleaks",
379
+ "detect",
380
+ "--source",
381
+ str(inp.root),
382
+ "--report-format",
383
+ "json",
384
+ "--redact",
385
+ ],
386
+ capture_output=True,
387
+ text=True,
388
+ timeout=120,
389
+ )
390
+ if proc.stdout.strip():
391
+ import json
392
+
393
+ try:
394
+ data = json.loads(proc.stdout)
395
+ for item in data if isinstance(data, list) else []:
396
+ rule = item.get("RuleID", "").lower()
397
+ description = item.get("Description", "")
398
+ if any(
399
+ kw in rule or kw in description.lower()
400
+ for kw in ("fcm", "firebase", "apns", "push", "notification")
401
+ ):
402
+ findings.append(
403
+ make_finding(
404
+ severity=Severity.CRITICAL,
405
+ file=item.get("File", "(gitleaks)"),
406
+ line_start=item.get("StartLine", 0),
407
+ title="PUSH-01: Push Secret Detected by Gitleaks",
408
+ description=f"Gitleaks detected: {item.get('RuleID', 'unknown')}",
409
+ evidence=item.get("Match", "")[:120],
410
+ suggestion="Remove the secret and rotate credentials immediately.",
411
+ )
412
+ )
413
+ except json.JSONDecodeError:
414
+ pass
415
+ except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
416
+ pass
417
+ return findings
@@ -0,0 +1,143 @@
1
+ """
2
+ PysaAgent - Python Static Analyzer integration.
3
+
4
+ Pysa (Python Static Analyzer) is a static analysis tool for Python that focuses on
5
+ security vulnerabilities like SQL injection, XSS, and other taint-style vulnerabilities.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ import logging
11
+ import subprocess
12
+ import tempfile
13
+ from pathlib import Path
14
+ from typing import Any
15
+
16
+ from patchi.core.agents.base import (
17
+ AgentDomain,
18
+ AgentGroup,
19
+ AgentInput,
20
+ AgentResult,
21
+ AgentStatus,
22
+ BaseAgent,
23
+ register,
24
+ )
25
+
26
+ _log = logging.getLogger("patchi.security.agents.pysa_agent")
27
+
28
+
29
+ @register
30
+ class PysaAgent(BaseAgent):
31
+ """
32
+ PysaAgent - Python Static Analyzer integration.
33
+
34
+ Pysa is a static analysis tool for Python that focuses on security vulnerabilities
35
+ through taint analysis. It tracks data flow from sources to sinks.
36
+ """
37
+
38
+ group = AgentGroup.SECURITY
39
+ domain = AgentDomain.CODE_QUALITY
40
+ name = "PysaAgent"
41
+ description = "Pysa static analyzer for Python security vulnerabilities"
42
+ timeout = 120
43
+
44
+ def _run(self, inp: AgentInput, result: AgentResult) -> None:
45
+ """Run Pysa analysis on the project."""
46
+ if not self._is_pysa_available():
47
+ result.status = AgentStatus.SKIPPED
48
+ result.data["error"] = "Pysa not installed"
49
+ return
50
+
51
+ findings = self._run_pysa(inp.root)
52
+
53
+ for finding_data in findings:
54
+ finding = self._create_finding(finding_data)
55
+ result.add_finding(finding)
56
+
57
+ result.status = AgentStatus.DONE
58
+ result.files_scanned = 1
59
+
60
+ def _is_pysa_available(self) -> bool:
61
+ """Check if Pysa is installed and available."""
62
+ try:
63
+ result = subprocess.run(["pysa", "--version"], capture_output=True, timeout=10)
64
+ return result.returncode == 0
65
+ except FileNotFoundError:
66
+ return False
67
+
68
+ def _run_pysa(self, root: Path) -> list[dict[str, Any]]:
69
+ """Run Pysa and parse JSON output."""
70
+ findings = []
71
+
72
+ with tempfile.TemporaryDirectory() as tmpdir:
73
+ Path(tmpdir) / "pysa_output.json"
74
+
75
+ try:
76
+ subprocess.run(
77
+ [
78
+ "pysa",
79
+ "analyze",
80
+ "--output-format",
81
+ "json",
82
+ "--output",
83
+ str(tmpdir / "pysa_output"),
84
+ str(root),
85
+ ],
86
+ capture_output=True,
87
+ timeout=120,
88
+ cwd=root,
89
+ )
90
+
91
+ # Parse Pysa JSON output
92
+ output_dir = Path(tmpdir) / "pysa_output"
93
+ if output_dir.exists():
94
+ for json_file in output_dir.glob("*.json"):
95
+ with open(json_file) as f:
96
+ data = json.load(f)
97
+ findings.extend(self._parse_pysa_output(data))
98
+
99
+ except subprocess.TimeoutExpired:
100
+ _log.warning("Pysa timed out")
101
+ except Exception as e:
102
+ _log.warning(f"Pysa execution failed: {e}")
103
+
104
+ return findings
105
+
106
+ def _parse_pysa_output(self, data: dict[str, Any]) -> list[dict[str, Any]]:
107
+ """Parse Pysa JSON output into findings."""
108
+ findings = []
109
+
110
+ for issue in data.get("issues", []):
111
+ findings.append(
112
+ {
113
+ "type": issue.get("code", "pysa_finding"),
114
+ "severity": issue.get("severity", "MEDIUM").upper(),
115
+ "file": issue.get("file", ""),
116
+ "line": issue.get("line", 0),
117
+ "message": issue.get("message", ""),
118
+ "cwe": issue.get("cwe", ""),
119
+ "confidence": 0.8,
120
+ }
121
+ )
122
+ return findings
123
+
124
+ def _create_finding(self, finding_data: dict[str, Any]):
125
+ """Create a Finding object from Pysa output."""
126
+ from patchi.core.security.tool_adapters import make_tool_finding
127
+
128
+ return make_tool_finding(
129
+ agent="PysaAgent",
130
+ ftype=finding_data.get("type", "pysa_finding"),
131
+ raw_severity=finding_data.get("severity", "MEDIUM"),
132
+ file=finding_data.get("file", ""),
133
+ line=finding_data.get("line", 0),
134
+ message=finding_data.get("message", ""),
135
+ cwe=finding_data.get("cwe", ""),
136
+ snippet=finding_data.get("snippet", ""),
137
+ confidence_raw=0.8, # taint-path findings: tool asserts a full flow
138
+ extra={"flow": finding_data.get("flow", [])},
139
+ )
140
+
141
+
142
+ # Register the agent
143
+ register(PysaAgent)