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,245 @@
1
+ """
2
+ Runtime Security Validation (Layer 5) — DAST for web apps.
3
+
4
+ Deploys the generated app in an isolated environment and runs:
5
+ - Security header validation on actual HTTP responses
6
+ - TLS/SSL configuration checks
7
+ - HTTP method tampering tests
8
+ - Server information leakage detection
9
+ - Basic active probing (when dev mode enabled)
10
+
11
+ Requires the app to be running (or spins up via Docker if available).
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import logging
17
+ import subprocess
18
+ import urllib.error
19
+ import urllib.request
20
+
21
+ from patchi.core.agents.base import (
22
+ AgentGroup,
23
+ AgentInput,
24
+ AgentResult,
25
+ BaseAgent,
26
+ Finding,
27
+ Severity,
28
+ register,
29
+ )
30
+ from patchi.core.constants import is_offline
31
+
32
+ # ── Expected security headers ─────────────────────────────────────────────────
33
+
34
+ _REQUIRED_HEADERS = {
35
+ "strict-transport-security": ("HSTS missing", Severity.HIGH, "CWE-319"),
36
+ "x-content-type-options": ("X-Content-Type-Options missing", Severity.MEDIUM, "CWE-693"),
37
+ "x-frame-options": ("X-Frame-Options missing (clickjacking)", Severity.MEDIUM, "CWE-1021"),
38
+ "x-xss-protection": ("X-XSS-Protection missing", Severity.LOW, "CWE-79"),
39
+ "content-security-policy": ("Content-Security-Policy missing", Severity.HIGH, "CWE-693"),
40
+ "referrer-policy": ("Referrer-Policy missing", Severity.LOW, "CWE-200"),
41
+ "permissions-policy": ("Permissions-Policy missing", Severity.LOW, "CWE-250"),
42
+ }
43
+
44
+ _DANGEROUS_HEADERS = {
45
+ "server": ("Server header leaks version info", Severity.LOW, "CWE-200"),
46
+ "x-powered-by": ("X-Powered-By header leaks technology stack", Severity.LOW, "CWE-200"),
47
+ }
48
+
49
+ _SENSITIVE_METHODS = ["PUT", "DELETE", "PATCH", "TRACE", "OPTIONS"]
50
+
51
+
52
+ _log = logging.getLogger("patchi.security.runtime_validator")
53
+
54
+
55
+ def _check_headers(url: str) -> list[dict]:
56
+ """Fetch URL and check security headers."""
57
+ findings = []
58
+ try:
59
+ req = urllib.request.Request(url, method="GET")
60
+ req.add_header("User-Agent", "Patchi-SecurityValidator/1.0")
61
+ with urllib.request.urlopen(req, timeout=10) as resp:
62
+ headers = {k.lower(): v for k, v in resp.headers.items()}
63
+
64
+ for header, (msg, sev, cwe) in _REQUIRED_HEADERS.items():
65
+ if header not in headers:
66
+ findings.append(
67
+ {"message": msg, "severity": sev.value, "cwe": cwe, "header": header}
68
+ )
69
+
70
+ for header, (msg, sev, cwe) in _DANGEROUS_HEADERS.items():
71
+ if header in headers:
72
+ findings.append(
73
+ {
74
+ "message": f"{msg}: {headers[header]}",
75
+ "severity": sev.value,
76
+ "cwe": cwe,
77
+ "header": header,
78
+ }
79
+ )
80
+
81
+ except (urllib.error.URLError, urllib.error.HTTPError, OSError, ValueError):
82
+ pass
83
+
84
+ return findings
85
+
86
+
87
+ def _check_method_tampering(base_url: str) -> list[dict]:
88
+ """Test if sensitive HTTP methods are allowed."""
89
+ findings = []
90
+ for method in _SENSITIVE_METHODS:
91
+ try:
92
+ req = urllib.request.Request(base_url, method=method)
93
+ req.add_header("User-Agent", "Patchi-SecurityValidator/1.0")
94
+ with urllib.request.urlopen(req, timeout=5) as resp:
95
+ if resp.status < 400:
96
+ findings.append(
97
+ {
98
+ "message": f"HTTP {method} allowed on {base_url} (status {resp.status})",
99
+ "severity": "medium" if method in ("PUT", "DELETE", "PATCH") else "low",
100
+ "cwe": "CWE-749",
101
+ }
102
+ )
103
+ except (urllib.error.HTTPError, urllib.error.URLError, OSError):
104
+ # 405 Method Not Allowed is expected
105
+ pass
106
+ except Exception as e:
107
+ _log.warning("_check_method_tampering failed: %s", e)
108
+
109
+ return findings
110
+
111
+
112
+ def _check_tls(url: str) -> list[dict]:
113
+ """Basic TLS check — verify HTTPS and cert validity."""
114
+ findings = []
115
+ if url.startswith("http://"):
116
+ findings.append(
117
+ {
118
+ "message": "Application uses plain HTTP — no TLS",
119
+ "severity": "high",
120
+ "cwe": "CWE-319",
121
+ }
122
+ )
123
+
124
+ # Try sslyze if available
125
+ try:
126
+ from urllib.parse import urlparse
127
+
128
+ parsed = urlparse(url)
129
+ hostname = parsed.hostname or ""
130
+ if hostname:
131
+ proc = subprocess.run(
132
+ ["sslyze", "--json", hostname],
133
+ capture_output=True,
134
+ text=True,
135
+ timeout=15,
136
+ )
137
+ if proc.returncode == 0 and proc.stdout.strip():
138
+ import json
139
+
140
+ data = json.loads(proc.stdout)
141
+ for scan in data.get("scan_result", {}).get("tls_cipher_suites", []):
142
+ for suite in scan.get("tls_cipher_suites", []):
143
+ name = suite.get("name", "")
144
+ if any(
145
+ weak in name.upper() for weak in ("RC4", "DES", "NULL", "EXPORT", "MD5")
146
+ ):
147
+ findings.append(
148
+ {
149
+ "message": f"Weak TLS cipher: {name}",
150
+ "severity": "high",
151
+ "cwe": "CWE-326",
152
+ }
153
+ )
154
+ except (FileNotFoundError, subprocess.TimeoutExpired, Exception):
155
+ pass
156
+
157
+ return findings
158
+
159
+
160
+ # ── Runtime Validator Agent ───────────────────────────────────────────────────
161
+
162
+
163
+ @register
164
+ class RuntimeValidatorAgent(BaseAgent):
165
+ """Runtime security validation: headers, TLS, method tampering."""
166
+
167
+ name = "RuntimeValidatorAgent"
168
+ group = AgentGroup.SECURITY
169
+ timeout = 60
170
+
171
+ def _run(self, inp: AgentInput, result: AgentResult) -> None:
172
+ if is_offline():
173
+ result.add_finding(
174
+ Finding(
175
+ agent=self.name,
176
+ type="skipped_offline",
177
+ severity=Severity.INFO,
178
+ file="",
179
+ message="Runtime validation skipped — offline mode (no live probing)",
180
+ )
181
+ )
182
+ return
183
+
184
+ # Look for configured URL in brain or config
185
+ app_url = (
186
+ inp.brain.get("app_url", "")
187
+ or inp.config.get("app_url", "")
188
+ or inp.extra.get("app_url", "")
189
+ )
190
+
191
+ if not app_url:
192
+ result.add_finding(
193
+ Finding(
194
+ agent=self.name,
195
+ type="no_target_url",
196
+ severity=Severity.INFO,
197
+ file="",
198
+ message="No app URL configured — set 'app_url' in config or brain to enable runtime validation",
199
+ )
200
+ )
201
+ return
202
+
203
+ # 1. Header checks
204
+ header_findings = _check_headers(app_url)
205
+ for f in header_findings:
206
+ result.add_finding(
207
+ Finding(
208
+ agent=self.name,
209
+ type="missing_header" if "missing" in f["message"] else "header_leak",
210
+ severity=Severity(f["severity"]),
211
+ file=app_url,
212
+ message=f["message"],
213
+ cwe=f["cwe"],
214
+ )
215
+ )
216
+
217
+ # 2. TLS check
218
+ tls_findings = _check_tls(app_url)
219
+ for f in tls_findings:
220
+ result.add_finding(
221
+ Finding(
222
+ agent=self.name,
223
+ type="tls_issue",
224
+ severity=Severity(f["severity"]),
225
+ file=app_url,
226
+ message=f["message"],
227
+ cwe=f["cwe"],
228
+ )
229
+ )
230
+
231
+ # 3. Method tampering
232
+ method_findings = _check_method_tampering(app_url)
233
+ for f in method_findings:
234
+ result.add_finding(
235
+ Finding(
236
+ agent=self.name,
237
+ type="method_tampering",
238
+ severity=Severity(f["severity"]),
239
+ file=app_url,
240
+ message=f["message"],
241
+ cwe=f["cwe"],
242
+ )
243
+ )
244
+
245
+ result.files_scanned = 1
@@ -0,0 +1,401 @@
1
+ """SAML/SSO security agent — validates SAML implementations against known attack patterns."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ import re
7
+ import shutil
8
+ import subprocess
9
+
10
+ from ..agents.base import (
11
+ AgentDomain,
12
+ AgentGroup,
13
+ AgentInput,
14
+ AgentResult,
15
+ AgentStatus,
16
+ BaseAgent,
17
+ Finding,
18
+ Severity,
19
+ make_finding,
20
+ register,
21
+ safe_rglob,
22
+ )
23
+ from ..brain.trace_log import trace_agent
24
+
25
+ # SAML library detection patterns
26
+ SAML_LIBRARIES = {
27
+ "pysaml2": {
28
+ "import": r"from\s+saml2\b|import\s+saml2\b",
29
+ "config": r"saml2\.config|SPConfig|IdPConfig",
30
+ },
31
+ "onelogin": {
32
+ "import": r"from\s+onelogin\.saml2\b|import\s+onelogin\.saml2\b",
33
+ "auth": r"OneLogin_Saml2_Auth|AuthnRequest",
34
+ },
35
+ "python3-saml": {
36
+ "import": r"from\s+onelogin\.saml2\b|import\s+onelogin\.saml2\b",
37
+ "settings": r"saml_settings|SAML_SETTINGS",
38
+ },
39
+ "lxml-saml": {
40
+ "import": r"from\s+lxml\.etree\b|import\s+lxml\.etree\b",
41
+ "etree": r"etree\.fromstring|etree\.parse|etree\.XML",
42
+ },
43
+ }
44
+
45
+ # Dangerous XML patterns (XXE, XSW)
46
+ XXE_PATTERNS = [
47
+ (r"resolve_entities\s*=\s*True", "XML entity resolution enabled"),
48
+ (r"no_network\s*=\s*False", "Network access during XML parsing"),
49
+ (r"DTDLOAD|dtd_load|load_dtd", "DTD loading enabled"),
50
+ (r"ENTITY|entity|<!ENTITY", "XML entity declaration (potential XXE)"),
51
+ (r"SYSTEM\s+[\"']", "XML SYSTEM entity reference"),
52
+ ]
53
+
54
+ XSW_PATTERNS = [
55
+ (r"cloneNode|importNode|appendChild.*Signature", "XML Signature Wrapping — node cloning"),
56
+ (r"insertBefore.*Signature", "XML Signature Wrapping — insert before signature"),
57
+ (r"getElementById.*Signature", "XML Signature Wrapping — ID reference"),
58
+ ]
59
+
60
+ SIGNATURE_VALIDATION_PATTERNS = [
61
+ (
62
+ r"check_signature|validate_signature|verify_signature|isValidSignature",
63
+ "Signature validation present",
64
+ ),
65
+ (r"assertion.*valid|validate.*assertion", "Assertion validation"),
66
+ (r"SignedInfo|Reference.*URI.*DigestValue", "XML signature structure detected"),
67
+ ]
68
+
69
+ ASSERTION_LIFETIME_PATTERNS = [
70
+ (r"NotBefore|NotOnOrAfter", "Assertion time bounds present"),
71
+ (r"SessionIndex|session_not_on_or_after", "Session index tracking"),
72
+ (r"clock_slop|clockSkew|ALLOWED_CLOCK_SKEW", "Clock skew tolerance configured"),
73
+ ]
74
+
75
+ RELAYSTATE_PATTERNS = [
76
+ (r"RelayState|relay_state", "RelayState handling detected"),
77
+ (r"redirect.*url|return.*url|target.*url", "Redirect URL handling"),
78
+ ]
79
+
80
+ CONTROL_IDS = {
81
+ "SAML-01": ("SAML XXE in XML parsing", Severity.CRITICAL),
82
+ "SAML-02": ("XML Signature Wrapping attack vector", Severity.CRITICAL),
83
+ "SAML-03": ("Weak algorithm enforcement (SHA-1)", Severity.HIGH),
84
+ "SAML-04": ("Missing signature validation", Severity.HIGH),
85
+ "SAML-05": ("No assertion lifetime enforcement", Severity.HIGH),
86
+ "SAML-06": ("RelayState open redirect", Severity.MEDIUM),
87
+ "SAML-07": ("SAML library in use", Severity.INFO),
88
+ "SAML-08": ("Assertion replay protection missing", Severity.HIGH),
89
+ "SAML-09": ("Certificate trust not pinned", Severity.MEDIUM),
90
+ }
91
+
92
+
93
+ _log = logging.getLogger("patchi.security.saml_sso_agent")
94
+
95
+
96
+ @register
97
+ class SamlSSOAgent(BaseAgent):
98
+ name = "SamlSSOAgent"
99
+ group = AgentGroup.SECURITY
100
+ domain = AgentDomain.SECURITY
101
+ description = (
102
+ "SAML/SSO security: XSW, XXE, algorithm enforcement, assertion replay, certificate trust"
103
+ )
104
+
105
+ def _run(self, inp: AgentInput, result: AgentResult) -> None:
106
+ with trace_agent(self.name, inp.root) as trace:
107
+ findings: list[Finding] = []
108
+ files_scanned = 0
109
+
110
+ # Phase 1: Static analysis of source code
111
+ for fpath in safe_rglob(inp.root, "*.py"):
112
+ files_scanned += 1
113
+ try:
114
+ content = fpath.read_text(encoding="utf-8", errors="replace")
115
+ except Exception as e:
116
+ _log.warning("SamlSSOAgent._run failed: %s", e)
117
+ continue
118
+
119
+ rel = str(fpath.relative_to(inp.root))
120
+
121
+ # Detect SAML library usage
122
+ for lib_name, lib_patterns in SAML_LIBRARIES.items():
123
+ for pattern_key, pattern in lib_patterns.items():
124
+ for m in re.finditer(pattern, content, re.IGNORECASE):
125
+ line_no = content[: m.start()].count("\n") + 1
126
+ findings.append(
127
+ make_finding(
128
+ Severity.INFO,
129
+ rel,
130
+ line_no,
131
+ f"SAML library detected: {lib_name}",
132
+ f"Code uses {lib_name} library ({pattern_key} pattern). Verify SAML security configuration.",
133
+ code_snippet=m.group(),
134
+ suggestion=f"Review {lib_name} configuration for security best practices",
135
+ control_id="SAML-07",
136
+ )
137
+ )
138
+
139
+ # Check for XXE patterns
140
+ for pattern, desc in XXE_PATTERNS:
141
+ for m in re.finditer(pattern, content, re.IGNORECASE):
142
+ line_no = content[: m.start()].count("\n") + 1
143
+ findings.append(
144
+ make_finding(
145
+ Severity.CRITICAL,
146
+ rel,
147
+ line_no,
148
+ "SAML-01: XML External Entity (XXE) vulnerability",
149
+ desc,
150
+ code_snippet=m.group(),
151
+ suggestion="Disable DTD loading and entity resolution in XML parser",
152
+ cwe="CWE-611",
153
+ control_id="SAML-01",
154
+ )
155
+ )
156
+
157
+ # Check for XSW patterns
158
+ for pattern, desc in XSW_PATTERNS:
159
+ for m in re.finditer(pattern, content, re.IGNORECASE):
160
+ line_no = content[: m.start()].count("\n") + 1
161
+ findings.append(
162
+ make_finding(
163
+ Severity.CRITICAL,
164
+ rel,
165
+ line_no,
166
+ "SAML-02: XML Signature Wrapping attack vector",
167
+ desc,
168
+ code_snippet=m.group(),
169
+ suggestion="Use a SAML library that prevents Signature Wrapping attacks",
170
+ cwe="CWE-327",
171
+ control_id="SAML-02",
172
+ )
173
+ )
174
+
175
+ # Check for weak algorithms (SHA-1)
176
+ weak_algo = re.findall(r"sha-?1|SHA-?1|md5|MD5", content)
177
+ if weak_algo:
178
+ for m in re.finditer(r"sha-?1|SHA-?1|md5|MD5", content):
179
+ line_no = content[: m.start()].count("\n") + 1
180
+ findings.append(
181
+ make_finding(
182
+ Severity.HIGH,
183
+ rel,
184
+ line_no,
185
+ "SAML-03: Weak cryptographic algorithm (SHA-1/MD5)",
186
+ "SAML assertion uses deprecated hash algorithm. SHA-1 is vulnerable to collision attacks.",
187
+ code_snippet=m.group(),
188
+ suggestion="Upgrade to SHA-256 or stronger algorithms",
189
+ cwe="CWE-327",
190
+ control_id="SAML-03",
191
+ )
192
+ )
193
+
194
+ # Check for missing signature validation
195
+ has_sig_validation = any(
196
+ re.search(p, content, re.IGNORECASE) for p, _ in SIGNATURE_VALIDATION_PATTERNS
197
+ )
198
+ if (
199
+ re.search(
200
+ r"saml2?\b|onelogin|AuthnRequest|SAMLResponse", content, re.IGNORECASE
201
+ )
202
+ and not has_sig_validation
203
+ ):
204
+ findings.append(
205
+ make_finding(
206
+ Severity.HIGH,
207
+ rel,
208
+ 0,
209
+ "SAML-04: Missing signature validation",
210
+ "SAML processing code detected without signature validation. Assertions may be forged.",
211
+ suggestion="Implement XML signature validation before processing assertions",
212
+ cwe="CWE-347",
213
+ control_id="SAML-04",
214
+ )
215
+ )
216
+
217
+ # Check for assertion lifetime
218
+ has_lifetime = any(
219
+ re.search(p, content, re.IGNORECASE) for p, _ in ASSERTION_LIFETIME_PATTERNS
220
+ )
221
+ if (
222
+ re.search(r"saml2?\b|assertion|SAMLResponse", content, re.IGNORECASE)
223
+ and not has_lifetime
224
+ ):
225
+ findings.append(
226
+ make_finding(
227
+ Severity.HIGH,
228
+ rel,
229
+ 0,
230
+ "SAML-05: No assertion lifetime enforcement",
231
+ "SAML assertions processed without checking NotBefore/NotOnOrAfter. Replay attacks possible.",
232
+ suggestion="Enforce assertion time bounds and session index tracking",
233
+ cwe="CWE-294",
234
+ control_id="SAML-05",
235
+ )
236
+ )
237
+
238
+ # Check for RelayState validation
239
+ has_relay = any(
240
+ re.search(p, content, re.IGNORECASE) for p, _ in RELAYSTATE_PATTERNS
241
+ )
242
+ if has_relay:
243
+ # Check if validation is present
244
+ if not re.search(
245
+ r"whitelist|allowlist|validate.*relay|check.*relay|sanitize.*relay",
246
+ content,
247
+ re.IGNORECASE,
248
+ ):
249
+ findings.append(
250
+ make_finding(
251
+ Severity.MEDIUM,
252
+ rel,
253
+ 0,
254
+ "SAML-06: RelayState open redirect potential",
255
+ "RelayState parameter used without explicit validation. May allow open redirect attacks.",
256
+ suggestion="Validate RelayState against an allowlist of permitted redirect URLs",
257
+ cwe="CWE-601",
258
+ control_id="SAML-06",
259
+ )
260
+ )
261
+
262
+ # Check for assertion replay protection
263
+ if re.search(r"saml2?\b|SAMLResponse", content, re.IGNORECASE):
264
+ if not re.search(
265
+ r"SessionIndex|session_index|replay|nonce|jti", content, re.IGNORECASE
266
+ ):
267
+ findings.append(
268
+ make_finding(
269
+ Severity.HIGH,
270
+ rel,
271
+ 0,
272
+ "SAML-08: Assertion replay protection missing",
273
+ "No session index or nonce tracking detected. SAML assertions may be replayed.",
274
+ suggestion="Track assertion IDs and session indices to prevent replay attacks",
275
+ cwe="CWE-294",
276
+ control_id="SAML-08",
277
+ )
278
+ )
279
+
280
+ # Check for certificate trust pinning
281
+ if re.search(r"certificate|cert|X509|trust", content, re.IGNORECASE):
282
+ if not re.search(
283
+ r"pin|trust_store|ca_bundle|certificate_file|verify.*cert",
284
+ content,
285
+ re.IGNORECASE,
286
+ ):
287
+ findings.append(
288
+ make_finding(
289
+ Severity.MEDIUM,
290
+ rel,
291
+ 0,
292
+ "SAML-09: Certificate trust not pinned",
293
+ "SAML certificate handling without explicit trust pinning. May accept forged certificates.",
294
+ suggestion="Pin IdP certificate or use a trusted CA bundle for validation",
295
+ cwe="CWE-295",
296
+ control_id="SAML-09",
297
+ )
298
+ )
299
+
300
+ # Phase 2: Scan YAML/JSON configs for SAML metadata
301
+ for fpath in safe_rglob(inp.root, "*.xml"):
302
+ files_scanned += 1
303
+ try:
304
+ content = fpath.read_text(encoding="utf-8", errors="replace")
305
+ except Exception as e:
306
+ _log.warning("SamlSSOAgent._run failed: %s", e)
307
+ continue
308
+
309
+ rel = str(fpath.relative_to(inp.root))
310
+
311
+ # Check for SAML metadata files
312
+ if re.search(r"EntityDescriptor|SPSSODescriptor|IDPSSODescriptor", content):
313
+ # Check for SHA-1 in metadata
314
+ if re.search(r"SHA-1|sha1|DigestMethod.*SHA-1", content):
315
+ findings.append(
316
+ make_finding(
317
+ Severity.HIGH,
318
+ rel,
319
+ 0,
320
+ "SAML-03: Weak algorithm in SAML metadata",
321
+ "SAML metadata specifies SHA-1 digest method. Upgrade to SHA-256.",
322
+ suggestion="Update DigestMethod and SignatureMethod to SHA-256 or stronger",
323
+ cwe="CWE-327",
324
+ control_id="SAML-03",
325
+ )
326
+ )
327
+
328
+ # Check for WantAssertionsSigned
329
+ if re.search(r"WantAssertionsSigned\s*=\s*[\"']false[\"']", content):
330
+ findings.append(
331
+ make_finding(
332
+ Severity.HIGH,
333
+ rel,
334
+ 0,
335
+ "SAML-04: Assertions not required to be signed",
336
+ "WantAssertionsSigned=false allows unsigned assertions. Forged assertions accepted.",
337
+ suggestion="Set WantAssertionsSigned=true in SP metadata",
338
+ cwe="CWE-347",
339
+ control_id="SAML-04",
340
+ )
341
+ )
342
+
343
+ # Phase 3: Try external SAST tools (semgrep has SAML rules)
344
+ semgrep_path = shutil.which("semgrep")
345
+ if semgrep_path:
346
+ from .security_config import _semgrep_config_value
347
+ semgrep_config = _semgrep_config_value(inp.root)
348
+ if not semgrep_config:
349
+ semgrep_path = None # offline + no local pack -> skip
350
+ if semgrep_path:
351
+ try:
352
+ proc = subprocess.run(
353
+ [
354
+ semgrep_path,
355
+ "--config",
356
+ semgrep_config,
357
+ "--include",
358
+ "*.py",
359
+ "--json",
360
+ "--quiet",
361
+ str(inp.root),
362
+ ],
363
+ capture_output=True,
364
+ text=True,
365
+ timeout=120,
366
+ )
367
+ if proc.returncode == 0 and proc.stdout:
368
+ import json
369
+
370
+ try:
371
+ data = json.loads(proc.stdout)
372
+ for r in data.get("results", []):
373
+ rule_id = r.get("check_id", "")
374
+ if (
375
+ "saml" in rule_id.lower()
376
+ or "xxe" in rule_id.lower()
377
+ or "xml" in rule_id.lower()
378
+ ):
379
+ findings.append(
380
+ make_finding(
381
+ Severity.HIGH,
382
+ r.get("path", ""),
383
+ r.get("start", {}).get("line", 0),
384
+ f"Semgrep: {rule_id}",
385
+ r.get("extra", {}).get("message", ""),
386
+ code_snippet=r.get("extra", {}).get("lines", ""),
387
+ suggestion="Review semgrep finding for SAML security impact",
388
+ )
389
+ )
390
+ except json.JSONDecodeError:
391
+ pass
392
+ except (subprocess.TimeoutExpired, Exception):
393
+ pass
394
+
395
+ result.findings = findings
396
+ result.files_scanned = files_scanned
397
+ result.status = AgentStatus.DONE
398
+ trace.findings = len(findings)
399
+ trace.files_scanned = files_scanned
400
+
401
+ return