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,333 @@
1
+ """
2
+ Browser Tester — Playwright-based security testing for live web apps.
3
+
4
+ Drives a real browser through the app map to test:
5
+ - Authentication bypass (bad/empty credentials)
6
+ - XSS injection in form fields
7
+ - Authorization checks (accessing pages without session)
8
+ - Form submission with malicious payloads
9
+ - Coverage: clicking every button/link
10
+
11
+ Requires: pip install playwright && playwright install
12
+ Falls back gracefully when Playwright is not installed.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import logging
18
+
19
+ from patchi.core.agents.base import (
20
+ AgentGroup,
21
+ AgentInput,
22
+ AgentResult,
23
+ BaseAgent,
24
+ Finding,
25
+ Severity,
26
+ register,
27
+ )
28
+
29
+ # ── Test payloads ─────────────────────────────────────────────────────────────
30
+
31
+ _XSS_PAYLOADS = [
32
+ '<script>alert("xss")</script>',
33
+ '"><img src=x onerror=alert(1)>',
34
+ "javascript:alert(1)",
35
+ "<svg/onload=alert(1)>",
36
+ "{{7*7}}",
37
+ "${7*7}",
38
+ ]
39
+
40
+ _AUTH_BYPASS_PAYLOADS = [
41
+ {"username": "admin", "password": ""},
42
+ {"username": "admin", "password": "admin"},
43
+ {"username": "' OR '1'='1", "password": "' OR '1'='1"},
44
+ {"username": "admin", "password": "password"},
45
+ {"username": "administrator", "password": ""},
46
+ ]
47
+
48
+ _SQLI_PAYLOADS = [
49
+ "' OR '1'='1",
50
+ "1; DROP TABLE users--",
51
+ "' UNION SELECT NULL--",
52
+ "admin'--",
53
+ ]
54
+
55
+
56
+ _log = logging.getLogger("patchi.security.browser_tester")
57
+
58
+
59
+ def _check_playwright_available() -> bool:
60
+ try:
61
+ import importlib.util
62
+
63
+ return importlib.util.find_spec("playwright") is not None
64
+ except (ImportError, ValueError):
65
+ return False
66
+
67
+
68
+ def patchi_browser_test(url: str, app_map: dict | None = None) -> list[dict]:
69
+ """Run browser-based security tests against a live app."""
70
+ if not _check_playwright_available():
71
+ return [
72
+ {
73
+ "type": "playwright_unavailable",
74
+ "message": "Playwright not installed — pip install playwright",
75
+ }
76
+ ]
77
+
78
+ findings = []
79
+
80
+ try:
81
+ from playwright.sync_api import sync_playwright
82
+
83
+ with sync_playwright() as p:
84
+ browser = p.chromium.launch(headless=True)
85
+ context = browser.new_context(
86
+ user_agent="Patchi-SecurityTester/1.0",
87
+ ignore_https_errors=True,
88
+ )
89
+ page = context.new_page()
90
+
91
+ # 1. Auth bypass test
92
+ findings.extend(_test_auth_bypass(page, url))
93
+
94
+ # 2. XSS injection in forms
95
+ findings.extend(_test_xss_in_forms(page, url, app_map))
96
+
97
+ # 3. SQLi in form fields
98
+ findings.extend(_test_sqli_in_forms(page, url, app_map))
99
+
100
+ browser.close()
101
+
102
+ except Exception as e:
103
+ findings.append({"type": "browser_test_error", "message": f"Browser test failed: {e}"})
104
+
105
+ return findings
106
+
107
+
108
+ def _test_auth_bypass(page, url: str) -> list[dict]:
109
+ """Try logging in with bad credentials."""
110
+ findings = []
111
+ try:
112
+ page.goto(url, timeout=10000)
113
+
114
+ # Look for login forms
115
+ login_forms = page.query_selector_all(
116
+ 'form[action*="login"], form[action*="auth"], form:has(input[type="password"])'
117
+ )
118
+
119
+ for form in login_forms:
120
+ for payload in _AUTH_BYPASS_PAYLOADS:
121
+ try:
122
+ username_input = form.query_selector(
123
+ 'input[name="username"], input[name="email"], input[type="email"]'
124
+ )
125
+ password_input = form.query_selector('input[type="password"]')
126
+
127
+ if username_input and password_input:
128
+ username_input.fill(payload["username"])
129
+ password_input.fill(payload["password"])
130
+ form.evaluate("el => el.submit()")
131
+
132
+ page.wait_for_timeout(2000)
133
+
134
+ # Check if we got past login (no login form visible)
135
+ still_on_login = page.query_selector('input[type="password"]')
136
+ if not still_on_login:
137
+ findings.append(
138
+ {
139
+ "type": "auth_bypass",
140
+ "severity": "critical",
141
+ "message": f"Possible auth bypass with credentials: {payload['username']}/{payload['password']}",
142
+ "cwe": "CWE-287",
143
+ }
144
+ )
145
+ break
146
+
147
+ page.goto(url, timeout=10000)
148
+ except Exception as e:
149
+ _log.warning("_test_auth_bypass failed: %s", e)
150
+ continue
151
+
152
+ except Exception as e:
153
+ _log.warning("_test_auth_bypass failed: %s", e)
154
+
155
+ return findings
156
+
157
+
158
+ def _test_xss_in_forms(page, url: str, app_map: dict | None) -> list[dict]:
159
+ """Inject XSS payloads into form fields."""
160
+ findings = []
161
+
162
+ pages_to_test = []
163
+ if app_map and "pages" in app_map:
164
+ pages_to_test = [(p["url"], p["forms"]) for p in app_map["pages"] if p["forms"]]
165
+ else:
166
+ pages_to_test = [(url, [])]
167
+
168
+ for page_url, _forms in pages_to_test:
169
+ try:
170
+ page.goto(page_url, timeout=10000)
171
+ except Exception as e:
172
+ _log.warning("_test_xss_in_forms failed: %s", e)
173
+ continue
174
+
175
+ page_forms = page.query_selector_all("form")
176
+ for form in page_forms:
177
+ inputs = form.query_selector_all('input[type="text"], input[type="search"], textarea')
178
+ for inp_field in inputs:
179
+ for payload in _XSS_PAYLOADS[:2]:
180
+ try:
181
+ name = inp_field.get_attribute("name") or ""
182
+ inp_field.fill(payload)
183
+
184
+ # Check if payload reflected in page source
185
+ page_content = page.content()
186
+ if payload in page_content and payload not in ("{{7*7}}", "${7*7}"):
187
+ findings.append(
188
+ {
189
+ "type": "reflected_xss",
190
+ "severity": "high",
191
+ "message": f"XSS payload reflected in form field '{name}' at {page_url}",
192
+ "cwe": "CWE-79",
193
+ "evidence": payload,
194
+ }
195
+ )
196
+ break
197
+ except Exception as e:
198
+ _log.warning("_test_xss_in_forms failed: %s", e)
199
+ continue
200
+
201
+ return findings
202
+
203
+
204
+ def _test_sqli_in_forms(page, url: str, app_map: dict | None) -> list[dict]:
205
+ """Test for SQL error-based injection in form fields."""
206
+ findings = []
207
+
208
+ pages_to_test = []
209
+ if app_map and "pages" in app_map:
210
+ pages_to_test = [(p["url"], p["forms"]) for p in app_map["pages"] if p["forms"]]
211
+ else:
212
+ pages_to_test = [(url, [])]
213
+
214
+ SQL_ERROR_PATTERNS = [
215
+ "sql syntax",
216
+ "mysql_fetch",
217
+ "sqlite3.",
218
+ "postgresql",
219
+ "ora-",
220
+ "syntax error",
221
+ "unterminated",
222
+ "pg_query",
223
+ "warning:",
224
+ "fatal error",
225
+ "microsoft odbc",
226
+ ]
227
+
228
+ for page_url, _forms in pages_to_test:
229
+ try:
230
+ page.goto(page_url, timeout=10000)
231
+ except Exception as e:
232
+ _log.warning("_test_sqli_in_forms failed: %s", e)
233
+ continue
234
+
235
+ page_forms = page.query_selector_all("form")
236
+ for form in page_forms:
237
+ inputs = form.query_selector_all('input[type="text"], input[type="search"], textarea')
238
+ for inp_field in inputs:
239
+ for payload in _SQLI_PAYLOADS[:2]:
240
+ try:
241
+ name = inp_field.get_attribute("name") or ""
242
+ inp_field.fill(payload)
243
+ form.evaluate("el => el.submit()")
244
+
245
+ page.wait_for_timeout(2000)
246
+ page_content = page.content().lower()
247
+
248
+ for pattern in SQL_ERROR_PATTERNS:
249
+ if pattern in page_content:
250
+ findings.append(
251
+ {
252
+ "type": "sql_injection_error",
253
+ "severity": "critical",
254
+ "message": f"SQL error pattern '{pattern}' after injecting '{payload}' in field '{name}' at {page_url}",
255
+ "cwe": "CWE-89",
256
+ "evidence": pattern,
257
+ }
258
+ )
259
+ break
260
+
261
+ page.goto(page_url, timeout=10000)
262
+ except Exception as e:
263
+ _log.warning("_test_sqli_in_forms failed: %s", e)
264
+ continue
265
+
266
+ return findings
267
+
268
+
269
+ # ── Browser Tester Agent ──────────────────────────────────────────────────────
270
+
271
+
272
+ @register
273
+ class BrowserTesterAgent(BaseAgent):
274
+ """Playwright-based security testing for live web apps."""
275
+
276
+ name = "BrowserTesterAgent"
277
+ group = AgentGroup.SECURITY
278
+ timeout = 120
279
+
280
+ def _run(self, inp: AgentInput, result: AgentResult) -> None:
281
+ app_url = (
282
+ inp.brain.get("app_url", "")
283
+ or inp.config.get("app_url", "")
284
+ or inp.extra.get("app_url", "")
285
+ )
286
+
287
+ if not app_url:
288
+ result.add_finding(
289
+ Finding(
290
+ agent=self.name,
291
+ type="no_target_url",
292
+ severity=Severity.INFO,
293
+ file="",
294
+ message="No app URL configured — set 'app_url' in config to enable browser testing",
295
+ )
296
+ )
297
+ return
298
+
299
+ app_map = inp.extra.get("app_map", None)
300
+ test_results = patchi_browser_test(app_url, app_map)
301
+
302
+ for tr in test_results:
303
+ if tr.get("type") == "playwright_unavailable":
304
+ result.add_finding(
305
+ Finding(
306
+ agent=self.name,
307
+ type="playwright_unavailable",
308
+ severity=Severity.INFO,
309
+ file="",
310
+ message=tr["message"],
311
+ )
312
+ )
313
+ continue
314
+ if tr.get("type") == "browser_test_error":
315
+ result.add_error(tr["message"])
316
+ continue
317
+
318
+ sev = tr.get("severity", "medium")
319
+ result.add_finding(
320
+ Finding(
321
+ agent=self.name,
322
+ type=tr.get("type", "browser_finding"),
323
+ severity=Severity(sev)
324
+ if sev in ("critical", "high", "medium", "low", "info")
325
+ else Severity.MEDIUM,
326
+ file="",
327
+ message=tr.get("message", ""),
328
+ cwe=tr.get("cwe", ""),
329
+ evidence=tr.get("evidence", ""),
330
+ )
331
+ )
332
+
333
+ result.files_scanned = 1
@@ -0,0 +1,263 @@
1
+ """
2
+ BusinessLogicAgent — Business logic abuse detection.
3
+
4
+ Detects:
5
+ - Mass assignment / broken object property level mapping
6
+ - Excessive data exposure (returning full models)
7
+ - Missing pagination on list endpoints
8
+ - Missing ownership checks (IDOR)
9
+ - Rate limiting missing on mutation endpoints
10
+ - Unvalidated business flow state transitions
11
+
12
+ Structural detections (mass assignment, pagination, state transitions) use
13
+ tree-sitter AST (find_calls / find_assignments). Route markers, ownership
14
+ checks, and serialization calls use high-quality literal-pattern regex.
15
+ Does NOT call AI. Does NOT write to disk. Does NOT touch the queue.
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import re
21
+
22
+ from ..agents.base import (
23
+ AgentDomain,
24
+ AgentGroup,
25
+ AgentInput,
26
+ AgentResult,
27
+ BaseAgent,
28
+ Finding,
29
+ Severity,
30
+ register,
31
+ safe_rglob,
32
+ )
33
+ from ..brain.ast_utils import find_assignments, find_calls
34
+ from ..brain.languages import EXTENSION_MAP, Lang
35
+
36
+ # Source extensions to scan (language parity).
37
+ _SOURCE_EXTENSIONS = [
38
+ "*.py",
39
+ "*.js",
40
+ "*.jsx",
41
+ "*.ts",
42
+ "*.tsx",
43
+ "*.java",
44
+ "*.rb",
45
+ "*.php",
46
+ "*.go",
47
+ "*.rs",
48
+ "*.cs",
49
+ "*.kt",
50
+ "*.kts",
51
+ ]
52
+
53
+ # Path keywords that indicate an API/route file (early-exit gate).
54
+ _API_KEYWORDS = [
55
+ "route",
56
+ "view",
57
+ "api",
58
+ "controller",
59
+ "serializer",
60
+ "resource",
61
+ "handler",
62
+ "endpoint",
63
+ "action",
64
+ ]
65
+
66
+ # Request-body sources passed directly to model updates (mass assignment).
67
+ _REQUEST_BODY_RE = re.compile(r"(?:request\.(?:json|data|form|args)|body)", re.I)
68
+ # Model mutation methods that, applied to a request body, are mass assignment.
69
+ _MUTATION_CALLS = {"update", "put", "patch", "save"}
70
+ # Pagination call names.
71
+ _PAGINATION_CALLS = {"limit", "paginate", "offset", "page"}
72
+ # Objects whose status/state changes are business-critical.
73
+ _STATE_BASES = {"order", "payment", "subscription", "ticket", "workflow"}
74
+ _STATE_VALIDATION_RE = re.compile(
75
+ r"(?i)(?:\ballowed\b|\bvalid\b|\btransition\b|state_machine|STATUS_FLOW|\benum\b)"
76
+ )
77
+ # Ownership-identifying symbols for IDOR checks, including Patchi's own
78
+ # tenant-isolation idiom (with tenant_context(...) + per-project ownership
79
+ # comparison) so fixed endpoints stop flagging.
80
+ _OWNERSHIP_RE = re.compile(
81
+ r"(?i)(?:current_user|user\.id|\bowner\b|request\.user|\.user_id\s*=|user_id\s*=="
82
+ r"|tenant_context|patch_project|\btenant\b)"
83
+ )
84
+ _RATE_LIMIT_RE = re.compile(r"(?i)@(?:ratelimit|throttle|limit)")
85
+
86
+
87
+ @register
88
+ class BusinessLogicAgent(BaseAgent):
89
+ """Detects business logic abuse, mass assignment, IDOR, excessive data exposure."""
90
+
91
+ group = AgentGroup.SECURITY
92
+ domain = AgentDomain.SECURITY
93
+ name = "BusinessLogicAgent"
94
+ description = (
95
+ "Business logic abuse: mass assignment, IDOR, excessive data exposure, missing pagination"
96
+ )
97
+
98
+ def _run(self, inp: AgentInput, result: AgentResult) -> None:
99
+ route_guard = re.compile(
100
+ r"(?i)(?:@(?:app\.)?route|def\s+\w+|class\s+\w+[^(]*View|/api/)", re.I
101
+ )
102
+
103
+ for pattern in _SOURCE_EXTENSIONS:
104
+ for fpath in safe_rglob(inp.root, pattern):
105
+ if not fpath.is_file():
106
+ continue
107
+ rel = fpath.relative_to(inp.root).as_posix()
108
+ ext = fpath.suffix.lower()
109
+ lang = EXTENSION_MAP.get(ext)
110
+
111
+ # Skip non-route files early.
112
+ if not any(kw in rel.lower() for kw in _API_KEYWORDS):
113
+ try:
114
+ first_kb = fpath.read_text(encoding="utf-8", errors="ignore")[:1024]
115
+ except OSError:
116
+ continue
117
+ if not route_guard.search(first_kb):
118
+ continue
119
+ content = first_kb
120
+ else:
121
+ try:
122
+ content = fpath.read_text(encoding="utf-8", errors="ignore")
123
+ except OSError:
124
+ continue
125
+
126
+ result.files_scanned += 1
127
+ self._scan_business_logic(content, rel, result, lang)
128
+
129
+ def _scan_business_logic(
130
+ self, content: str, rel: str, result: AgentResult, lang: Lang | None
131
+ ) -> None:
132
+ # ── Mass assignment detection (AST) ───────────────────────────────────
133
+ if lang is not None:
134
+ for call in find_calls(content, lang, _MUTATION_CALLS):
135
+ if _REQUEST_BODY_RE.search(call.get("full_text", "")):
136
+ result.add_finding(
137
+ Finding(
138
+ agent=self.name,
139
+ type="mass_assignment",
140
+ severity=Severity.HIGH,
141
+ file=rel,
142
+ line=call.get("line", 0),
143
+ message="Mass assignment — request body applied directly to model without field whitelist",
144
+ suggestion="Define an explicit field allowlist and only update whitelisted fields",
145
+ cwe="CWE-915",
146
+ extra={"skill": "api-security.skill"},
147
+ )
148
+ )
149
+
150
+ # ── Excessive data exposure (__dict__, vars(), .to_dict()) ──────────
151
+ if any(kw in rel.lower() for kw in _API_KEYWORDS):
152
+ for m in re.finditer(
153
+ r"(?i)(?:__dict__|vars\s*\(|\.to_dict\s*\(|\.serialize\s*\()", content
154
+ ):
155
+ result.add_finding(
156
+ Finding(
157
+ agent=self.name,
158
+ type="excessive_data_exposure",
159
+ severity=Severity.MEDIUM,
160
+ file=rel,
161
+ line=content[: m.start()].count("\n") + 1,
162
+ message="Returning full model serialization — may leak sensitive fields",
163
+ suggestion="Define explicit response fields; never return __dict__ or vars()",
164
+ cwe="CWE-200",
165
+ extra={"skill": "api-security.skill"},
166
+ )
167
+ )
168
+
169
+ # ── Missing pagination on collection endpoints (AST) ──────────────────
170
+ if lang is not None:
171
+ all_calls = find_calls(content, lang, {"all"})
172
+ paginate_calls = find_calls(content, lang, _PAGINATION_CALLS)
173
+ if all_calls and not paginate_calls:
174
+ result.add_finding(
175
+ Finding(
176
+ agent=self.name,
177
+ type="missing_pagination",
178
+ severity=Severity.LOW,
179
+ file=rel,
180
+ line=0,
181
+ message="Collection endpoint uses .all() without pagination — unbounded resource consumption",
182
+ suggestion="Add pagination with .paginate() or .limit()/.offset()",
183
+ cwe="CWE-770",
184
+ extra={"skill": "api-security.skill"},
185
+ )
186
+ )
187
+
188
+ # ── IDOR — missing ownership check (route marker regex) ───────────────
189
+ for m in re.finditer(
190
+ r"(?i)(?:@(?:app\.)?route|(?:app|router)\.(?:get|post|put|delete|patch))\s*\([^)]*\{(\w+_)?id\}",
191
+ content,
192
+ ):
193
+ line_start = content[: m.start()].count("\n") + 1
194
+ lines = content.splitlines()
195
+ surrounding = "\n".join(lines[line_start : min(line_start + 15, len(lines))])
196
+ if not _OWNERSHIP_RE.search(surrounding):
197
+ result.add_finding(
198
+ Finding(
199
+ agent=self.name,
200
+ type="idor_missing_ownership_check",
201
+ severity=Severity.HIGH,
202
+ file=rel,
203
+ line=line_start,
204
+ message="Endpoint with ID parameter lacks ownership verification — potential IDOR",
205
+ suggestion="Add ownership check: filter query by current_user.id or owner_id",
206
+ cwe="CWE-862",
207
+ extra={"skill": "api-security.skill"},
208
+ )
209
+ )
210
+
211
+ # ── Missing rate limiting on mutation endpoints (route marker regex) ──
212
+ for m in re.finditer(
213
+ r"(?i)(?:@(?:app\.)?route|(?:app|router)\.(?:get|post|put|delete|patch))\s*\(", content
214
+ ):
215
+ line_start = content[: m.start()].count("\n") + 1
216
+ lines = content.splitlines()
217
+ surrounding = "\n".join(
218
+ lines[max(0, line_start - 3) : min(line_start + 10, len(lines))]
219
+ )
220
+ decorator_line = lines[line_start - 1] if line_start <= len(lines) else ""
221
+ method_line = lines[line_start] if line_start < len(lines) else ""
222
+ if re.search(r"(?i)(?:post|put|delete|patch)", decorator_line[:80]) or re.search(
223
+ r"(?i)(?:post|put|delete|patch)", method_line[:80]
224
+ ):
225
+ if not _RATE_LIMIT_RE.search(surrounding[:200]):
226
+ result.add_finding(
227
+ Finding(
228
+ agent=self.name,
229
+ type="missing_rate_limit",
230
+ severity=Severity.LOW,
231
+ file=rel,
232
+ line=line_start,
233
+ message="Mutation endpoint missing rate limiting — potential for abuse",
234
+ suggestion="Add @ratelimit decorator with appropriate limits",
235
+ cwe="CWE-799",
236
+ extra={"skill": "api-security.skill"},
237
+ )
238
+ )
239
+
240
+ # ── Unvalidated state transitions (AST assignments) ───────────────────
241
+ if lang is not None:
242
+ for asn in find_assignments(content, lang):
243
+ target = asn.get("target", "")
244
+ if "." not in target:
245
+ continue
246
+ base, _, leaf = target.rpartition(".")
247
+ if leaf in ("status", "state") and base.lower() in _STATE_BASES:
248
+ line_start = asn.get("line", 0)
249
+ ctx = content[max(0, line_start - 15) : line_start + 5]
250
+ if not _STATE_VALIDATION_RE.search(ctx):
251
+ result.add_finding(
252
+ Finding(
253
+ agent=self.name,
254
+ type="unvalidated_state_transition",
255
+ severity=Severity.MEDIUM,
256
+ file=rel,
257
+ line=line_start,
258
+ message="Status/state change without transition validation — business logic bypass",
259
+ suggestion="Validate state transitions against allowed flow matrix",
260
+ cwe="CWE-284",
261
+ extra={"skill": "api-security.skill"},
262
+ )
263
+ )