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,442 @@
1
+ """
2
+ Reasoning Engine — queries the Layered Brain for structured impact analysis.
3
+
4
+ Answers questions like:
5
+ - "What changed?" → reads git diff, maps to layers, lists affected subsystems
6
+ - "What does auth do?" → reads layer summaries, shows public API + dependencies
7
+ - "What imports X?" → traces dependency graph through layers
8
+ - "What are the security hotspots?" → cross-references charter rules with layers
9
+
10
+ No AI tokens required — pure heuristic reasoning over the layered brain.
11
+ No regex — uses keyword matching for classification.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ from dataclasses import dataclass, field
17
+ from pathlib import Path
18
+ from typing import Any
19
+
20
+ from patchi.core.brain.layered_brain import (
21
+ Layer,
22
+ find_layers_for_file,
23
+ get_layer_context,
24
+ layers_from_dict,
25
+ )
26
+
27
+
28
+ @dataclass
29
+ class ReasoningResult:
30
+ """Structured answer from the reasoning engine."""
31
+
32
+ question: str
33
+ answer: str # human-readable summary
34
+ layers: list[str] = field(default_factory=list) # affected layer names
35
+ files: list[str] = field(default_factory=list) # affected files
36
+ details: dict[str, Any] = field(default_factory=dict) # structured data
37
+
38
+ def to_dict(self) -> dict:
39
+ return {
40
+ "question": self.question,
41
+ "answer": self.answer,
42
+ "layers": self.layers,
43
+ "files": self.files,
44
+ "details": self.details,
45
+ }
46
+
47
+
48
+ # ── Layered Brain loader ─────────────────────────────────────────────────────
49
+
50
+
51
+ def _load_layers(root: Path) -> dict[str, Layer]:
52
+ """Load cached layers from .patchi/memory/layers.json."""
53
+ path = root / ".patchi" / "memory" / "layers.json"
54
+ if not path.exists():
55
+ return {}
56
+ try:
57
+ data = {}
58
+ with open(path, encoding="utf-8") as f:
59
+ import json
60
+ data = json.load(f)
61
+ return layers_from_dict(data)
62
+ except Exception:
63
+ return {}
64
+
65
+
66
+ # ── Question classifiers (keyword-based, no regex) ───────────────────────────
67
+
68
+
69
+ def classify_question(question: str) -> str:
70
+ """Classify a question into a category using keyword matching."""
71
+ q = question.lower().strip()
72
+
73
+ # What changed?
74
+ change_words = ["changed", "new", "modified", "different", "diff"]
75
+ if any(w in q for w in change_words):
76
+ return "what_changed"
77
+
78
+ # What does X do?
79
+ does_words = ["does", "is", "are"]
80
+ do_words = ["do", "about"]
81
+ if any(w in q for w in does_words) and any(w in q for w in do_words):
82
+ return "what_does"
83
+
84
+ # What imports/uses/depends on X?
85
+ import_words = ["import", "use", "depend"]
86
+ if any(w in q for w in import_words):
87
+ return "what_imports"
88
+
89
+ # Security hotspots
90
+ security_words = ["security", "hotspot", "vuln", "vulnerability", "risk", "dangerous"]
91
+ if any(w in q for w in security_words):
92
+ return "hotspots"
93
+
94
+ # Routes/endpoints
95
+ route_words = ["route", "endpoint", "api", "handler"]
96
+ if any(w in q for w in route_words):
97
+ return "routes"
98
+
99
+ # Layers/modules
100
+ layer_words = ["layer", "subsystem", "module", "component"]
101
+ if any(w in q for w in layer_words):
102
+ return "layers"
103
+
104
+ # Dependencies
105
+ dep_words = ["depend", "import", "use"]
106
+ if any(w in q for w in dep_words):
107
+ return "imports"
108
+
109
+ return "general"
110
+
111
+
112
+ # ── Answer generators ─────────────────────────────────────────────────────────
113
+
114
+
115
+ def _answer_what_changed(root: Path, layers: dict[str, Layer]) -> ReasoningResult:
116
+ """Answer 'what changed?' by checking git diff and mapping to layers."""
117
+ import subprocess
118
+
119
+ try:
120
+ result = subprocess.run(
121
+ ["git", "diff", "--name-only", "HEAD~1"],
122
+ capture_output=True,
123
+ text=True,
124
+ cwd=str(root),
125
+ timeout=10,
126
+ )
127
+ changed_files = [f.strip() for f in result.stdout.strip().split("\n") if f.strip()]
128
+ except Exception:
129
+ changed_files = []
130
+
131
+ if not changed_files:
132
+ return ReasoningResult(
133
+ question="what changed",
134
+ answer="No recent changes detected (or not a git repo).",
135
+ layers=[],
136
+ files=[],
137
+ )
138
+
139
+ # Map files to layers
140
+ affected_layers: set[str] = set()
141
+ affected_files: list[str] = []
142
+ layer_details: dict[str, list[str]] = {}
143
+
144
+ for fp in changed_files:
145
+ layer_names = find_layers_for_file(layers, fp)
146
+ for ln in layer_names:
147
+ affected_layers.add(ln)
148
+ layer_details.setdefault(ln, []).append(fp)
149
+ affected_files.append(fp)
150
+
151
+ # Build answer
152
+ parts = [f"Changed {len(changed_files)} file(s) across {len(affected_layers)} layer(s):"]
153
+ for ln in sorted(affected_layers):
154
+ layer = layers.get(ln)
155
+ summary = layer.summary if layer else "unknown"
156
+ files_in = layer_details.get(ln, [])
157
+ parts.append(f" {ln}: {summary}")
158
+ for f in files_in[:5]:
159
+ parts.append(f" - {f}")
160
+ if len(files_in) > 5:
161
+ parts.append(f" ... and {len(files_in) - 5} more")
162
+
163
+ # Check for boundary violations
164
+ if affected_layers:
165
+ parts.append("")
166
+ parts.append("Dependency analysis:")
167
+ for ln in sorted(affected_layers):
168
+ layer = layers.get(ln)
169
+ if layer and layer.dependents:
170
+ cross = [d for d in layer.dependents if d not in affected_layers]
171
+ if cross:
172
+ parts.append(f" {ln} is imported by: {', '.join(cross[:5])}")
173
+
174
+ return ReasoningResult(
175
+ question="what changed",
176
+ answer="\n".join(parts),
177
+ layers=sorted(affected_layers),
178
+ files=affected_files,
179
+ details={"layer_files": layer_details},
180
+ )
181
+
182
+
183
+ def _answer_what_does(root: Path, layers: dict[str, Layer], module_name: str) -> ReasoningResult:
184
+ """Answer 'what does X do?' by reading layer summaries."""
185
+ # Try exact match first
186
+ layer = layers.get(module_name)
187
+
188
+ # Try partial match
189
+ if not layer:
190
+ for name, lay in layers.items():
191
+ if module_name.lower() in name.lower():
192
+ layer = lay
193
+ module_name = name
194
+ break
195
+
196
+ if not layer:
197
+ return ReasoningResult(
198
+ question=f"what does {module_name} do",
199
+ answer=f"Layer '{module_name}' not found in the layered brain.",
200
+ layers=[],
201
+ )
202
+
203
+ parts = [f"=== {layer.name} (Level {layer.level}) ==="]
204
+ parts.append(f"Summary: {layer.summary}")
205
+ parts.append(f"Purpose: {layer.purpose}")
206
+ if layer.files:
207
+ parts.append(f"Files: {len(layer.files)}")
208
+ for f in layer.files[:10]:
209
+ parts.append(f" - {f}")
210
+ if layer.public_api:
211
+ parts.append(f"Public API: {len(layer.public_api)} symbols")
212
+ for api in layer.public_api[:15]:
213
+ parts.append(f" - {api}")
214
+ if layer.depends_on:
215
+ parts.append(f"Depends on: {', '.join(layer.depends_on[:10])}")
216
+ if layer.dependents:
217
+ parts.append(f"Used by: {', '.join(layer.dependents[:10])}")
218
+
219
+ # Get context from dependencies
220
+ context = get_layer_context(layers, module_name, max_depth=1)
221
+ if len(context) > 1:
222
+ parts.append(f"\nDirect dependencies ({len(context) - 1}):")
223
+ for name, summary in context.items():
224
+ if name != module_name:
225
+ parts.append(f" {name}: {summary[:100]}")
226
+
227
+ return ReasoningResult(
228
+ question=f"what does {module_name} do",
229
+ answer="\n".join(parts),
230
+ layers=[module_name] + layer.depends_on,
231
+ files=layer.files,
232
+ details={
233
+ "api": layer.public_api,
234
+ "depends": layer.depends_on,
235
+ "dependents": layer.dependents,
236
+ },
237
+ )
238
+
239
+
240
+ def _answer_what_imports(root: Path, layers: dict[str, Layer], module_name: str) -> ReasoningResult:
241
+ """Answer 'what imports X?' by tracing dependency graph."""
242
+ # Find all layers that depend on the target
243
+ importers: list[str] = []
244
+ for name, layer in layers.items():
245
+ if module_name in layer.depends_on:
246
+ importers.append(name)
247
+
248
+ if not importers:
249
+ return ReasoningResult(
250
+ question=f"what imports {module_name}",
251
+ answer=f"No layers import '{module_name}'.",
252
+ layers=[module_name] if module_name in layers else [],
253
+ )
254
+
255
+ parts = [f"Layers that import '{module_name}':"]
256
+ for imp in importers:
257
+ layer = layers.get(imp)
258
+ summary = layer.summary[:80] if layer else "unknown"
259
+ parts.append(f" {imp}: {summary}")
260
+
261
+ # Check for circular dependencies
262
+ if module_name in layers and module_name in layers[module_name].dependents:
263
+ parts.append(f"\nCircular dependency detected: {module_name} imports itself!")
264
+
265
+ return ReasoningResult(
266
+ question=f"what imports {module_name}",
267
+ answer="\n".join(parts),
268
+ layers=importers + [module_name],
269
+ details={"importers": importers},
270
+ )
271
+
272
+
273
+ def _answer_hotspots(root: Path, layers: dict[str, Layer]) -> ReasoningResult:
274
+ """Answer 'what are the security hotspots?' by analyzing layer properties."""
275
+ hotspots: list[dict] = []
276
+
277
+ for name, layer in layers.items():
278
+ if name.startswith("__"):
279
+ continue
280
+ risk_score = 0
281
+ reasons: list[str] = []
282
+
283
+ # High dependency count = more attack surface
284
+ if len(layer.dependents) > 5:
285
+ risk_score += 2
286
+ reasons.append(f"imported by {len(layer.dependents)} modules (high attack surface)")
287
+
288
+ # Auth/security layers are critical
289
+ security_keywords = ["auth", "security", "session", "crypto"]
290
+ if any(kw in name.lower() for kw in security_keywords):
291
+ risk_score += 3
292
+ reasons.append("security-critical layer")
293
+
294
+ # Large layers with many files are harder to audit
295
+ if len(layer.files) > 20:
296
+ risk_score += 1
297
+ reasons.append(f"{len(layer.files)} files (large layer)")
298
+
299
+ # API layers accept external input
300
+ api_keywords = ["api", "route", "endpoint", "handler", "view"]
301
+ if any(kw in name.lower() for kw in api_keywords):
302
+ risk_score += 2
303
+ reasons.append("accepts external input")
304
+
305
+ if risk_score > 0:
306
+ hotspots.append({
307
+ "name": name,
308
+ "risk_score": risk_score,
309
+ "reasons": reasons,
310
+ "summary": layer.summary[:100],
311
+ })
312
+
313
+ hotspots.sort(key=lambda h: -h["risk_score"])
314
+
315
+ parts = ["Security hotspots (sorted by risk):"]
316
+ for h in hotspots[:10]:
317
+ parts.append(f" [{h['risk_score']}] {h['name']}: {', '.join(h['reasons'])}")
318
+
319
+ return ReasoningResult(
320
+ question="security hotspots",
321
+ answer="\n".join(parts),
322
+ layers=[h["name"] for h in hotspots],
323
+ details={"hotspots": hotspots},
324
+ )
325
+
326
+
327
+ def _answer_layers(root: Path, layers: dict[str, Layer]) -> ReasoningResult:
328
+ """Answer 'what layers exist?' by listing all layers."""
329
+ parts = [f"Layered Brain contains {len(layers)} layers:"]
330
+ for name, layer in sorted(layers.items()):
331
+ if name.startswith("__"):
332
+ continue
333
+ parts.append(f" [{layer.level}] {name}: {layer.summary[:80]}")
334
+
335
+ return ReasoningResult(
336
+ question="layers",
337
+ answer="\n".join(parts),
338
+ layers=list(layers.keys()),
339
+ )
340
+
341
+
342
+ def _answer_general(root: Path, layers: dict[str, Layer], question: str) -> ReasoningResult:
343
+ """Generic answer — list all layers with summaries."""
344
+ project = layers.get("__project__")
345
+ parts = ["Project overview:"]
346
+ if project:
347
+ parts.append(f" {project.summary}")
348
+
349
+ parts.append(f"\nLayers ({len(layers)}):")
350
+ for name, layer in sorted(layers.items()):
351
+ if name.startswith("__"):
352
+ continue
353
+ parts.append(f" [{layer.level}] {name}: {layer.summary[:80]}")
354
+
355
+ return ReasoningResult(
356
+ question=question,
357
+ answer="\n".join(parts),
358
+ layers=list(layers.keys()),
359
+ )
360
+
361
+
362
+ # ── Main entry point ─────────────────────────────────────────────────────────
363
+
364
+
365
+ def answer_question(question: str, root: Path | None = None) -> ReasoningResult:
366
+ """Answer a question about the codebase using the layered brain."""
367
+ r = root or Path.cwd()
368
+ layers = _load_layers(r)
369
+
370
+ if not layers:
371
+ return ReasoningResult(
372
+ question=question,
373
+ answer="No layered brain found. Run 'p scan' first to build the layer cache.",
374
+ )
375
+
376
+ category = classify_question(question)
377
+
378
+ if category == "what_changed":
379
+ return _answer_what_changed(r, layers)
380
+
381
+ if category == "what_does":
382
+ # Extract module name from question
383
+ q = question.lower()
384
+ module_name = ""
385
+ # Try to find a quoted word
386
+ if "'" in question:
387
+ parts = question.split("'")
388
+ if len(parts) >= 2:
389
+ module_name = parts[1]
390
+ elif '"' in question:
391
+ parts = question.split('"')
392
+ if len(parts) >= 2:
393
+ module_name = parts[1]
394
+ else:
395
+ # Try to find word after "does" or "is"
396
+ words = q.split()
397
+ for i, word in enumerate(words):
398
+ if word in ("does", "is", "are") and i + 1 < len(words):
399
+ next_word = words[i + 1]
400
+ if next_word not in ("the", "a", "an"):
401
+ module_name = next_word
402
+ break
403
+ return _answer_what_does(r, layers, module_name)
404
+
405
+ if category in ("what_imports", "imports"):
406
+ # Extract module name from question
407
+ q = question.lower()
408
+ module_name = ""
409
+ words = q.split()
410
+ for i, word in enumerate(words):
411
+ if word in ("imports", "import", "uses", "use", "depends", "depend", "on") and i + 1 < len(words):
412
+ next_word = words[i + 1]
413
+ if next_word not in ("the", "a", "an"):
414
+ module_name = next_word
415
+ break
416
+ return _answer_what_imports(r, layers, module_name)
417
+
418
+ if category == "hotspots":
419
+ return _answer_hotspots(r, layers)
420
+
421
+ if category == "routes":
422
+ # List API layers
423
+ api_layers = {
424
+ name: layer
425
+ for name, layer in layers.items()
426
+ if any(kw in name.lower() for kw in ["api", "route", "endpoint", "handler", "view"])
427
+ }
428
+ if api_layers:
429
+ parts = ["API/Route layers:"]
430
+ for name, layer in api_layers.items():
431
+ parts.append(f" {name}: {layer.summary[:80]}")
432
+ return ReasoningResult(
433
+ question="routes",
434
+ answer="\n".join(parts),
435
+ layers=list(api_layers.keys()),
436
+ )
437
+ return _answer_general(r, layers, question)
438
+
439
+ if category == "layers":
440
+ return _answer_layers(r, layers)
441
+
442
+ return _answer_general(r, layers, question)
@@ -0,0 +1,184 @@
1
+ """
2
+ Red Team Agent — adversarial testing and attack simulation.
3
+
4
+ Generates adversarial test cases and simulates attacker workflows.
5
+ Reports findings with exploit scenarios and severity.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import re
11
+
12
+ from patchi.core.agents.base import (
13
+ AgentDomain,
14
+ AgentGroup,
15
+ AgentInput,
16
+ AgentResult,
17
+ BaseAgent,
18
+ Finding,
19
+ Severity,
20
+ register,
21
+ safe_rglob,
22
+ )
23
+
24
+
25
+ @register
26
+ class RedTeamAgent(BaseAgent):
27
+ """Adversarial testing: attack trees, exploit scenarios, fuzzing targets."""
28
+
29
+ name = "RedTeamAgent"
30
+ group = AgentGroup.SECURITY
31
+ domain = AgentDomain.SECURITY
32
+ timeout = 120
33
+
34
+ # Static-analysis hits mapped to the attack-scenario library
35
+ # (patchi/core/security/attack_scenarios/) and fix playbooks, so findings
36
+ # from this agent feed the Red Team Engine / AutoFixer pipeline instead of
37
+ # being dead-end pattern reports.
38
+ _PATTERN_LINKS: dict[str, tuple[str, str]] = {
39
+ # finding-message substring -> (scenario_family, remediation_playbook)
40
+ "eval(": ("injection-command", "fix-command-injection-no-eval"),
41
+ "exec(": ("injection-command", "fix-command-injection-no-exec"),
42
+ "os.system": ("injection-command", "fix-command-injection-shell-false"),
43
+ "subprocess.call": ("injection-command", "fix-command-injection-shell-false"),
44
+ "pickle deserialization": ("deserialization", "fix-deserialization-safe-load"),
45
+ "marshal deserialization": ("deserialization", "fix-deserialization-safe-load"),
46
+ "yaml.load": ("deserialization", "fix-deserialization-safe-load"),
47
+ "template rendering": ("xss-template", "fix-xss-template-autoescape"),
48
+ "debug mode enabled": ("security-headers", "fix-misconfig-debug-off"),
49
+ "allowed_hosts": ("security-headers", "fix-misconfig-host-allowlist"),
50
+ }
51
+
52
+ def _link_for(self, message: str) -> tuple[str, str]:
53
+ for needle, link in self._PATTERN_LINKS.items():
54
+ if needle.lower() in message.lower():
55
+ return link
56
+ return ("", "")
57
+
58
+ def _run(self, inp: AgentInput, result: AgentResult) -> None:
59
+ routes = inp.brain.get("routes", [])
60
+ files_scanned = 0
61
+
62
+ # Analyze routes for attack surface
63
+ for route_info in routes:
64
+ route = (
65
+ route_info.get("path", "")
66
+ if isinstance(route_info, dict)
67
+ else getattr(route_info, "path", "")
68
+ )
69
+ method = (
70
+ route_info.get("method", "get")
71
+ if isinstance(route_info, dict)
72
+ else getattr(route_info, "method", "get")
73
+ ).lower()
74
+
75
+ # Check for parameterized routes (injection surface)
76
+ if re.search(r"\{[^}]+\}|:<\w+>|\d+", route):
77
+ result.add_finding(
78
+ Finding(
79
+ agent=self.name,
80
+ type="attack_surface",
81
+ severity=Severity.MEDIUM,
82
+ file="",
83
+ line=0,
84
+ message=f"Parameterized route: {method.upper()} {route}",
85
+ detail="Route accepts dynamic parameters — verify input validation",
86
+ suggestion="Validate/parameterize inputs; see sqli-basic-union scenario",
87
+ cwe="CWE-20",
88
+ extra={
89
+ "attack_surface": "static",
90
+ "scenario_family": "injection",
91
+ "remediation_playbook": "fix-sqli-parameterized-queries",
92
+ "route": f"{method.upper()} {route}",
93
+ },
94
+ )
95
+ )
96
+
97
+ # State-changing without apparent auth
98
+ if method in ("post", "put", "patch", "delete"):
99
+ result.add_finding(
100
+ Finding(
101
+ agent=self.name,
102
+ type="attack_surface",
103
+ severity=Severity.HIGH,
104
+ file="",
105
+ line=0,
106
+ message=f"State-changing endpoint: {method.upper()} {route}",
107
+ detail="Verify authentication and authorization are enforced",
108
+ suggestion="Enforce auth decorators + object-level authz checks",
109
+ cwe="CWE-306",
110
+ extra={
111
+ "attack_surface": "static",
112
+ "scenario_family": "authorization",
113
+ "remediation_playbook": "fix-authz-role-checks",
114
+ "route": f"{method.upper()} {route}",
115
+ },
116
+ )
117
+ )
118
+
119
+ # Scan source for attack patterns
120
+ source_patterns = ["*.py", "*.js", "*.ts", "*.jsx", "*.tsx", "*.go", "*.java"]
121
+ attack_patterns = [
122
+ (r"eval\s*\(", Severity.CRITICAL, "eval() usage — potential code injection"),
123
+ (r"exec\s*\(", Severity.CRITICAL, "exec() usage — potential code injection"),
124
+ (r"os\.system\s*\(", Severity.CRITICAL, "os.system() — command injection risk"),
125
+ (
126
+ r"subprocess\.call\s*\(",
127
+ Severity.HIGH,
128
+ "subprocess.call — verify input sanitization",
129
+ ),
130
+ (r"pickle\.loads?\s*\(", Severity.CRITICAL, "pickle deserialization — RCE risk"),
131
+ (r"marshal\.loads?\s*\(", Severity.CRITICAL, "marshal deserialization — RCE risk"),
132
+ (
133
+ r"yaml\.load\s*\([^)]*\)",
134
+ Severity.HIGH,
135
+ "yaml.load without SafeLoader — code execution risk",
136
+ ),
137
+ (
138
+ r"template\.render\s*\(",
139
+ Severity.MEDIUM,
140
+ "Template rendering — verify XSS protection",
141
+ ),
142
+ (r"DEBUG\s*=\s*True", Severity.HIGH, "Debug mode enabled — information disclosure"),
143
+ (r'ALLOWED_HOSTS\s*=\s*\["?\*"?\]', Severity.HIGH, "ALLOWED_HOSTS set to wildcard"),
144
+ ]
145
+
146
+ for pattern in source_patterns:
147
+ for fpath in safe_rglob(inp.root, pattern):
148
+ if not fpath.is_file():
149
+ continue
150
+ rel = fpath.relative_to(inp.root).as_posix()
151
+ try:
152
+ content = fpath.read_text(encoding="utf-8", errors="ignore")
153
+ except OSError:
154
+ continue
155
+ files_scanned += 1
156
+
157
+ lines = content.splitlines()
158
+ for i, line in enumerate(lines, 1):
159
+ for rx, sev, msg in attack_patterns:
160
+ if re.search(rx, line):
161
+ family, playbook = self._link_for(msg)
162
+ result.add_finding(
163
+ Finding(
164
+ agent=self.name,
165
+ type="vulnerable_pattern",
166
+ severity=sev,
167
+ file=rel,
168
+ line=i,
169
+ message=msg,
170
+ code_snippet=line.strip()[:120],
171
+ suggestion=(
172
+ f"Remediate via playbook: {playbook}"
173
+ if playbook
174
+ else "Review for safe usage or remove"
175
+ ),
176
+ extra={
177
+ "attack_surface": "static",
178
+ "scenario_family": family,
179
+ "remediation_playbook": playbook,
180
+ },
181
+ )
182
+ )
183
+
184
+ result.files_scanned = files_scanned