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,497 @@
1
+ """
2
+ DNSSecurityAgent — DNS security issues.
3
+
4
+ Detects DNS-related security issues:
5
+ - Dangling/orphaned DNS records (subdomain takeover risk)
6
+ - Missing zone transfer protection
7
+ - Missing DNSSEC configuration
8
+ - Missing CAA records
9
+ - Terraform Route53 misconfigurations
10
+ - Exposed DNS zone files
11
+
12
+ Uses static file analysis and optional external tools (dig, nslookup, dnsReaper).
13
+ Does NOT call AI.
14
+ Does NOT write to disk.
15
+ Does NOT touch the queue.
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import logging
21
+ import re
22
+ import shutil
23
+ import subprocess
24
+ from pathlib import Path
25
+
26
+ from ..agents.base import (
27
+ AgentDomain,
28
+ AgentGroup,
29
+ AgentInput,
30
+ AgentResult,
31
+ AgentStatus,
32
+ BaseAgent,
33
+ Finding,
34
+ Severity,
35
+ make_finding,
36
+ register,
37
+ safe_rglob,
38
+ )
39
+ from ..brain.trace_log import trace_agent
40
+
41
+ _SOURCE_EXTENSIONS = {
42
+ "*.py",
43
+ "*.js",
44
+ "*.jsx",
45
+ "*.ts",
46
+ "*.tsx",
47
+ "*.java",
48
+ "*.php",
49
+ "*.rb",
50
+ "*.go",
51
+ "*.rs",
52
+ "*.cs",
53
+ }
54
+
55
+ _DNS_CONFIG_NAMES = {
56
+ "dns.tf",
57
+ "route53.tf",
58
+ "dns.yaml",
59
+ "dns.yml",
60
+ "dns.json",
61
+ "named.conf",
62
+ "zone.db",
63
+ "db.",
64
+ "corefile",
65
+ "Corefile",
66
+ "unbound.conf",
67
+ }
68
+
69
+ _ROUTE53_PATTERNS = [
70
+ (re.compile(r"aws_route53_record", re.IGNORECASE), "Terraform Route53 record"),
71
+ (re.compile(r"aws_route53_zone", re.IGNORECASE), "Terraform Route53 zone"),
72
+ (re.compile(r"Route53|route53", re.IGNORECASE), "Route53 reference"),
73
+ ]
74
+
75
+ _ZONE_TRANSFER_PATTERNS = [
76
+ (re.compile(r"allow-transfer\s*\{[^}]*any", re.IGNORECASE), "DNS zone transfer allowed to any"),
77
+ (re.compile(r"allow-transfer\s*\{[^}]*\}", re.IGNORECASE), "DNS zone transfer configured"),
78
+ (re.compile(r"also-notify", re.IGNORECASE), "DNS secondary notification configured"),
79
+ ]
80
+
81
+ _DNSSEC_PATTERNS = [
82
+ re.compile(r"dnssec\s*=\s*['\"]?enable", re.IGNORECASE),
83
+ re.compile(r"dnssec-validation", re.IGNORECASE),
84
+ re.compile(r"SignedZone|signed-zone", re.IGNORECASE),
85
+ ]
86
+
87
+ _CAA_RECORD_PATTERNS = [
88
+ re.compile(r"CAA|caa_record", re.IGNORECASE),
89
+ ]
90
+
91
+ _SUBDOMAIN_TAKEOVER_PATTERNS = [
92
+ (
93
+ re.compile(
94
+ r"CNAME\s+\S+\.(amazonaws\.com|azurewebsites\.net|herokuapp\.com|github\.io|surge\.sh|bitbucket\.io|ghost\.io)",
95
+ re.IGNORECASE,
96
+ ),
97
+ "Potential dangling CNAME to third-party service",
98
+ ),
99
+ (
100
+ re.compile(
101
+ r"ALIAS\s+\S+\.(amazonaws\.com|azurewebsites\.net|herokuapp\.com)", re.IGNORECASE
102
+ ),
103
+ "Potential dangling ALIAS to third-party service",
104
+ ),
105
+ ]
106
+
107
+
108
+ _log = logging.getLogger("patchi.security.dns_security_agent")
109
+
110
+
111
+ @register
112
+ class DNSSecurityAgent(BaseAgent):
113
+ """Agent for detecting DNS security issues."""
114
+
115
+ group = AgentGroup.SECURITY
116
+ domain = AgentDomain.SECURITY
117
+ name = "DNSSecurityAgent"
118
+ description = "DNS security: dangling records, zone transfer, DNSSEC, CAA records"
119
+
120
+ def _run(self, inp: AgentInput, result: AgentResult) -> None:
121
+ findings: list[Finding] = []
122
+ files_scanned = 0
123
+
124
+ with trace_agent(self.name, inp.root) as trace:
125
+ # ── Static analysis ────────────────────────────────────────────
126
+ findings.extend(self._scan_dns_configs(inp))
127
+ findings.extend(self._scan_zone_transfer(inp))
128
+ findings.extend(self._scan_dnssec(inp))
129
+ findings.extend(self._scan_caa_records(inp))
130
+ findings.extend(self._scan_terraform_route53(inp))
131
+ findings.extend(self._scan_subdomain_takeover(inp))
132
+
133
+ for pattern in _SOURCE_EXTENSIONS:
134
+ for fp in safe_rglob(inp.root, pattern):
135
+ if fp.is_file():
136
+ files_scanned += 1
137
+ findings.extend(self._scan_file(fp, inp.root))
138
+
139
+ # ── External tools ─────────────────────────────────────────────
140
+ if shutil.which("dig"):
141
+ findings.extend(self._run_dig_checks(inp))
142
+ if shutil.which("dnsReaper") or shutil.which("dnsreaper"):
143
+ findings.extend(self._run_dnsreaper(inp))
144
+
145
+ trace.findings = len(findings)
146
+ trace.files_scanned = files_scanned
147
+
148
+ result.status = AgentStatus.SUCCEEDED
149
+ result.findings = findings
150
+ result.data.update(
151
+ {
152
+ "dns_findings": len(findings),
153
+ "dig_available": shutil.which("dig") is not None,
154
+ "dnsreaper_available": shutil.which("dnsReaper") is not None
155
+ or shutil.which("dnsreaper") is not None,
156
+ }
157
+ )
158
+ return
159
+
160
+ # ── DNS config file scan ───────────────────────────────────────────────
161
+
162
+ def _scan_dns_configs(self, inp: AgentInput) -> list[Finding]:
163
+ findings: list[Finding] = []
164
+ for fp in safe_rglob(inp.root, "*"):
165
+ if fp.name in _DNS_CONFIG_NAMES or fp.name.startswith("db."):
166
+ rel = fp.relative_to(inp.root).as_posix()
167
+ try:
168
+ content = fp.read_text(encoding="utf-8", errors="replace")
169
+ if "dnssec" not in content.lower():
170
+ findings.append(
171
+ make_finding(
172
+ severity=Severity.MEDIUM,
173
+ file=rel,
174
+ line_start=0,
175
+ title="DNS-03: DNSSEC Not Configured",
176
+ description="DNS zone file found without DNSSEC signing configuration.",
177
+ suggestion="Enable DNSSEC to prevent DNS spoofing and cache poisoning.",
178
+ )
179
+ )
180
+ if "CAA" not in content:
181
+ findings.append(
182
+ make_finding(
183
+ severity=Severity.MEDIUM,
184
+ file=rel,
185
+ line_start=0,
186
+ title="DNS-04: No CAA Records Found",
187
+ description="No CAA (Certification Authority Authorization) records found in zone file.",
188
+ suggestion="Add CAA records to restrict certificate issuance to authorized CAs.",
189
+ )
190
+ )
191
+ except Exception as e:
192
+ _log.warning("DNSSecurityAgent._scan_dns_configs failed: %s", e)
193
+ return findings
194
+
195
+ # ── Zone transfer protection ───────────────────────────────────────────
196
+
197
+ def _scan_zone_transfer(self, inp: AgentInput) -> list[Finding]:
198
+ findings: list[Finding] = []
199
+ for pattern in ("*.conf", "*.cfg", "*.tf", "*.yaml", "*.yml"):
200
+ for fp in safe_rglob(inp.root, pattern):
201
+ if not fp.is_file():
202
+ continue
203
+ rel = fp.relative_to(inp.root).as_posix()
204
+ try:
205
+ content = fp.read_text(encoding="utf-8", errors="replace")
206
+ lines = content.splitlines()
207
+ for i, line in enumerate(lines, 1):
208
+ for rx, desc in _ZONE_TRANSFER_PATTERNS:
209
+ if rx.search(line):
210
+ severity = (
211
+ Severity.HIGH if "any" in line.lower() else Severity.MEDIUM
212
+ )
213
+ findings.append(
214
+ make_finding(
215
+ severity=severity,
216
+ file=rel,
217
+ line_start=i,
218
+ title="DNS-01: Zone Transfer Risk",
219
+ description=desc,
220
+ evidence=line.strip(),
221
+ suggestion="Restrict zone transfers to authorized secondaries only.",
222
+ )
223
+ )
224
+ break
225
+ except Exception as e:
226
+ _log.warning("DNSSecurityAgent._scan_zone_transfer failed: %s", e)
227
+ return findings
228
+
229
+ # ── DNSSEC configuration ───────────────────────────────────────────────
230
+
231
+ def _scan_dnssec(self, inp: AgentInput) -> list[Finding]:
232
+ findings: list[Finding] = []
233
+ for pattern in ("*.conf", "*.tf", "*.yaml", "*.yml", "*.json"):
234
+ for fp in safe_rglob(inp.root, pattern):
235
+ if not fp.is_file():
236
+ continue
237
+ rel = fp.relative_to(inp.root).as_posix()
238
+ try:
239
+ content = fp.read_text(encoding="utf-8", errors="replace")
240
+ has_dnssec = any(rx.search(content) for rx in _DNSSEC_PATTERNS)
241
+ if not has_dnssec and any(
242
+ k in content.lower() for k in ("zone", "dns", "route53")
243
+ ):
244
+ findings.append(
245
+ make_finding(
246
+ severity=Severity.MEDIUM,
247
+ file=rel,
248
+ line_start=0,
249
+ title="DNS-03: DNSSEC Not Enabled",
250
+ description="DNS configuration found without DNSSEC validation.",
251
+ suggestion="Enable DNSSEC validation to prevent DNS spoofing.",
252
+ )
253
+ )
254
+ except Exception as e:
255
+ _log.warning("DNSSecurityAgent._scan_dnssec failed: %s", e)
256
+ return findings
257
+
258
+ # ── CAA records ────────────────────────────────────────────────────────
259
+
260
+ def _scan_caa_records(self, inp: AgentInput) -> list[Finding]:
261
+ findings: list[Finding] = []
262
+ for pattern in ("*.tf", "*.yaml", "*.yml", "*.json", "*.conf"):
263
+ for fp in safe_rglob(inp.root, pattern):
264
+ if not fp.is_file():
265
+ continue
266
+ rel = fp.relative_to(inp.root).as_posix()
267
+ try:
268
+ content = fp.read_text(encoding="utf-8", errors="replace")
269
+ if any(k in content.lower() for k in ("zone", "dns", "domain", "route53")):
270
+ has_caa = any(rx.search(content) for rx in _CAA_RECORD_PATTERNS)
271
+ if not has_caa:
272
+ findings.append(
273
+ make_finding(
274
+ severity=Severity.MEDIUM,
275
+ file=rel,
276
+ line_start=0,
277
+ title="DNS-04: No CAA Records",
278
+ description="DNS zone configuration has no CAA records to restrict certificate issuance.",
279
+ suggestion='Add CAA records (e.g., 0 issue "letsencrypt.org").',
280
+ )
281
+ )
282
+ except Exception as e:
283
+ _log.warning("DNSSecurityAgent._scan_caa_records failed: %s", e)
284
+ return findings
285
+
286
+ # ── Terraform Route53 ──────────────────────────────────────────────────
287
+
288
+ def _scan_terraform_route53(self, inp: AgentInput) -> list[Finding]:
289
+ findings: list[Finding] = []
290
+ for fp in safe_rglob(inp.root, "*.tf"):
291
+ if not fp.is_file():
292
+ continue
293
+ rel = fp.relative_to(inp.root).as_posix()
294
+ try:
295
+ content = fp.read_text(encoding="utf-8", errors="replace")
296
+ has_route53 = any(rx.search(content) for rx in _ROUTE53_PATTERNS)
297
+ if has_route53:
298
+ if "enable_dnssec" not in content.lower():
299
+ findings.append(
300
+ make_finding(
301
+ severity=Severity.MEDIUM,
302
+ file=rel,
303
+ line_start=0,
304
+ title="DNS-03: Route53 Zone Without DNSSEC",
305
+ description="Terraform Route53 zone does not enable DNSSEC.",
306
+ suggestion="Set enable_dnssec = true on aws_route53_zone.",
307
+ )
308
+ )
309
+ if "caa" not in content.lower() and "record" in content.lower():
310
+ findings.append(
311
+ make_finding(
312
+ severity=Severity.MEDIUM,
313
+ file=rel,
314
+ line_start=0,
315
+ title="DNS-04: Route53 Without CAA Records",
316
+ description="Route53 records found but no CAA record defined.",
317
+ suggestion="Add an aws_route53_record of type CAA.",
318
+ )
319
+ )
320
+ if "allow_transfer" not in content.lower() and "zone" in content.lower():
321
+ findings.append(
322
+ make_finding(
323
+ severity=Severity.LOW,
324
+ file=rel,
325
+ line_start=0,
326
+ title="DNS-05: Route53 Zone Transfer Not Configured",
327
+ description="Route53 zone found without explicit transfer restrictions.",
328
+ suggestion="Ensure zone transfer is restricted or disabled.",
329
+ )
330
+ )
331
+ except Exception as e:
332
+ _log.warning("DNSSecurityAgent._scan_terraform_route53 failed: %s", e)
333
+ return findings
334
+
335
+ # ── Subdomain takeover patterns ────────────────────────────────────────
336
+
337
+ def _scan_subdomain_takeover(self, inp: AgentInput) -> list[Finding]:
338
+ findings: list[Finding] = []
339
+ for pattern in ("*.tf", "*.yaml", "*.yml", "*.json", "*.conf", "*.db"):
340
+ for fp in safe_rglob(inp.root, pattern):
341
+ if not fp.is_file():
342
+ continue
343
+ rel = fp.relative_to(inp.root).as_posix()
344
+ try:
345
+ content = fp.read_text(encoding="utf-8", errors="replace")
346
+ lines = content.splitlines()
347
+ for i, line in enumerate(lines, 1):
348
+ for rx, desc in _SUBDOMAIN_TAKEOVER_PATTERNS:
349
+ if rx.search(line):
350
+ findings.append(
351
+ make_finding(
352
+ severity=Severity.HIGH,
353
+ file=rel,
354
+ line_start=i,
355
+ title="DNS-02: Potential Subdomain Takeover",
356
+ description=desc,
357
+ evidence=line.strip(),
358
+ suggestion="Verify the target service is claimed and not vulnerable to takeover.",
359
+ )
360
+ )
361
+ except Exception as e:
362
+ _log.warning("DNSSecurityAgent._scan_subdomain_takeover failed: %s", e)
363
+ return findings
364
+
365
+ # ── General source file scan ───────────────────────────────────────────
366
+
367
+ def _scan_file(self, fp: Path, root: Path) -> list[Finding]:
368
+ findings: list[Finding] = []
369
+ rel = fp.relative_to(root).as_posix()
370
+ try:
371
+ content = fp.read_text(encoding="utf-8", errors="replace")
372
+ # DNS-related hardcoded secrets
373
+ secret_patterns = [
374
+ (
375
+ re.compile(
376
+ r"(?:route53|dns|cloudflare)[_-]?(?:api[_-]?key|token|secret)\s*[:=]\s*['\"][^'\"]{8,}['\"]",
377
+ re.IGNORECASE,
378
+ ),
379
+ "DNS-06: Hardcoded DNS/CDN API Credential",
380
+ ),
381
+ ]
382
+ lines = content.splitlines()
383
+ for i, line in enumerate(lines, 1):
384
+ for rx, title in secret_patterns:
385
+ if rx.search(line):
386
+ findings.append(
387
+ make_finding(
388
+ severity=Severity.CRITICAL,
389
+ file=rel,
390
+ line_start=i,
391
+ title=title,
392
+ description="Hardcoded DNS API credential found in source code.",
393
+ evidence=line.strip()[:120],
394
+ suggestion="Move DNS API credentials to environment variables or a secrets manager.",
395
+ )
396
+ )
397
+ except Exception as e:
398
+ _log.warning("DNSSecurityAgent._scan_file failed: %s", e)
399
+ return findings
400
+
401
+ # ── External tool: dig ─────────────────────────────────────────────────
402
+
403
+ def _run_dig_checks(self, inp: AgentInput) -> list[Finding]:
404
+ """Run dig to check DNS records if a domain is provided."""
405
+ findings: list[Finding] = []
406
+ domain = inp.config.get("dns_check_domain")
407
+ if not domain or not isinstance(domain, str):
408
+ return findings
409
+
410
+ # Check for zone transfer vulnerability
411
+ try:
412
+ proc = subprocess.run(
413
+ ["dig", "axfr", domain, "+short", "+timeout=5"],
414
+ capture_output=True,
415
+ text=True,
416
+ timeout=10,
417
+ )
418
+ if proc.returncode == 0 and proc.stdout.strip():
419
+ findings.append(
420
+ make_finding(
421
+ severity=Severity.CRITICAL,
422
+ file="(dns_probe)",
423
+ line_start=0,
424
+ title="DNS-01: Zone Transfer Allowed",
425
+ description=f"DNS zone transfer (AXFR) succeeded for {domain}.",
426
+ evidence=proc.stdout[:300],
427
+ suggestion="Restrict zone transfers to authorized secondaries.",
428
+ )
429
+ )
430
+ except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
431
+ pass
432
+
433
+ # Check for DNSSEC
434
+ try:
435
+ proc = subprocess.run(
436
+ ["dig", "+dnssec", domain, "+short", "+timeout=5"],
437
+ capture_output=True,
438
+ text=True,
439
+ timeout=10,
440
+ )
441
+ if proc.returncode == 0 and "RRSIG" not in proc.stdout:
442
+ findings.append(
443
+ make_finding(
444
+ severity=Severity.MEDIUM,
445
+ file="(dns_probe)",
446
+ line_start=0,
447
+ title="DNS-03: DNSSEC Not Active",
448
+ description=f"No DNSSEC RRSIG records found for {domain}.",
449
+ evidence=proc.stdout[:200],
450
+ suggestion="Enable DNSSEC signing for the domain.",
451
+ )
452
+ )
453
+ except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
454
+ pass
455
+
456
+ return findings
457
+
458
+ # ── External tool: dnsReaper ───────────────────────────────────────────
459
+
460
+ def _run_dnsreaper(self, inp: AgentInput) -> list[Finding]:
461
+ """Run dnsReaper for subdomain takeover detection."""
462
+ findings: list[Finding] = []
463
+ domain = inp.config.get("dns_check_domain")
464
+ if not domain or not isinstance(domain, str):
465
+ return findings
466
+
467
+ tool = "dnsReaper" if shutil.which("dnsReaper") else "dnsreaper"
468
+ try:
469
+ proc = subprocess.run(
470
+ [tool, "scan", "--domain", domain, "--output", "json"],
471
+ capture_output=True,
472
+ text=True,
473
+ timeout=120,
474
+ )
475
+ if proc.returncode == 0 and proc.stdout.strip():
476
+ import json
477
+
478
+ try:
479
+ data = json.loads(proc.stdout)
480
+ for record in data if isinstance(data, list) else []:
481
+ if record.get("vulnerable"):
482
+ findings.append(
483
+ make_finding(
484
+ severity=Severity.HIGH,
485
+ file="(dnsreaper)",
486
+ line_start=0,
487
+ title="DNS-06: Subdomain Takeover Vulnerability",
488
+ description=f"Subdomain {record.get('subdomain', 'unknown')} is vulnerable to takeover.",
489
+ evidence=str(record)[:200],
490
+ suggestion="Remove the dangling record or claim the target resource.",
491
+ )
492
+ )
493
+ except json.JSONDecodeError:
494
+ pass
495
+ except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
496
+ pass
497
+ return findings