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,602 @@
1
+ """
2
+ AuthZAgent — authorization bypass, privilege escalation.
3
+
4
+ Detects authorization issues:
5
+ - Missing authentication checks
6
+ - Broken access control
7
+ - Privilege escalation vulnerabilities
8
+ - Horizontal/vertical privilege escalation
9
+ - Missing authorization middleware
10
+ - Overly permissive access controls
11
+ - Insecure direct object references (IDOR)
12
+
13
+ Uses pattern matching and AST analysis.
14
+ Does NOT call AI.
15
+ Does NOT write to disk.
16
+ Does NOT touch the queue.
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ import ast as py_ast
22
+ import logging
23
+ import re
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.ast_utils import node_text, parse_source
40
+ from ..brain.languages import TREE_SITTER_LANGS, Lang, detect_language, get_parser
41
+
42
+ _log = logging.getLogger("patchi.security.authz_agent")
43
+
44
+
45
+ @register
46
+ class AuthZAgent(BaseAgent):
47
+ """Agent for detecting authorization vulnerabilities."""
48
+
49
+ group = AgentGroup.SECURITY
50
+ domain = AgentDomain.SECURITY
51
+ name = "AuthZAgent"
52
+ description = "Authorization bypass, privilege escalation, IDOR, missing auth checks"
53
+
54
+ def _run(self, inp: AgentInput, result: AgentResult) -> None:
55
+ from patchi.core.brain.project_context import agent_is_relevant
56
+
57
+ relevant, reason = agent_is_relevant(inp.domain, inp.purpose, self.name, inp.active_domains)
58
+ if not relevant:
59
+ result.status = AgentStatus.SKIPPED
60
+ result.data.update({"skip_reason": reason, "needs_ai": False})
61
+ return
62
+
63
+ findings = []
64
+
65
+ # Define source file patterns to scan
66
+ source_patterns = [
67
+ "*.py",
68
+ "*.js",
69
+ "*.jsx",
70
+ "*.ts",
71
+ "*.tsx",
72
+ "*.java",
73
+ "*.php",
74
+ "*.rb",
75
+ "*.go",
76
+ "*.rs",
77
+ "*.cpp",
78
+ "*.cxx",
79
+ "*.cc",
80
+ "*.c",
81
+ "*.h",
82
+ "*.hpp",
83
+ "*.cs",
84
+ ]
85
+
86
+ # Search for source files
87
+ for pattern in source_patterns:
88
+ for file_path in safe_rglob(inp.root, pattern):
89
+ if file_path.is_file():
90
+ rel_path = file_path.relative_to(inp.root).as_posix()
91
+ if not self._should_skip_file(rel_path, inp):
92
+ findings.extend(self._scan_file_authz(file_path, rel_path))
93
+
94
+ result.status = AgentStatus.SUCCEEDED
95
+ result.findings = findings
96
+ result.data.update(
97
+ {
98
+ "authz_findings": len(
99
+ [
100
+ f
101
+ for f in findings
102
+ if any(
103
+ word in f.title.lower()
104
+ for word in ["authorization", "auth", "privilege", "idor"]
105
+ )
106
+ ]
107
+ ),
108
+ "needs_ai": False,
109
+ }
110
+ )
111
+ return
112
+
113
+ def _should_skip_file(self, file_path: str, inp: AgentInput) -> bool:
114
+ """Check if file should be skipped based on restrictions."""
115
+ from pathlib import PurePosixPath
116
+
117
+ # Check restrictions
118
+ restrictions = inp.config.get("restrictions", [])
119
+ for r in restrictions:
120
+ if r.get("enabled", True):
121
+ path = r["path"]
122
+ if file_path.startswith(path) or PurePosixPath(file_path).match(path):
123
+ if r["type"] == "NO_TOUCH":
124
+ return True
125
+ elif r["type"] == "SCAN_ONLY" and self.__class__.__name__ == "FixAgent":
126
+ return True
127
+ return False
128
+
129
+ def _scan_file_authz(self, file_path: Path, rel_path: str) -> list[Finding]:
130
+ """Scan a file for authorization vulnerabilities."""
131
+ findings = []
132
+
133
+ try:
134
+ content = file_path.read_text(encoding="utf-8")
135
+ lang = detect_language(file_path)
136
+
137
+ # Apply language-specific authorization checks
138
+ if lang == Lang.PYTHON:
139
+ findings.extend(self._scan_python_authz(content, rel_path))
140
+ elif lang in [Lang.JAVASCRIPT, Lang.TYPESCRIPT]:
141
+ findings.extend(self._scan_javascript_authz(content, rel_path, lang))
142
+ elif lang == Lang.JAVA:
143
+ findings.extend(self._scan_java_authz(content, rel_path))
144
+ elif lang == Lang.PHP:
145
+ findings.extend(self._scan_php_authz(content, rel_path))
146
+ elif lang == Lang.RUBY:
147
+ findings.extend(self._scan_ruby_authz(content, rel_path))
148
+ elif lang in TREE_SITTER_LANGS:
149
+ findings.extend(self._scan_treesitter_authz(content, rel_path, lang))
150
+ else:
151
+ # Apply general authorization checks
152
+ findings.extend(self._scan_general_authz(content, rel_path))
153
+
154
+ except Exception as e:
155
+ findings.append(
156
+ make_finding(
157
+ severity=Severity.LOW,
158
+ file=rel_path,
159
+ line_start=0,
160
+ title="AuthZ scanner file read error",
161
+ description=f"Could not analyze {file_path.name} for authorization issues: {str(e)}",
162
+ evidence=str(e),
163
+ )
164
+ )
165
+
166
+ return findings
167
+
168
+ def _scan_python_authz(self, content: str, rel_path: str) -> list[Finding]:
169
+ """Scan Python code for authorization vulnerabilities."""
170
+ findings = []
171
+
172
+ try:
173
+ tree = py_ast.parse(content)
174
+ except SyntaxError:
175
+ # If we can't parse the AST, fall back to pattern matching
176
+ return self._scan_general_authz(content, rel_path)
177
+
178
+ # Walk the AST to find authorization issues
179
+ routes_with_auth = set()
180
+ routes_without_auth = set()
181
+
182
+ for node in py_ast.walk(tree):
183
+ # Look for route definitions in common frameworks
184
+ if isinstance(node, py_ast.FunctionDef):
185
+ func_name = node.name
186
+ has_auth_decorator = False
187
+
188
+ # Check for authentication/authorization decorators
189
+ for decorator in node.decorator_list:
190
+ if isinstance(decorator, py_ast.Name):
191
+ if decorator.id.lower() in [
192
+ "login_required",
193
+ "auth_required",
194
+ "admin_required",
195
+ "permission_required",
196
+ ]:
197
+ has_auth_decorator = True
198
+ break
199
+ elif isinstance(decorator, py_ast.Attribute):
200
+ if decorator.attr.lower() in [
201
+ "login_required",
202
+ "auth_required",
203
+ "admin_required",
204
+ "permission_required",
205
+ ]:
206
+ has_auth_decorator = True
207
+ break
208
+
209
+ # Check if this looks like a route handler
210
+ has_route_decorator = any(
211
+ isinstance(d, py_ast.Attribute)
212
+ and d.attr
213
+ in (
214
+ "get",
215
+ "post",
216
+ "put",
217
+ "delete",
218
+ "patch",
219
+ "route",
220
+ "add_url_rule",
221
+ "before_request",
222
+ )
223
+ or (
224
+ isinstance(d, py_ast.Call)
225
+ and isinstance(d.func, py_ast.Attribute)
226
+ and d.func.attr
227
+ in (
228
+ "get",
229
+ "post",
230
+ "put",
231
+ "delete",
232
+ "patch",
233
+ )
234
+ )
235
+ for d in node.decorator_list
236
+ )
237
+ # Also flag functions named like route handlers (get_X, post_X, delete_X)
238
+ is_route_name = any(
239
+ func_name.lower().startswith(v)
240
+ for v in ("get_", "post_", "put_", "delete_", "patch_", "handle_")
241
+ )
242
+ if has_route_decorator or is_route_name:
243
+ if has_auth_decorator:
244
+ routes_with_auth.add(func_name)
245
+ else:
246
+ routes_without_auth.add(func_name)
247
+
248
+ # Report routes without authentication
249
+ for node in py_ast.walk(tree):
250
+ if isinstance(node, py_ast.FunctionDef) and node.name in routes_without_auth:
251
+ if node.name not in routes_with_auth:
252
+ findings.append(
253
+ make_finding(
254
+ severity=Severity.HIGH,
255
+ file=rel_path,
256
+ line_start=getattr(node, "lineno", 0),
257
+ title="Missing Authentication Check",
258
+ description=f"Route '{node.name}' may lack proper authentication/authorization",
259
+ evidence=f"Function {node.name} does not have authentication decorator",
260
+ )
261
+ )
262
+
263
+ # General pattern matching for Python
264
+ findings.extend(self._scan_general_authz(content, rel_path))
265
+
266
+ return findings
267
+
268
+ def _scan_javascript_authz(
269
+ self, content: str, rel_path: str, lang: Lang = Lang.JAVASCRIPT
270
+ ) -> list[Finding]:
271
+ """Scan JavaScript/TypeScript code for authorization vulnerabilities.
272
+
273
+ Uses tree-sitter to locate route-definition call expressions (e.g.
274
+ ``app.get(...)`` / ``router.post(...)``) and inspects the full call
275
+ text (including any multi-line handler chain) for an authentication /
276
+ authorization guard. Falls back to the line-regex scanner if the file
277
+ cannot be parsed.
278
+ """
279
+ tree = parse_source(content, lang)
280
+ if tree is None:
281
+ return self._scan_javascript_authz_regex(content, rel_path)
282
+
283
+ findings: list[Finding] = []
284
+ lines = content.splitlines()
285
+ route_verbs = {"get", "post", "put", "delete", "patch"}
286
+ auth_words = {
287
+ "authenticate",
288
+ "authorize",
289
+ "jwt",
290
+ "session",
291
+ "login",
292
+ "requireauth",
293
+ "isloggedin",
294
+ "permission",
295
+ "hasrole",
296
+ "hasauthority",
297
+ "secured",
298
+ "preauthorize",
299
+ }
300
+ sensitive_re = re.compile(
301
+ r"/api/.*(?:/users?|/admin|/settings|/profile|/account)", re.IGNORECASE
302
+ )
303
+
304
+ for node in self._iter_nodes(tree.root_node):
305
+ if node.type != "call_expression":
306
+ continue
307
+ fn = node.child_by_field_name("function")
308
+ if fn is None or fn.type != "member_expression":
309
+ continue
310
+ name = self._node_name(fn)
311
+ leaf = name.split(".")[-1].lower()
312
+ if leaf not in route_verbs:
313
+ continue
314
+
315
+ call_text = node_text(node)
316
+ line = node.start_point[0] + 1
317
+ lowered = call_text.lower()
318
+ has_auth = any(w in lowered for w in auth_words)
319
+ evidence_line = lines[line - 1].strip() if 0 < line <= len(lines) else call_text.strip()
320
+ if not has_auth:
321
+ sensitive = bool(sensitive_re.search(call_text))
322
+ findings.append(
323
+ make_finding(
324
+ severity=Severity.HIGH if sensitive else Severity.MEDIUM,
325
+ file=rel_path,
326
+ line_start=line,
327
+ title=(
328
+ "Sensitive Endpoint Without Authorization"
329
+ if sensitive
330
+ else "Potential Missing Authorization Check"
331
+ ),
332
+ description="Route definition may lack authentication/authorization middleware",
333
+ evidence=evidence_line,
334
+ )
335
+ )
336
+ return findings
337
+
338
+ def _scan_javascript_authz_regex(self, content: str, rel_path: str) -> list[Finding]:
339
+ """Line-based fallback for JS/TS when tree-sitter is unavailable."""
340
+ findings: list[Finding] = []
341
+ express_route_patterns = [
342
+ r'app\.(get|post|put|delete|patch)\s*\(\s*["\'][^"\']+["\']\s*,\s*(?!.*(?:authenticate|authorize|\bjwt\b|\bsession\b|\blogin\b|requireAuth|isLoggedIn)).*?=>',
343
+ r'router\.(get|post|put|delete|patch)\s*\(\s*["\'][^"\']+["\']\s*,\s*(?!.*(?:authenticate|authorize|\bjwt\b|\bsession\b|\blogin\b|requireAuth|isLoggedIn)).*?=>',
344
+ ]
345
+ lines = content.splitlines()
346
+ for i, line in enumerate(lines, 1):
347
+ for pattern in express_route_patterns:
348
+ if re.search(pattern, line, re.IGNORECASE):
349
+ findings.append(
350
+ make_finding(
351
+ severity=Severity.MEDIUM,
352
+ file=rel_path,
353
+ line_start=i,
354
+ title="Potential Missing Authorization Check",
355
+ description="Route definition may lack authentication/authorization middleware",
356
+ evidence=line.strip(),
357
+ )
358
+ )
359
+ sensitive_re = re.compile(
360
+ r"/api/.*(?:/users?|/admin|/settings|/profile|/account)", re.IGNORECASE
361
+ )
362
+ for i, line in enumerate(lines, 1):
363
+ if sensitive_re.search(line) and not any(
364
+ w in line.lower() for w in ["auth", "login", "jwt", "session", "verify"]
365
+ ):
366
+ findings.append(
367
+ make_finding(
368
+ severity=Severity.HIGH,
369
+ file=rel_path,
370
+ line_start=i,
371
+ title="Sensitive Endpoint Without Authorization",
372
+ description="API endpoint for sensitive data may lack authorization",
373
+ evidence=line.strip(),
374
+ )
375
+ )
376
+ return findings
377
+
378
+ # ── Shared tree-sitter helpers ─────────────────────────────────────────
379
+
380
+ def _iter_nodes(self, node):
381
+ yield node
382
+ for child in node.children:
383
+ yield from self._iter_nodes(child)
384
+
385
+ def _node_name(self, node: object) -> str:
386
+ """Recursively reconstruct a dotted/scoped call name from a node."""
387
+ if node is None:
388
+ return ""
389
+ ntype = getattr(node, "type", "")
390
+ if ntype == "identifier":
391
+ return node_text(node)
392
+ if ntype == "member_expression":
393
+ obj = node.child_by_field_name("object")
394
+ prop = node.child_by_field_name("property")
395
+ if obj is not None and prop is not None:
396
+ return f"{self._node_name(obj)}.{node_text(prop)}"
397
+ return node_text(node)
398
+ if ntype in ("scoped_identifier", "qualified_name", "scoped_type_identifier"):
399
+ return node_text(node)
400
+ return node_text(node)
401
+
402
+ def _scan_java_authz(self, content: str, rel_path: str) -> list[Finding]:
403
+ """Scan Java code for authorization vulnerabilities."""
404
+ findings = []
405
+
406
+ # Check if security annotations are present
407
+ has_security_annotation = any(
408
+ sec_word in content.lower()
409
+ for sec_word in [
410
+ "@secured",
411
+ "@preauthorize",
412
+ "@postauthorize",
413
+ "hasrole",
414
+ "hasauthority",
415
+ ]
416
+ )
417
+
418
+ if not has_security_annotation:
419
+ lines = content.splitlines()
420
+ for i, line in enumerate(lines, 1):
421
+ # Look for controller or mapping annotations
422
+ if any(
423
+ annotation in line
424
+ for annotation in [
425
+ "@Controller",
426
+ "@RestController",
427
+ "@GetMapping",
428
+ "@PostMapping",
429
+ "@PutMapping",
430
+ "@DeleteMapping",
431
+ "@RequestMapping",
432
+ ]
433
+ ):
434
+ findings.append(
435
+ make_finding(
436
+ severity=Severity.MEDIUM,
437
+ file=rel_path,
438
+ line_start=i,
439
+ title="Potential Missing Authorization Check",
440
+ description="Controller or endpoint may lack authorization annotations",
441
+ evidence=line.strip(),
442
+ )
443
+ )
444
+
445
+ return findings
446
+
447
+ def _scan_php_authz(self, content: str, rel_path: str) -> list[Finding]:
448
+ """Scan PHP code for authorization vulnerabilities."""
449
+ findings = []
450
+
451
+ # Look for PHP route definitions without auth checks
452
+ php_auth_patterns = [
453
+ r'Route::(get|post|put|delete|patch)\s*\(\s*["\'][^"\']+["\']\s*,\s*function\s*\(',
454
+ r"function\s+\w+Action\s*\(\s*\)",
455
+ ]
456
+
457
+ lines = content.splitlines()
458
+ for i, line in enumerate(lines, 1):
459
+ for pattern in php_auth_patterns:
460
+ matches = re.finditer(pattern, line, re.IGNORECASE)
461
+ for _match in matches:
462
+ # Check if this line has authentication
463
+ if not any(
464
+ auth_word in content[max(0, i - 10) : i + 10]
465
+ for auth_word in [
466
+ "auth",
467
+ "Auth",
468
+ "login",
469
+ "Login",
470
+ "middleware",
471
+ "Middleware",
472
+ ]
473
+ ):
474
+ findings.append(
475
+ make_finding(
476
+ severity=Severity.MEDIUM,
477
+ file=rel_path,
478
+ line_start=i,
479
+ title="Potential Missing Authorization Check",
480
+ description="Route or controller method may lack authentication/authorization",
481
+ evidence=line.strip(),
482
+ )
483
+ )
484
+
485
+ return findings
486
+
487
+ def _scan_ruby_authz(self, content: str, rel_path: str) -> list[Finding]:
488
+ """Scan Ruby code for authorization vulnerabilities."""
489
+ findings = []
490
+
491
+ # Look for Rails controller actions without authentication
492
+ ruby_auth_patterns = [
493
+ r"\bdef\s+(show|edit|update|destroy)\b",
494
+ ]
495
+
496
+ # Check if authentication methods are called
497
+ has_auth_check = any(
498
+ auth_method in content
499
+ for auth_method in [
500
+ "before_action",
501
+ "before_filter",
502
+ "authenticate_",
503
+ "logged_in",
504
+ "authorized",
505
+ ]
506
+ )
507
+
508
+ if not has_auth_check:
509
+ lines = content.splitlines()
510
+ for i, line in enumerate(lines, 1):
511
+ for pattern in ruby_auth_patterns:
512
+ matches = re.finditer(pattern, line)
513
+ for _match in matches:
514
+ findings.append(
515
+ make_finding(
516
+ severity=Severity.MEDIUM,
517
+ file=rel_path,
518
+ line_start=i,
519
+ title="Potential Missing Authorization Check",
520
+ description="Controller action may lack authentication/authorization",
521
+ evidence=line.strip(),
522
+ )
523
+ )
524
+
525
+ return findings
526
+
527
+ def _scan_treesitter_authz(self, content: str, rel_path: str, lang: Lang) -> list[Finding]:
528
+ """Scan using tree-sitter for supported languages."""
529
+ findings = []
530
+
531
+ try:
532
+ parser, lang_obj = get_parser(lang)
533
+ parser.parse(bytes(content, "utf8"))
534
+
535
+ # For now, fall back to general pattern matching
536
+ findings.extend(self._scan_general_authz(content, rel_path))
537
+ except Exception as e:
538
+ # If tree-sitter fails, use general patterns
539
+ _log.warning("AuthZAgent._scan_treesitter_authz failed: %s", e)
540
+ findings.extend(self._scan_general_authz(content, rel_path))
541
+
542
+ return findings
543
+
544
+ def _scan_general_authz(self, content: str, rel_path: str) -> list[Finding]:
545
+ """Apply general authorization pattern matching."""
546
+ findings = []
547
+
548
+ # General authorization patterns
549
+ authz_patterns = [
550
+ (
551
+ r"\b(?:delete|remove|update|modify)\b.*\buser\b",
552
+ "Potential Missing Authorization",
553
+ Severity.HIGH,
554
+ ),
555
+ (
556
+ r"\b(?:change|set|update)\b.*\bpassword\b",
557
+ "Potential Missing Authorization",
558
+ Severity.HIGH,
559
+ ),
560
+ (
561
+ r"\b(?:admin|administrator|superuser)\b",
562
+ "Admin Functionality",
563
+ Severity.MEDIUM,
564
+ ),
565
+ (
566
+ r"\b(?:get|fetch|retrieve)\b.*\b(?:config|setting)\b",
567
+ "Configuration Access",
568
+ Severity.MEDIUM,
569
+ ),
570
+ ]
571
+
572
+ lines = content.splitlines()
573
+ for i, line in enumerate(lines, 1):
574
+ for pattern, description, severity in authz_patterns:
575
+ matches = re.finditer(pattern, line, re.IGNORECASE)
576
+ for _match in matches:
577
+ # Only flag if there's no obvious auth check in the vicinity
578
+ context = " ".join(lines[max(0, i - 3) : min(len(lines), i + 3)])
579
+ if not any(
580
+ auth_word in context.lower()
581
+ for auth_word in [
582
+ "auth",
583
+ "login",
584
+ "verify",
585
+ "validate",
586
+ "permission",
587
+ "authorize",
588
+ "allow",
589
+ ]
590
+ ):
591
+ findings.append(
592
+ make_finding(
593
+ severity=severity,
594
+ file=rel_path,
595
+ line_start=i,
596
+ title=description,
597
+ description=f"Found potential {description.lower()} without clear authorization check",
598
+ evidence=line.strip(),
599
+ )
600
+ )
601
+
602
+ return findings