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,2183 @@
1
+ """
2
+ Patchi Domain Activator
3
+ =======================
4
+
5
+ Determines which security domains should be activated for a given codebase
6
+ based on detected imports, dependencies, file extensions, infra files, and
7
+ other signals. Each domain has a dedicated ``_check_<domain_id>`` function
8
+ that returns ``True`` if the domain should be activated.
9
+
10
+ The functions are registered in ``_DOMAIN_CHECKERS`` and invoked by the
11
+ scanner with an 11-element tuple (see ``_DomainContext`` below).
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ # ---------------------------------------------------------------------------
17
+ # Context passed to every checker
18
+ # ---------------------------------------------------------------------------
19
+ import logging
20
+ import re
21
+ from collections.abc import Callable, Iterable, Sequence
22
+ from dataclasses import dataclass
23
+ from typing import Any
24
+
25
+ _log = logging.getLogger("patchi.brain.domain_activator")
26
+
27
+
28
+ @dataclass(frozen=True)
29
+ class _DomainContext:
30
+ """Structured view of the 11-element args tuple passed by the scanner.
31
+
32
+ Fields (in order):
33
+ language: detected primary language (e.g. "python", "go")
34
+ framework: detected framework name (e.g. "django", "rails")
35
+ imports: list of import strings observed in source files
36
+ deps: list of declared dependencies (gemfile, package.json, etc.)
37
+ routes: list of route strings detected (URL paths, handlers)
38
+ config_keys: list of config keys observed in YAML/ENV/toml files
39
+ infra_files: list of infra-relative file paths (Cargo.toml, routes.rb, etc.)
40
+ has_web: True if a web framework is detected
41
+ has_cli: True if a CLI entrypoint is detected
42
+ has_mobile: True if mobile (iOS/Android) markers are present
43
+ exts: list of file extensions observed (lowercase, with leading dot)
44
+ """
45
+
46
+ language: str
47
+ framework: str
48
+ imports: Sequence[str]
49
+ deps: Sequence[str]
50
+ routes: Sequence[str]
51
+ config_keys: Sequence[str]
52
+ infra_files: Sequence[str]
53
+ has_web: bool
54
+ has_cli: bool
55
+ has_mobile: bool
56
+ exts: Sequence[str]
57
+
58
+ @classmethod
59
+ def from_args(cls, args: Sequence) -> _DomainContext:
60
+ if len(args) != 11:
61
+ raise ValueError(f"Expected 11-element args tuple, got {len(args)}: {args!r}")
62
+ (
63
+ language,
64
+ framework,
65
+ imports,
66
+ deps,
67
+ routes,
68
+ config_keys,
69
+ infra_files,
70
+ has_web,
71
+ has_cli,
72
+ has_mobile,
73
+ exts,
74
+ ) = args
75
+ return cls(
76
+ language=language or "",
77
+ framework=framework or "",
78
+ imports=tuple(imports or ()),
79
+ deps=tuple(deps or ()),
80
+ routes=tuple(routes or ()),
81
+ config_keys=tuple(config_keys or ()),
82
+ infra_files=tuple(infra_files or ()),
83
+ has_web=bool(has_web),
84
+ has_cli=bool(has_cli),
85
+ has_mobile=bool(has_mobile),
86
+ exts=tuple(exts or ()),
87
+ )
88
+
89
+
90
+ # ---------------------------------------------------------------------------
91
+ # Matching helpers
92
+ # ---------------------------------------------------------------------------
93
+
94
+
95
+ def _compile_pattern(pat: str) -> re.Pattern:
96
+ """Compile a substring pattern into a regex with word-boundary awareness.
97
+
98
+ For patterns that begin or end with a word character (letter, digit, or
99
+ underscore), we add a negative lookbehind / lookahead so that the pattern
100
+ must NOT be preceded/followed by another word character. This prevents
101
+ false positives like the pattern ``"sync"`` matching the Python import
102
+ ``"asyncio"`` (which contains "sync" as a substring) while still matching
103
+ legitimate occurrences like ``"sync.Mutex"``, ``"use sync"``, etc.
104
+
105
+ For patterns that begin or end with a non-word character (e.g. ``"libc::"``,
106
+ ``"<stdlib.h>"``, ``"@sveltejs/kit"``), no boundary assertion is added on
107
+ that side because the non-word character itself acts as a sufficient
108
+ delimiter.
109
+ """
110
+ escaped = re.escape(pat)
111
+ if pat and (pat[0].isalnum() or pat[0] == "_"):
112
+ escaped = r"(?<![A-Za-z0-9_])" + escaped
113
+ if pat and (pat[-1].isalnum() or pat[-1] == "_"):
114
+ escaped = escaped + r"(?![A-Za-z0-9_])"
115
+ return re.compile(escaped, re.IGNORECASE)
116
+
117
+
118
+ def _has_import(imports: Iterable[str], patterns: Iterable[str]) -> bool:
119
+ """Return True if any ``patterns`` substring is found in any ``imports`` entry.
120
+
121
+ Uses word-boundary-aware matching (see :func:`_compile_pattern`) so that
122
+ short identifiers like ``"sync"`` or ``"context"`` do not match inside
123
+ longer identifiers like ``"asyncio"`` or ``"user_context_manager"``.
124
+ """
125
+ compiled = [_compile_pattern(p) for p in patterns]
126
+ for imp in imports:
127
+ for r in compiled:
128
+ if r.search(imp):
129
+ return True
130
+ return False
131
+
132
+
133
+ def _has_dependency(deps: Iterable[str], patterns: Iterable[str]) -> bool:
134
+ """Return True if any ``patterns`` substring is found in any ``deps`` entry.
135
+
136
+ Same word-boundary semantics as :func:`_has_import`. This prevents e.g.
137
+ a pattern ``"rails"`` from matching a dep named ``"grails"``.
138
+ """
139
+ compiled = [_compile_pattern(p) for p in patterns]
140
+ for dep in deps:
141
+ for r in compiled:
142
+ if r.search(dep):
143
+ return True
144
+ return False
145
+
146
+
147
+ def _has_ext(exts: Iterable[str], patterns: Iterable[str]) -> bool:
148
+ """Return True if any ``patterns`` extension is observed.
149
+
150
+ Patterns are matched as case-insensitive suffix so that ``".go"`` matches
151
+ both ``".go"`` and ``".GO"`` (rare, but legacy Windows tooling produces it).
152
+ """
153
+ exts_lower = [e.lower() for e in exts]
154
+ return any(ext.lower().endswith(pat.lower()) for pat in patterns for ext in exts_lower)
155
+
156
+
157
+ def _has_infra_file(infra_files: Iterable[str], patterns: Iterable[str]) -> bool:
158
+ """Return True if any infra file path matches any pattern (case-insensitive)."""
159
+ infra_lower = [f.lower() for f in infra_files]
160
+ return any(pat.lower() in f for pat in patterns for f in infra_lower)
161
+
162
+
163
+ def _has_config_key(config_keys: Iterable[str], patterns: Iterable[str]) -> bool:
164
+ """Return True if any config key matches any pattern (case-insensitive)."""
165
+ keys_lower = [k.lower() for k in config_keys]
166
+ return any(pat.lower() in k for pat in patterns for k in keys_lower)
167
+
168
+
169
+ # ---------------------------------------------------------------------------
170
+ # Domain checkers
171
+ # ---------------------------------------------------------------------------
172
+
173
+
174
+ def _check_native_code_safety(*args) -> bool:
175
+ """Activate for C/C++/Rust codebases with native-unsafe patterns."""
176
+ ctx = _DomainContext.from_args(args)
177
+
178
+ ncs_exts = [".c", ".cpp", ".cc", ".cxx", ".h", ".hpp", ".hh", ".hxx", ".rs"]
179
+ ncs_imports = [
180
+ "unsafe", # Rust unsafe blocks (lexically detectable)
181
+ "std::ptr", # Rust raw pointer module
182
+ "std::mem::transmute", # Rust transmute
183
+ "libc::", # Rust libc FFI
184
+ 'extern "C"', # Rust FFI
185
+ "Box::into_raw", # Rust manual lifetime
186
+ "Box::from_raw",
187
+ "<stdlib.h>", # C/C++ malloc family
188
+ "<string.h>", # C/C++ strcpy family
189
+ "<stdio.h>", # C/C++ printf family
190
+ "strcpy",
191
+ "strcat",
192
+ "sprintf",
193
+ "gets", # CWE-119 classics
194
+ ]
195
+ ncs_deps: list[str] = [] # C/C++/Rust do not use dep manifests we scan here
196
+
197
+ return (
198
+ _has_ext(ctx.exts, ncs_exts)
199
+ or _has_import(ctx.imports, ncs_imports)
200
+ or _has_dependency(ctx.deps, ncs_deps)
201
+ )
202
+
203
+
204
+ def _check_go_concurrency(*args) -> bool:
205
+ """Activate for Go codebases with goroutine / sync usage."""
206
+ ctx = _DomainContext.from_args(args)
207
+
208
+ gco_exts = [".go"]
209
+ gco_imports = [
210
+ "go func", # goroutine spawn (lexical)
211
+ "sync", # sync.Mutex / sync.RWMutex / sync.WaitGroup
212
+ "sync/atomic", # atomic primitives
213
+ "context", # context.Context for cancellation
214
+ "sync.Map", # concurrent map
215
+ "errgroup", # golang.org/x/sync/errgroup
216
+ "runtime.Goexit", # goroutine exit
217
+ ]
218
+ gco_deps = [
219
+ "golang.org/x/sync", # errgroup, semaphore
220
+ "github.com/sourcegraph/conc", # modern concurrency helpers
221
+ ]
222
+
223
+ return (
224
+ _has_ext(ctx.exts, gco_exts)
225
+ or _has_import(ctx.imports, gco_imports)
226
+ or _has_dependency(ctx.deps, gco_deps)
227
+ )
228
+
229
+
230
+ def _check_jvm_hardening(*args) -> bool:
231
+ """Activate for JVM (Java/Kotlin/Scala) codebases, esp. Spring Boot."""
232
+ ctx = _DomainContext.from_args(args)
233
+
234
+ jvm_exts = [".java", ".kt", ".kts", ".scala"]
235
+ jvm_imports = [
236
+ "org.springframework", # Spring Framework
237
+ "org.springframework.boot", # Spring Boot
238
+ "javax.servlet", # Servlet API
239
+ "jakarta.servlet", # Jakarta EE (Spring Boot 3+)
240
+ "java.io.ObjectInputStream", # CWE-502 deserialization
241
+ "java.lang.Runtime", # CWE-78 command exec
242
+ "java.lang.reflect", # CWE-470 reflection
243
+ "com.fasterxml.jackson", # Jackson (default typing risk)
244
+ "com.alibaba.fastjson", # Fastjson (autoType risk)
245
+ "org.apache.commons.collections", # ysoserial gadget
246
+ ]
247
+ jvm_deps = [
248
+ "spring-boot-starter",
249
+ "spring-boot-actuator", # CWE-526 actuator exposure
250
+ "spring-boot-devtools", # CWE-489 debug code
251
+ "spring-web",
252
+ "spring-webmvc",
253
+ "spring-webflux",
254
+ "javax.servlet:javax.servlet-api",
255
+ "jakarta.servlet:jakarta.servlet-api",
256
+ "org.apache.commons:commons-collections",
257
+ ]
258
+
259
+ jvm_infra = ["pom.xml", "build.gradle", "build.gradle.kts", "settings.gradle"]
260
+
261
+ return (
262
+ _has_ext(ctx.exts, jvm_exts)
263
+ or _has_import(ctx.imports, jvm_imports)
264
+ or _has_dependency(ctx.deps, jvm_deps)
265
+ or _has_infra_file(ctx.infra_files, jvm_infra)
266
+ )
267
+
268
+
269
+ def _check_mobile_native(*args) -> bool:
270
+ """Activate for iOS (Swift) / Android (Kotlin/Java) mobile codebases."""
271
+ ctx = _DomainContext.from_args(args)
272
+
273
+ mob_exts = [".swift", ".kt", ".kts", ".m", ".mm"]
274
+ mob_imports = [
275
+ "UIKit", # iOS UI framework
276
+ "SwiftUI", # iOS modern UI
277
+ "Foundation", # iOS core (often co-located)
278
+ "Security", # iOS Keychain
279
+ "androidx.", # AndroidX
280
+ "android.app", # Android app framework
281
+ "android.content", # Android Intent / Context
282
+ "android.security.keystore", # Android Keystore
283
+ "com.android", # Android tooling
284
+ "java.security.KeyStore", # Java/Android Keystore
285
+ ]
286
+ mob_deps = [
287
+ "com.android.tools.build:gradle", # Android Gradle plugin
288
+ "io.realm:realm", # Realm mobile DB
289
+ "androidx.compose", # Jetpack Compose
290
+ "io.coil-kt:coil", # Coil (Android image lib)
291
+ "com.google.firebase", # Firebase mobile SDK
292
+ "platform-ui", # iOS package alias
293
+ ]
294
+ mob_infra = [
295
+ "info.plist",
296
+ "androidmanifest.xml",
297
+ "project.pbxproj",
298
+ "package.swift",
299
+ "podfile",
300
+ "podfile.lock",
301
+ "build.gradle",
302
+ "build.gradle.kts",
303
+ ]
304
+
305
+ return (
306
+ _has_ext(ctx.exts, mob_exts)
307
+ or _has_import(ctx.imports, mob_imports)
308
+ or _has_dependency(ctx.deps, mob_deps)
309
+ or _has_infra_file(ctx.infra_files, mob_infra)
310
+ or ctx.has_mobile
311
+ )
312
+
313
+
314
+ def _check_ruby_rails(*args) -> bool:
315
+ """Activate for Ruby on Rails codebases."""
316
+ ctx = _DomainContext.from_args(args)
317
+
318
+ rrs_exts = [".rb", ".erb", ".rhtml", ".rjs", ".rake", ".gemspec"]
319
+ rrs_imports = [
320
+ "ActiveRecord", # ORM
321
+ "ActionController", # controllers
322
+ "ActionView", # views
323
+ "ActionDispatch", # routing
324
+ "Rails", # Rails constant
325
+ "ApplicationController",
326
+ "ApplicationRecord",
327
+ "ActiveModel",
328
+ "ActiveJob",
329
+ "ActiveSupport",
330
+ "protect_from_forgery", # CSRF config
331
+ "params.permit", # strong params
332
+ "attr_accessible", # legacy mass-assignment
333
+ ]
334
+ rrs_deps = [
335
+ "rails", # the framework
336
+ "activerecord",
337
+ "actionpack",
338
+ "activesupport",
339
+ "actionview",
340
+ "railties",
341
+ "pg", # Postgres adapter (often Rails)
342
+ "mysql2",
343
+ "puma", # Rails app server
344
+ "devise", # Rails auth
345
+ ]
346
+ rrs_infra = [
347
+ "config/routes.rb",
348
+ "config/application.rb",
349
+ "config/database.yml",
350
+ "gemfile",
351
+ "gemfile.lock",
352
+ "config.ru",
353
+ "bin/rails",
354
+ "rakefile",
355
+ ]
356
+
357
+ return (
358
+ _has_ext(ctx.exts, rrs_exts)
359
+ or _has_import(ctx.imports, rrs_imports)
360
+ or _has_dependency(ctx.deps, rrs_deps)
361
+ or _has_infra_file(ctx.infra_files, rrs_infra)
362
+ )
363
+
364
+
365
+ def _check_svelte_ssr(*args) -> bool:
366
+ """Activate for SvelteKit SSR codebases."""
367
+ ctx = _DomainContext.from_args(args)
368
+
369
+ ssr_exts = [".svelte", ".svelte.js", ".svelte.ts"]
370
+ ssr_imports = [
371
+ "@sveltejs/kit", # SvelteKit core
372
+ "$app/store", # SvelteKit $app module
373
+ "$app/environment",
374
+ "$app/navigation",
375
+ "$env/static/private", # private env (server-only)
376
+ "$env/dynamic/private",
377
+ "$env/static/public",
378
+ "$env/dynamic/public",
379
+ "$lib/server", # server-only lib
380
+ "import { redirect }", # SvelteKit redirect helper
381
+ "import { error }", # SvelteKit error helper
382
+ "cookies.set", # SvelteKit cookies API (called inside actions/load)
383
+ "cookies.get", # SvelteKit cookies API (read)
384
+ ]
385
+ ssr_deps = [
386
+ "@sveltejs/kit",
387
+ "@sveltejs/adapter-node",
388
+ "@sveltejs/adapter-auto",
389
+ "@sveltejs/vite-plugin-svelte",
390
+ "svelte",
391
+ ]
392
+ ssr_infra = [
393
+ "svelte.config.js",
394
+ "svelte.config.mjs",
395
+ "src/routes/+page.server.js",
396
+ "src/routes/+page.server.ts",
397
+ "src/routes/+layout.server.js",
398
+ "src/routes/+layout.server.ts",
399
+ "+page.server.js",
400
+ "+page.server.ts",
401
+ "+layout.server.js",
402
+ "+layout.server.ts",
403
+ "src/app.html",
404
+ ]
405
+
406
+ return (
407
+ _has_ext(ctx.exts, ssr_exts)
408
+ or _has_import(ctx.imports, ssr_imports)
409
+ or _has_dependency(ctx.deps, ssr_deps)
410
+ or _has_infra_file(ctx.infra_files, ssr_infra)
411
+ )
412
+
413
+
414
+ def _check_cargo_supply_chain(*args) -> bool:
415
+ """Activate for Rust codebases that declare any Cargo dependencies."""
416
+ ctx = _DomainContext.from_args(args)
417
+
418
+ csc_infra = [
419
+ "cargo.toml", # case-insensitive
420
+ "cargo.lock",
421
+ ]
422
+
423
+ # Activation requires Cargo.toml present AND a non-empty [dependencies] section.
424
+ # The scanner surfaces Cargo.toml as an infra_file; we trust its presence.
425
+ if not (
426
+ _has_infra_file(ctx.infra_files, csc_infra)
427
+ or _has_ext(ctx.exts, [".rs"])
428
+ or _has_dependency(ctx.deps, ["cargo"]) # rare: cargo as a Cargo plugin
429
+ ):
430
+ return False
431
+
432
+ # At least one Rust source file OR Cargo.toml presence is required.
433
+ # The deps list may include crates pulled from Cargo.toml; if non-empty,
434
+ # the project has dependencies to audit.
435
+ if ctx.deps:
436
+ return True
437
+
438
+ # Fallback: just Cargo.toml presence (we cannot verify dep count from here,
439
+ # but the scanner's domain runner will re-check the file's [dependencies] section).
440
+ return _has_infra_file(ctx.infra_files, csc_infra)
441
+
442
+
443
+ def _check_nodejs_runtime(*args) -> bool:
444
+ """Activate for Node.js codebases (JS/TS) using Node runtime APIs."""
445
+ ctx = _DomainContext.from_args(args)
446
+
447
+ njs_exts = [".js", ".mjs", ".cjs", ".ts", ".mts", ".cts"]
448
+ njs_imports = [
449
+ "require(", # CommonJS require
450
+ "child_process", # CWE-78 command exec
451
+ "node:child_process",
452
+ "eval(", # CWE-95 code injection
453
+ "new Function(", # CWE-95 dynamic function
454
+ "vm.runInNewContext", # CWE-95 vm sandbox (not a security boundary)
455
+ "vm.runInThisContext",
456
+ "node:vm",
457
+ "fs.readFile", # CWE-22 path traversal (fs family)
458
+ "fs.writeFile",
459
+ "fs.createReadStream",
460
+ "fs.createWriteStream",
461
+ "path.join", # often misused for path traversal
462
+ "path.resolve",
463
+ "process.env", # env var access (CWE-200 if leaked to client)
464
+ "__proto__", # CWE-1321 prototype pollution
465
+ "Object.assign", # CWE-1321 if source is user input
466
+ "RegExp(", # CWE-1333 ReDoS if user-controlled pattern
467
+ ]
468
+ njs_deps = [
469
+ "express", # Express framework (overlaps with express-web)
470
+ "fastify",
471
+ "koa",
472
+ "lodash", # CWE-1321 prototype pollution in old versions
473
+ "jquery", # CWE-1321 $.extend
474
+ "ejs", # CWE-1336 SSTI
475
+ "pug",
476
+ "nunjucks",
477
+ "handlebars",
478
+ "vm2", # deprecated, vulnerable sandbox
479
+ "isolated-vm", # safer sandbox (positive signal)
480
+ ]
481
+ njs_infra = [
482
+ "package.json",
483
+ "package-lock.json",
484
+ "yarn.lock",
485
+ "pnpm-lock.yaml",
486
+ "tsconfig.json",
487
+ ]
488
+
489
+ return (
490
+ _has_ext(ctx.exts, njs_exts)
491
+ or _has_import(ctx.imports, njs_imports)
492
+ or _has_dependency(ctx.deps, njs_deps)
493
+ or _has_infra_file(ctx.infra_files, njs_infra)
494
+ )
495
+
496
+
497
+ def _check_express_web(*args) -> bool:
498
+ """Activate for Express.js web applications."""
499
+ ctx = _DomainContext.from_args(args)
500
+
501
+ exp_imports = [
502
+ "express", # the framework
503
+ "app.use(", # Express middleware mounting
504
+ "app.get(",
505
+ "app.post(",
506
+ "app.put(",
507
+ "app.patch(",
508
+ "app.delete(",
509
+ "router.get(",
510
+ "router.post(",
511
+ "express.json",
512
+ "express.urlencoded",
513
+ "express.static",
514
+ "res.render", # template rendering (SSTI risk)
515
+ "res.cookie", # cookie setting (insecure flag risk)
516
+ "req.body", # body access (validation risk)
517
+ ]
518
+ exp_deps = [
519
+ "express",
520
+ "body-parser",
521
+ "cookie-parser",
522
+ "express-session",
523
+ "cookie-session",
524
+ "csurf", # deprecated but still used
525
+ "csrf-csrf", # modern replacement
526
+ "helmet", # security headers (positive signal)
527
+ "cors", # CORS config (misconfig risk)
528
+ "ejs", # template engines
529
+ "pug",
530
+ "nunjucks",
531
+ "handlebars",
532
+ "multer", # file upload
533
+ ]
534
+ exp_infra = [
535
+ "app.js",
536
+ "server.js",
537
+ "index.js",
538
+ "src/app.js",
539
+ "src/server.js",
540
+ ]
541
+
542
+ return (
543
+ _has_import(ctx.imports, exp_imports)
544
+ or _has_dependency(ctx.deps, exp_deps)
545
+ or _has_infra_file(ctx.infra_files, exp_infra)
546
+ )
547
+
548
+
549
+ def _check_nextjs_app(*args) -> bool:
550
+ """Activate for Next.js applications (App Router or Pages Router)."""
551
+ ctx = _DomainContext.from_args(args)
552
+
553
+ nxt_imports = [
554
+ "next/server", # Next.js server utilities
555
+ "next/navigation",
556
+ "next/headers",
557
+ "next/image", # Image optimization (SSRF risk)
558
+ "next/link",
559
+ "next/router",
560
+ "next/document",
561
+ "next/script",
562
+ "NextResponse", # Next.js response class
563
+ "NextRequest",
564
+ "getServerSideProps", # Pages Router SSR (data leak risk)
565
+ "getStaticProps",
566
+ "getInitialProps",
567
+ "use server", # Server Actions (Next.js 14+)
568
+ "searchParams", # App Router searchParams
569
+ ]
570
+ nxt_deps = [
571
+ "next",
572
+ "next-auth",
573
+ "@auth/core",
574
+ ]
575
+ nxt_infra = [
576
+ "next.config.js",
577
+ "next.config.mjs",
578
+ "next.config.ts",
579
+ "middleware.ts",
580
+ "middleware.js",
581
+ "src/middleware.ts",
582
+ "src/middleware.js",
583
+ "src/app/layout.tsx",
584
+ "src/app/layout.js",
585
+ "src/app/page.tsx",
586
+ "src/app/page.js",
587
+ "src/app/route.ts",
588
+ "src/app/route.js",
589
+ "pages/_app.js",
590
+ "pages/_app.tsx",
591
+ "pages/api/",
592
+ ]
593
+
594
+ return (
595
+ _has_import(ctx.imports, nxt_imports)
596
+ or _has_dependency(ctx.deps, nxt_deps)
597
+ or _has_infra_file(ctx.infra_files, nxt_infra)
598
+ )
599
+
600
+
601
+ def _check_python_runtime(*args) -> bool:
602
+ """Activate for Python codebases using runtime-unsafe APIs."""
603
+ ctx = _DomainContext.from_args(args)
604
+
605
+ pyr_exts = [".py", ".pyw", ".pyi"]
606
+ pyr_imports = [
607
+ "import os", # os.system (CWE-78)
608
+ "import subprocess", # subprocess shell=True (CWE-78)
609
+ "from subprocess",
610
+ "import pickle", # pickle.loads (CWE-502)
611
+ "import cPickle", # Python 2 pickle (CWE-502)
612
+ "import yaml", # yaml.load (CWE-502)
613
+ "import marshal", # marshal.loads (CWE-502)
614
+ "import shelve", # shelve uses pickle (CWE-502)
615
+ "import ctypes", # ctypes abuse (CWE-78)
616
+ "import requests", # SSRF risk
617
+ "import urllib", # SSRF risk
618
+ "from urllib",
619
+ "import httpx", # SSRF risk
620
+ "import aiohttp", # SSRF risk
621
+ "eval(", # CWE-95 code injection
622
+ "exec(", # CWE-95
623
+ "compile(", # CWE-95
624
+ "import re", # ReDoS risk
625
+ "tempfile.mktemp", # CWE-377 race condition
626
+ ]
627
+ pyr_deps = [
628
+ "requests",
629
+ "urllib3",
630
+ "httpx",
631
+ "aiohttp",
632
+ "pyyaml", # yaml.load risk
633
+ "pickle", # stdlib but listed for clarity
634
+ "google-re2", # positive signal (ReDoS mitigation)
635
+ ]
636
+ pyr_infra = [
637
+ "setup.py",
638
+ "setup.cfg",
639
+ "pyproject.toml",
640
+ "requirements.txt",
641
+ "pipfile",
642
+ "pipfile.lock",
643
+ "poetry.lock",
644
+ "tox.ini",
645
+ ]
646
+
647
+ return (
648
+ _has_ext(ctx.exts, pyr_exts)
649
+ or _has_import(ctx.imports, pyr_imports)
650
+ or _has_dependency(ctx.deps, pyr_deps)
651
+ or _has_infra_file(ctx.infra_files, pyr_infra)
652
+ )
653
+
654
+
655
+ def _check_django_hardening(*args) -> bool:
656
+ """Activate for Django applications."""
657
+ ctx = _DomainContext.from_args(args)
658
+
659
+ djg_imports = [
660
+ "django", # the framework
661
+ "from django",
662
+ "import django",
663
+ "django.http",
664
+ "django.conf",
665
+ "django.shortcuts",
666
+ "django.views",
667
+ "django.middleware",
668
+ "django.contrib",
669
+ "django.db.models",
670
+ "django.template",
671
+ "django.urls",
672
+ "Model.objects.raw", # SQL injection risk
673
+ "cursor.execute",
674
+ "csrf_exempt", # CSRF bypass risk
675
+ "autoescape off", # XSS risk in templates
676
+ ]
677
+ djg_deps = [
678
+ "django",
679
+ "Django",
680
+ "django-rest-framework",
681
+ "djangorestframework",
682
+ "django-cors-headers",
683
+ "django-debug-toolbar", # debug code risk
684
+ "django-extensions",
685
+ "celery", # often paired with Django
686
+ "gunicorn", # Django app server
687
+ "whitenoise", # Django static files
688
+ ]
689
+ djg_infra = [
690
+ "manage.py",
691
+ "wsgi.py",
692
+ "asgi.py",
693
+ "settings.py",
694
+ "settings/__init__.py",
695
+ "settings/base.py",
696
+ "settings/production.py",
697
+ "urls.py",
698
+ ]
699
+
700
+ return (
701
+ _has_import(ctx.imports, djg_imports)
702
+ or _has_dependency(ctx.deps, djg_deps)
703
+ or _has_infra_file(ctx.infra_files, djg_infra)
704
+ )
705
+
706
+
707
+ def _check_flask_hardening(*args) -> bool:
708
+ """Activate for Flask applications."""
709
+ ctx = _DomainContext.from_args(args)
710
+
711
+ flk_imports = [
712
+ "from flask", # the framework
713
+ "import flask",
714
+ "flask.Flask",
715
+ "flask.request",
716
+ "flask.session",
717
+ "flask.render_template",
718
+ "flask.render_template_string", # SSTI risk
719
+ "flask.redirect",
720
+ "flask.url_for",
721
+ "flask.abort",
722
+ "flask.send_file", # path traversal risk
723
+ "flask.send_from_directory",
724
+ "app.run(", # debug mode risk
725
+ "app.debug",
726
+ "app.config[", # config access (SECRET_KEY risk)
727
+ "cursor.execute", # SQL injection risk (raw SQL)
728
+ "db.session.execute", # SQLAlchemy raw SQL
729
+ ]
730
+ flk_deps = [
731
+ "flask",
732
+ "Flask",
733
+ "flask-wtf", # CSRF protection (positive signal)
734
+ "flask-sqlalchemy", # ORM
735
+ "flask-login", # auth
736
+ "flask-session", # server-side sessions
737
+ "flask-jwt-extended",
738
+ "flask-cors",
739
+ "flask-limiter",
740
+ "werkzeug", # Flask's WSGI lib (debugger RCE risk)
741
+ ]
742
+ flk_infra = [
743
+ "app.py",
744
+ "wsgi.py",
745
+ "asgi.py",
746
+ "flaskr/__init__.py",
747
+ "requirements.txt",
748
+ ]
749
+
750
+ return (
751
+ _has_import(ctx.imports, flk_imports)
752
+ or _has_dependency(ctx.deps, flk_deps)
753
+ or _has_infra_file(ctx.infra_files, flk_infra)
754
+ )
755
+
756
+
757
+ # ---------------------------------------------------------------------------
758
+ # Registry
759
+ # ---------------------------------------------------------------------------
760
+ # New domain checkers (2026-07-12)
761
+ # ---------------------------------------------------------------------------
762
+
763
+
764
+ def _check_cdn_cache_security(*args) -> bool:
765
+ """Activate for projects using a CDN/edge cache layer."""
766
+ ctx = _DomainContext.from_args(args)
767
+
768
+ cdn_infra = [
769
+ "cloudfront",
770
+ "cloudflare",
771
+ "fastly",
772
+ "akamai",
773
+ "cdn",
774
+ "edge",
775
+ "varnish",
776
+ "cloudflare",
777
+ ]
778
+ cdn_deps = ["cloudfront", "fastly", "cloudflare", "boto3"]
779
+ cdn_imports = ["cloudfront", "fastly", "cloudflare", "cdn"]
780
+
781
+ return (
782
+ _has_infra_file(ctx.infra_files, cdn_infra)
783
+ or _has_dependency(ctx.deps, cdn_deps)
784
+ or _has_import(ctx.imports, cdn_imports)
785
+ )
786
+
787
+
788
+ def _check_dns_security(*args) -> bool:
789
+ """Activate for projects managing DNS zones."""
790
+ ctx = _DomainContext.from_args(args)
791
+
792
+ dns_infra = ["route53", "dns", "zone-file", "named.conf", "cloudflare"]
793
+ dns_deps = ["dnspython", "dns", "ns1", "dyn", "route53", "boto3"]
794
+ dns_imports = ["dns", "route53", "ns1", "dnspython"]
795
+
796
+ return (
797
+ _has_infra_file(ctx.infra_files, dns_infra)
798
+ or _has_dependency(ctx.deps, dns_deps)
799
+ or _has_import(ctx.imports, dns_imports)
800
+ )
801
+
802
+
803
+ def _check_email_authentication(*args) -> bool:
804
+ """Activate for projects that send email."""
805
+ ctx = _DomainContext.from_args(args)
806
+
807
+ email_deps = [
808
+ "sendgrid",
809
+ "mailgun",
810
+ "ses",
811
+ "sparkpost",
812
+ "postmark",
813
+ "mailchimp",
814
+ "aiosmtplib",
815
+ "django.core.mail",
816
+ ]
817
+ email_imports = ["smtplib", "sendgrid", "ses", "mailgun", "email.mime", "aiosmtplib"]
818
+
819
+ return _has_dependency(ctx.deps, email_deps) or _has_import(ctx.imports, email_imports)
820
+
821
+
822
+ def _check_push_notification_security(*args) -> bool:
823
+ """Activate for projects using push notifications (FCM/APNs)."""
824
+ ctx = _DomainContext.from_args(args)
825
+
826
+ push_deps = ["firebase", "fcm", "apns", "pyfcm", "python-push-notify", "firebase-admin"]
827
+ push_imports = ["firebase_admin", "apns", "fcm", "firebase"]
828
+
829
+ return _has_dependency(ctx.deps, push_deps) or _has_import(ctx.imports, push_imports)
830
+
831
+
832
+ def _check_saml_sso_security(*args) -> bool:
833
+ """Activate for projects using SAML SSO."""
834
+ ctx = _DomainContext.from_args(args)
835
+
836
+ saml_deps = [
837
+ "pysaml2",
838
+ "onelogin",
839
+ "python3-saml",
840
+ "spring-security-saml2",
841
+ "saml2",
842
+ "leptoplast",
843
+ ]
844
+ saml_imports = ["saml", "SAML", "OneLogin", "saml2", "onelogin"]
845
+
846
+ return _has_dependency(ctx.deps, saml_deps) or _has_import(ctx.imports, saml_imports)
847
+
848
+
849
+ def _check_secrets_runtime_management(*args) -> bool:
850
+ """Activate for projects using runtime secrets backends."""
851
+ ctx = _DomainContext.from_args(args)
852
+
853
+ secrets_deps = [
854
+ "hvac",
855
+ "boto3",
856
+ "google-cloud-secret-manager",
857
+ "azure-keyvault",
858
+ "vault",
859
+ "aws-secretsmanager",
860
+ ]
861
+ secrets_imports = ["vault", "secretsmanager", "keyvault", "hvac", "google.cloud.secretmanager"]
862
+
863
+ return _has_dependency(ctx.deps, secrets_deps) or _has_import(ctx.imports, secrets_imports)
864
+
865
+
866
+ def _check_service_mesh_security(*args) -> bool:
867
+ """Activate for projects deployed on a service mesh (Istio/Linkerd)."""
868
+ ctx = _DomainContext.from_args(args)
869
+
870
+ mesh_infra = [
871
+ "istio",
872
+ "linkerd",
873
+ "virtualservice",
874
+ "destinationrule",
875
+ "peerauthoration",
876
+ "authorizationpolicy",
877
+ ]
878
+ mesh_deps = ["istio-client", "linkerd2"]
879
+ mesh_imports = ["istio", "linkerd"]
880
+
881
+ return (
882
+ _has_infra_file(ctx.infra_files, mesh_infra)
883
+ or _has_dependency(ctx.deps, mesh_deps)
884
+ or _has_import(ctx.imports, mesh_imports)
885
+ )
886
+
887
+
888
+ def _check_kubernetes_hardening(*args) -> bool:
889
+ """Activate for projects deployed on Kubernetes."""
890
+ ctx = _DomainContext.from_args(args)
891
+
892
+ k8s_infra = [
893
+ "deployment.yaml",
894
+ "service.yaml",
895
+ "statefulset.yaml",
896
+ "daemonset.yaml",
897
+ "role.yaml",
898
+ "clusterrole.yaml",
899
+ "networkpolicy.yaml",
900
+ "helmfile.yaml",
901
+ "chart.yaml",
902
+ "values.yaml",
903
+ "kustomization.yaml",
904
+ ]
905
+ k8s_imports = ["kubernetes", "kubectl", "kube", "helm"]
906
+ k8s_deps = ["kubernetes", "pykube", "lightkube", "helm"]
907
+
908
+ has_k8s_yaml = any(
909
+ "k8s" in f.lower() or "kubernetes" in f.lower() or "deploy" in f.lower()
910
+ for f in ctx.infra_files
911
+ )
912
+
913
+ return (
914
+ _has_infra_file(ctx.infra_files, k8s_infra)
915
+ or has_k8s_yaml
916
+ or _has_import(ctx.imports, k8s_imports)
917
+ or _has_dependency(ctx.deps, k8s_deps)
918
+ )
919
+
920
+
921
+ # ---------------------------------------------------------------------------
922
+ # Auto-generated checkers for 253 remaining domains (2026-08-06)
923
+ # ---------------------------------------------------------------------------
924
+ _GENERATED_CHECKERS: dict[str, _DomainCheckerFn] = {}
925
+ try:
926
+ import patchi.core.brain._generated_checkers as _gen_mod
927
+
928
+ for _name in dir(_gen_mod):
929
+ if _name.startswith("_check_"):
930
+ _fn = getattr(_gen_mod, _name)
931
+ if callable(_fn):
932
+ # Extract domain slug from function name: _check_foo_bar -> foo-bar
933
+ _slug = _name[7:].replace("_", "-")
934
+ _GENERATED_CHECKERS[_slug] = _fn
935
+ except ImportError:
936
+ pass
937
+
938
+
939
+ # ---------------------------------------------------------------------------
940
+ # New domain checkers — 27 additional technology-stack domains (2026-07-12)
941
+ # ---------------------------------------------------------------------------
942
+
943
+
944
+ def _check_access_control_authz(*args) -> bool:
945
+ """Activate for projects with role/permission checks, admin routes, or multi-tenant data."""
946
+ ctx = _DomainContext.from_args(args)
947
+
948
+ az_imports = [
949
+ "authorize",
950
+ "permission",
951
+ "has_role",
952
+ "has_permission",
953
+ "is_admin",
954
+ "check_access",
955
+ "rbac",
956
+ "acl",
957
+ "can_access",
958
+ "require_role",
959
+ "login_required",
960
+ "permission_required",
961
+ "@requires_permissions",
962
+ "CurrentPrincipal",
963
+ "SecurityContext",
964
+ "AuthorizationService",
965
+ "policy.enforce",
966
+ "guard.can",
967
+ "authz",
968
+ ]
969
+ az_deps = [
970
+ "casbin",
971
+ "pycasbin",
972
+ "rbac",
973
+ "accesscontrol",
974
+ "casbin-rs",
975
+ "spring-security",
976
+ "django-guardian",
977
+ "django-rules",
978
+ "pundit",
979
+ "cancancan",
980
+ "policy_machine",
981
+ ]
982
+
983
+ return (
984
+ _has_import(ctx.imports, az_imports)
985
+ or _has_dependency(ctx.deps, az_deps)
986
+ or _has_config_key(ctx.config_keys, ["rbac", "permissions", "roles", "authorization"])
987
+ )
988
+
989
+
990
+ def _check_agent_orchestration(*args) -> bool:
991
+ """Activate for multi-agent / agentic-loop frameworks (LangGraph, CrewAI, AutoGen)."""
992
+ ctx = _DomainContext.from_args(args)
993
+
994
+ agt_deps = [
995
+ "langgraph",
996
+ "crewai",
997
+ "autogen",
998
+ "openai-agents",
999
+ "swarm",
1000
+ "semantic-kernel",
1001
+ "langchain",
1002
+ "llamaindex",
1003
+ ]
1004
+ agt_imports = [
1005
+ "langgraph",
1006
+ "crewai",
1007
+ "autogen",
1008
+ "AgentExecutor",
1009
+ "ToolNode",
1010
+ "create_react_agent",
1011
+ "StateGraph",
1012
+ "AgentGroupChat",
1013
+ "openai.agents",
1014
+ "Swarm",
1015
+ ]
1016
+
1017
+ return _has_import(ctx.imports, agt_imports) or _has_dependency(ctx.deps, agt_deps)
1018
+
1019
+
1020
+ def _check_auth_session(*args) -> bool:
1021
+ """Activate for projects with session management, login, password handling, or JWT."""
1022
+ ctx = _DomainContext.from_args(args)
1023
+
1024
+ as_deps = [
1025
+ "express-session",
1026
+ "cookie-session",
1027
+ "passport",
1028
+ "next-auth",
1029
+ "flask-login",
1030
+ "flask-session",
1031
+ "django.contrib.sessions",
1032
+ "devise",
1033
+ "warden",
1034
+ "spring-security",
1035
+ "jsonwebtoken",
1036
+ "pyjwt",
1037
+ "jose",
1038
+ "ruby-jwt",
1039
+ "jjwt",
1040
+ "bcrypt",
1041
+ "argon2-cffi",
1042
+ "passlib",
1043
+ ]
1044
+ as_imports = [
1045
+ "login",
1046
+ "signin",
1047
+ "session.create",
1048
+ "session.destroy",
1049
+ "bcrypt",
1050
+ "argon2",
1051
+ "password_hash",
1052
+ "check_password",
1053
+ "jwt.verify",
1054
+ "jwt.decode",
1055
+ "jwt.sign",
1056
+ "passport.authenticate",
1057
+ "sessions.create",
1058
+ "LoginView",
1059
+ "LoginController",
1060
+ "authenticat",
1061
+ ]
1062
+
1063
+ return _has_import(ctx.imports, as_imports) or _has_dependency(ctx.deps, as_deps)
1064
+
1065
+
1066
+ def _check_cicd_pipeline(*args) -> bool:
1067
+ """Activate for repos with CI/CD configuration files."""
1068
+ ctx = _DomainContext.from_args(args)
1069
+
1070
+ ci_infra = [
1071
+ ".github/workflows",
1072
+ ".gitlab-ci.yml",
1073
+ "jenkinsfile",
1074
+ ".circleci/config.yml",
1075
+ "azure-pipelines.yml",
1076
+ "bitbucket-pipelines.yml",
1077
+ ".travis.yml",
1078
+ "buildkite.yml",
1079
+ "cloudbuild.yaml",
1080
+ ".drone.yml",
1081
+ "taskcluster.yml",
1082
+ ]
1083
+
1084
+ return _has_infra_file(ctx.infra_files, ci_infra)
1085
+
1086
+
1087
+ def _check_configuration_hardening(*args) -> bool:
1088
+ """Activate for projects with deployable server config or secrets usage."""
1089
+ ctx = _DomainContext.from_args(args)
1090
+
1091
+ ch_infra = [
1092
+ "nginx.conf",
1093
+ "apache2.conf",
1094
+ "httpd.conf",
1095
+ ".env",
1096
+ ".env.production",
1097
+ ".env.local",
1098
+ "config.yaml",
1099
+ "config.yml",
1100
+ "application.yml",
1101
+ "application.properties",
1102
+ ]
1103
+ ch_imports = [
1104
+ "os.environ",
1105
+ "process.env",
1106
+ "dotenv",
1107
+ "load_dotenv",
1108
+ "config.get",
1109
+ "configparser",
1110
+ "yaml.safe_load",
1111
+ "getenv",
1112
+ "environ",
1113
+ ]
1114
+
1115
+ return (
1116
+ _has_infra_file(ctx.infra_files, ch_infra)
1117
+ or _has_import(ctx.imports, ch_imports)
1118
+ or _has_config_key(
1119
+ ctx.config_keys, ["secret", "password", "api_key", "token", "credentials"]
1120
+ )
1121
+ )
1122
+
1123
+
1124
+ def _check_container_infra(*args) -> bool:
1125
+ """Activate for projects with Dockerfiles, docker-compose, or K8s manifests."""
1126
+ ctx = _DomainContext.from_args(args)
1127
+
1128
+ ci_infra = [
1129
+ "dockerfile",
1130
+ "docker-compose.yml",
1131
+ "docker-compose.yaml",
1132
+ "compose.yaml",
1133
+ "compose.yml",
1134
+ ".dockerignore",
1135
+ "deployment.yaml",
1136
+ "service.yaml",
1137
+ "statefulset.yaml",
1138
+ "daemonset.yaml",
1139
+ "chart.yaml",
1140
+ "values.yaml",
1141
+ "kustomization.yaml",
1142
+ "helmfile.yaml",
1143
+ ]
1144
+
1145
+ return _has_infra_file(ctx.infra_files, ci_infra) or _has_import(
1146
+ ctx.imports, ["docker", "kubernetes", "kubectl", "helm"]
1147
+ )
1148
+
1149
+
1150
+ def _check_data_layer(*args) -> bool:
1151
+ """Activate for projects using databases (drivers, ORMs, migrations)."""
1152
+ ctx = _DomainContext.from_args(args)
1153
+
1154
+ dl_deps = [
1155
+ "sqlalchemy",
1156
+ "psycopg2",
1157
+ "asyncpg",
1158
+ "mysqlclient",
1159
+ "pymysql",
1160
+ "pymongo",
1161
+ "motor",
1162
+ "django",
1163
+ "prisma",
1164
+ "typeorm",
1165
+ "sequelize",
1166
+ "knex",
1167
+ "sequelize",
1168
+ "pg",
1169
+ "mysql2",
1170
+ "better-sqlite3",
1171
+ "sqlite3",
1172
+ "alembic",
1173
+ "django.db",
1174
+ "drizzle-orm",
1175
+ ]
1176
+ dl_imports = [
1177
+ "sqlalchemy",
1178
+ "psycopg2",
1179
+ "asyncpg",
1180
+ "pymysql",
1181
+ "pymongo",
1182
+ "mongoose",
1183
+ "prisma",
1184
+ "typeorm",
1185
+ "sequelize",
1186
+ "knex",
1187
+ "cursor.execute",
1188
+ "db.session",
1189
+ "connection.execute",
1190
+ "create_engine",
1191
+ "sessionmaker",
1192
+ "Base.metadata",
1193
+ "migration",
1194
+ "alembic",
1195
+ "db:migrate",
1196
+ ]
1197
+
1198
+ return _has_import(ctx.imports, dl_imports) or _has_dependency(ctx.deps, dl_deps)
1199
+
1200
+
1201
+ def _check_data_protection_privacy(*args) -> bool:
1202
+ """Activate for projects handling PII, client-side storage, or caching layers."""
1203
+ ctx = _DomainContext.from_args(args)
1204
+
1205
+ dp_deps = [
1206
+ "cookie",
1207
+ "redis",
1208
+ "memcached",
1209
+ "localforage",
1210
+ "client-session",
1211
+ "express-session",
1212
+ ]
1213
+ dp_imports = [
1214
+ "localStorage",
1215
+ "sessionStorage",
1216
+ "indexedDB",
1217
+ "cookies.set",
1218
+ "cookies.get",
1219
+ "setCookie",
1220
+ "cache",
1221
+ "Cache-Control",
1222
+ "Vary",
1223
+ "pii",
1224
+ "personal_data",
1225
+ "gdpr",
1226
+ "ccpa",
1227
+ ]
1228
+
1229
+ return (
1230
+ _has_import(ctx.imports, dp_imports)
1231
+ or _has_dependency(ctx.deps, dp_deps)
1232
+ or _has_config_key(ctx.config_keys, ["privacy", "pii", "gdpr", "ccpa", "data_protection"])
1233
+ )
1234
+
1235
+
1236
+ def _check_desktop_app(*args) -> bool:
1237
+ """Activate for Electron desktop applications."""
1238
+ ctx = _DomainContext.from_args(args)
1239
+
1240
+ da_deps = ["electron", "electron-builder", "electron-forge", "electron-packager"]
1241
+ da_imports = [
1242
+ "BrowserWindow",
1243
+ "electron",
1244
+ "ipcMain",
1245
+ "ipcRenderer",
1246
+ "contextBridge",
1247
+ "webContents",
1248
+ "app.getPath",
1249
+ "shell.openExternal",
1250
+ ]
1251
+ da_infra = ["electron-builder.yml", "electron-builder.yaml", "forge.config.js"]
1252
+
1253
+ return (
1254
+ _has_import(ctx.imports, da_imports)
1255
+ or _has_dependency(ctx.deps, da_deps)
1256
+ or _has_infra_file(ctx.infra_files, da_infra)
1257
+ )
1258
+
1259
+
1260
+ def _check_file_handling(*args) -> bool:
1261
+ """Activate for projects with file upload/download or archive extraction."""
1262
+ ctx = _DomainContext.from_args(args)
1263
+
1264
+ fh_imports = [
1265
+ "multer",
1266
+ "busboy",
1267
+ "formidable",
1268
+ "multipart",
1269
+ "zip",
1270
+ "tar",
1271
+ "gunzip",
1272
+ "zlib",
1273
+ "adm-zip",
1274
+ "unzipper",
1275
+ "send_file",
1276
+ "sendFile",
1277
+ "Content-Disposition",
1278
+ "open(",
1279
+ "write(",
1280
+ "os.path.join",
1281
+ "path.join",
1282
+ ]
1283
+ fh_deps = [
1284
+ "multer",
1285
+ "busboy",
1286
+ "formidable",
1287
+ "archiver",
1288
+ "adm-zip",
1289
+ "node-tar",
1290
+ "unzipper",
1291
+ "yauzl",
1292
+ "yazl",
1293
+ ]
1294
+
1295
+ return _has_import(ctx.imports, fh_imports) or _has_dependency(ctx.deps, fh_deps)
1296
+
1297
+
1298
+ def _check_general_cryptography(*args) -> bool:
1299
+ """Activate for projects using crypto libraries or encryption."""
1300
+ ctx = _DomainContext.from_args(args)
1301
+
1302
+ gc_imports = [
1303
+ "crypto",
1304
+ "hashlib",
1305
+ "hmac",
1306
+ "cryptography",
1307
+ "openssl",
1308
+ "libsodium",
1309
+ "bcrypt",
1310
+ "argon2",
1311
+ "scrypt",
1312
+ "pbkdf2",
1313
+ "AES",
1314
+ "RSA",
1315
+ "ECDSA",
1316
+ "HMAC",
1317
+ "from cryptography",
1318
+ "import hashlib",
1319
+ "subtle.encrypt",
1320
+ "subtle.decrypt",
1321
+ "subtle.sign",
1322
+ ]
1323
+ gc_deps = [
1324
+ "cryptography",
1325
+ "pycryptodome",
1326
+ "libsodium",
1327
+ "bcrypt",
1328
+ "argon2-cffi",
1329
+ "node-forge",
1330
+ "sjcl",
1331
+ "noble",
1332
+ "@noble/hashes",
1333
+ ]
1334
+
1335
+ return _has_import(ctx.imports, gc_imports) or _has_dependency(ctx.deps, gc_deps)
1336
+
1337
+
1338
+ def _check_github_app_bot(*args) -> bool:
1339
+ """Activate for repos operating GitHub Apps or bots with automated PR/issue actions."""
1340
+ ctx = _DomainContext.from_args(args)
1341
+
1342
+ gh_deps = [
1343
+ "@octokit/auth-app",
1344
+ "@octokit/rest",
1345
+ "pygithub",
1346
+ "probot",
1347
+ "github-app",
1348
+ ]
1349
+ gh_imports = [
1350
+ "octokit",
1351
+ "App",
1352
+ "createAppAuth",
1353
+ "getInstallationAccessToken",
1354
+ "pull_request_target",
1355
+ "workflow_run",
1356
+ ]
1357
+ gh_infra = [".github/workflows"]
1358
+
1359
+ return (
1360
+ _has_import(ctx.imports, gh_imports)
1361
+ or _has_dependency(ctx.deps, gh_deps)
1362
+ or _has_infra_file(ctx.infra_files, gh_infra)
1363
+ )
1364
+
1365
+
1366
+ def _check_graphql_api_security(*args) -> bool:
1367
+ """Activate for projects exposing GraphQL endpoints."""
1368
+ ctx = _DomainContext.from_args(args)
1369
+
1370
+ gql_deps = [
1371
+ "graphql",
1372
+ "apollo-server",
1373
+ "@apollo/server",
1374
+ "graphene",
1375
+ "strawberry-graphql",
1376
+ "ariadne",
1377
+ "hasura",
1378
+ "type-graphql",
1379
+ ]
1380
+ gql_imports = [
1381
+ "GraphQLSchema",
1382
+ "graphql",
1383
+ "apollo-server",
1384
+ "gql",
1385
+ "strawberry",
1386
+ "ariadne",
1387
+ "buildSchema",
1388
+ "makeExecutableSchema",
1389
+ ]
1390
+ gql_infra = [".graphql", ".gql", "schema.graphql", "schema.gql"]
1391
+
1392
+ return (
1393
+ _has_import(ctx.imports, gql_imports)
1394
+ or _has_dependency(ctx.deps, gql_deps)
1395
+ or _has_infra_file(ctx.infra_files, gql_infra)
1396
+ )
1397
+
1398
+
1399
+ def _check_input_validation_business_logic(*args) -> bool:
1400
+ """Activate for projects with multi-step business flows or validation logic."""
1401
+ ctx = _DomainContext.from_args(args)
1402
+
1403
+ iv_deps = [
1404
+ "joi",
1405
+ "yup",
1406
+ "zod",
1407
+ "ajv",
1408
+ "class-validator",
1409
+ "marshmallow",
1410
+ "pydantic",
1411
+ "cerberus",
1412
+ "jsonschema",
1413
+ "express-validator",
1414
+ "drf-validation",
1415
+ "django.forms",
1416
+ ]
1417
+ iv_imports = [
1418
+ "validate",
1419
+ "sanitize",
1420
+ "whitelist",
1421
+ "blacklist",
1422
+ "InputValidator",
1423
+ "RequestValidator",
1424
+ "Schema.validate",
1425
+ "checkConstraint",
1426
+ "assertValid",
1427
+ ]
1428
+
1429
+ return _has_import(ctx.imports, iv_imports) or _has_dependency(ctx.deps, iv_deps)
1430
+
1431
+
1432
+ def _check_llm_integration(*args) -> bool:
1433
+ """Activate for projects using LLM SDKs, vector DBs, or prompt templates."""
1434
+ ctx = _DomainContext.from_args(args)
1435
+
1436
+ llm_deps = [
1437
+ "openai",
1438
+ "anthropic",
1439
+ "@anthropic-ai/sdk",
1440
+ "langchain",
1441
+ "llamaindex",
1442
+ "google-generativeai",
1443
+ "cohere-ai",
1444
+ "transformers",
1445
+ "pinecone-client",
1446
+ "chromadb",
1447
+ "weaviate-client",
1448
+ "qdrant-client",
1449
+ "pgvector",
1450
+ "faiss",
1451
+ ]
1452
+ llm_imports = [
1453
+ "openai",
1454
+ "anthropic",
1455
+ "ChatOpenAI",
1456
+ "OpenAIEmbeddings",
1457
+ "PineconeVectorStore",
1458
+ "Chroma",
1459
+ "FAISS",
1460
+ "generateText",
1461
+ "generateContent",
1462
+ "messages.create",
1463
+ ]
1464
+
1465
+ return _has_import(ctx.imports, llm_imports) or _has_dependency(ctx.deps, llm_deps)
1466
+
1467
+
1468
+ def _check_logging_error_handling(*args) -> bool:
1469
+ """Activate for projects with logging libraries and error handling middleware."""
1470
+ ctx = _DomainContext.from_args(args)
1471
+
1472
+ le_deps = [
1473
+ "winston",
1474
+ "pino",
1475
+ "bunyan",
1476
+ "log4j",
1477
+ "logback",
1478
+ "slog",
1479
+ "zap",
1480
+ "logrus",
1481
+ "structlog",
1482
+ "loguru",
1483
+ ]
1484
+ le_imports = [
1485
+ "logging",
1486
+ "logger",
1487
+ "log.error",
1488
+ "log.warn",
1489
+ "log.info",
1490
+ "winston.createLogger",
1491
+ "pino",
1492
+ "console.error",
1493
+ "errorHandler",
1494
+ "ErrorMiddleware",
1495
+ "onerror",
1496
+ "try:",
1497
+ "catch",
1498
+ "except",
1499
+ "rescue",
1500
+ ]
1501
+
1502
+ return _has_import(ctx.imports, le_imports) or _has_dependency(ctx.deps, le_deps)
1503
+
1504
+
1505
+ def _check_mcp_tool_surface(*args) -> bool:
1506
+ """Activate for projects implementing an MCP server (Model Context Protocol)."""
1507
+ ctx = _DomainContext.from_args(args)
1508
+
1509
+ mcp_deps = ["mcp", "@modelcontextprotocol/sdk"]
1510
+ mcp_imports = [
1511
+ "mcp.server",
1512
+ "MCPServer",
1513
+ "tools/list",
1514
+ "tools/call",
1515
+ "resources/list",
1516
+ "resources/read",
1517
+ "prompts/list",
1518
+ "ListToolsRequest",
1519
+ "CallToolRequest",
1520
+ "ServerSession",
1521
+ ]
1522
+
1523
+ return _has_import(ctx.imports, mcp_imports) or _has_dependency(ctx.deps, mcp_deps)
1524
+
1525
+
1526
+ def _check_message_queue_event_driven(*args) -> bool:
1527
+ """Activate for projects using message brokers (Kafka, RabbitMQ, SQS, Pub/Sub)."""
1528
+ ctx = _DomainContext.from_args(args)
1529
+
1530
+ mq_deps = [
1531
+ "kafkajs",
1532
+ "kafka-node",
1533
+ "kafka-python",
1534
+ "confluent-kafka",
1535
+ "amqplib",
1536
+ "pika",
1537
+ "rabbitmq",
1538
+ "celery",
1539
+ "boto3",
1540
+ "aws-sdk",
1541
+ "sqs",
1542
+ "sns",
1543
+ "google-cloud-pubsub",
1544
+ "nats",
1545
+ "bull",
1546
+ "bullmq",
1547
+ "redis",
1548
+ ]
1549
+ mq_imports = [
1550
+ "kafka",
1551
+ "KafkaJS",
1552
+ "Consumer",
1553
+ "Producer",
1554
+ "Broker",
1555
+ "pika",
1556
+ "amqplib",
1557
+ "celery",
1558
+ "SQS",
1559
+ "sqs",
1560
+ "SNS",
1561
+ "sns",
1562
+ "PubSub",
1563
+ "pubsub",
1564
+ "Subscriber",
1565
+ "nats.connect",
1566
+ "Bull",
1567
+ ]
1568
+
1569
+ return _has_import(ctx.imports, mq_imports) or _has_dependency(ctx.deps, mq_deps)
1570
+
1571
+
1572
+ def _check_mobile(*args) -> bool:
1573
+ """Activate for Android/iOS or cross-platform mobile (React Native/Flutter/Xamarin)."""
1574
+ ctx = _DomainContext.from_args(args)
1575
+
1576
+ mob_deps = [
1577
+ "react-native",
1578
+ "flutter",
1579
+ "xamarin",
1580
+ "ionic",
1581
+ "capacitor",
1582
+ "cordova",
1583
+ "expo",
1584
+ "@ionic/core",
1585
+ ]
1586
+ mob_imports = [
1587
+ "ReactNative",
1588
+ "react-native",
1589
+ "flutter",
1590
+ "dart:ui",
1591
+ "Xamarin.Forms",
1592
+ "Capacitor",
1593
+ "Ionic",
1594
+ ]
1595
+ mob_infra = [
1596
+ "androidmanifest.xml",
1597
+ "info.plist",
1598
+ "pubspec.yaml",
1599
+ "config.xml",
1600
+ "build.gradle",
1601
+ "app.json",
1602
+ "android/app",
1603
+ "ios/app",
1604
+ ]
1605
+
1606
+ return (
1607
+ _has_import(ctx.imports, mob_imports)
1608
+ or _has_dependency(ctx.deps, mob_deps)
1609
+ or _has_infra_file(ctx.infra_files, mob_infra)
1610
+ or ctx.has_mobile
1611
+ )
1612
+
1613
+
1614
+ def _check_oauth_oidc(*args) -> bool:
1615
+ """Activate for projects using OAuth 2.0 or OpenID Connect."""
1616
+ ctx = _DomainContext.from_args(args)
1617
+
1618
+ oa_deps = [
1619
+ "passport",
1620
+ "passport-oauth2",
1621
+ "next-auth",
1622
+ "authlib",
1623
+ "django-allauth",
1624
+ "social-auth-app",
1625
+ "omniauth",
1626
+ "spring-security-oauth2",
1627
+ "oidc-client",
1628
+ "oidc",
1629
+ ]
1630
+ oa_imports = [
1631
+ "oauth",
1632
+ "OAuth",
1633
+ "OIDC",
1634
+ "openid",
1635
+ "OpenID",
1636
+ "passport.authenticate",
1637
+ "token.exchange",
1638
+ "authorization_code",
1639
+ "client_credentials",
1640
+ "refresh_token",
1641
+ "access_token",
1642
+ "googleapis/auth",
1643
+ "auth0",
1644
+ ]
1645
+
1646
+ return _has_import(ctx.imports, oa_imports) or _has_dependency(ctx.deps, oa_deps)
1647
+
1648
+
1649
+ def _check_secure_coding_architecture(*args) -> bool:
1650
+ """Activate for projects with multi-threaded/async code and third-party deps."""
1651
+ ctx = _DomainContext.from_args(args)
1652
+
1653
+ sc_imports = [
1654
+ "threading",
1655
+ "multiprocessing",
1656
+ "concurrent.futures",
1657
+ "asyncio",
1658
+ "ThreadPoolExecutor",
1659
+ "ProcessPoolExecutor",
1660
+ "WorkerPool",
1661
+ "TaskGroup",
1662
+ "spawn",
1663
+ ]
1664
+ sc_deps = [
1665
+ "celery",
1666
+ "rq",
1667
+ "huey",
1668
+ "dramatiq",
1669
+ "gunicorn",
1670
+ "uvicorn",
1671
+ "hypercorn",
1672
+ ]
1673
+
1674
+ return (
1675
+ _has_import(ctx.imports, sc_imports)
1676
+ or _has_dependency(ctx.deps, sc_deps)
1677
+ or len(list(ctx.deps)) > 5
1678
+ )
1679
+
1680
+
1681
+ def _check_secure_communication_tls(*args) -> bool:
1682
+ """Activate for projects with HTTPS endpoints, mTLS, or TLS client connections."""
1683
+ ctx = _DomainContext.from_args(args)
1684
+
1685
+ tls_imports = [
1686
+ "ssl",
1687
+ "tls",
1688
+ "https",
1689
+ "mTLS",
1690
+ "certificate",
1691
+ "SSLContext",
1692
+ "create_default_context",
1693
+ "CERT_REQUIRED",
1694
+ "verify=True",
1695
+ "verify_ssl",
1696
+ ]
1697
+ tls_deps = [
1698
+ "pyopenssl",
1699
+ "trustme",
1700
+ "certifi",
1701
+ "ssl",
1702
+ ]
1703
+ tls_config = ["ssl", "tls", "https", "certificate", "cert"]
1704
+
1705
+ return (
1706
+ _has_import(ctx.imports, tls_imports)
1707
+ or _has_dependency(ctx.deps, tls_deps)
1708
+ or _has_config_key(ctx.config_keys, tls_config)
1709
+ )
1710
+
1711
+
1712
+ def _check_self_contained_tokens(*args) -> bool:
1713
+ """Activate for projects issuing or validating JWT/SAML tokens."""
1714
+ ctx = _DomainContext.from_args(args)
1715
+
1716
+ sct_deps = [
1717
+ "jsonwebtoken",
1718
+ "jose",
1719
+ "pyjwt",
1720
+ "ruby-jwt",
1721
+ "jjwt",
1722
+ "pysaml2",
1723
+ "python3-saml",
1724
+ "onelogin",
1725
+ ]
1726
+ sct_imports = [
1727
+ "jwt.sign",
1728
+ "jwt.verify",
1729
+ "jwt.decode",
1730
+ "JWS",
1731
+ "JWE",
1732
+ "JWK",
1733
+ "JWA",
1734
+ "saml2",
1735
+ "SAMLResponse",
1736
+ "Assertion",
1737
+ "RS256",
1738
+ "ES256",
1739
+ "HS256",
1740
+ ]
1741
+
1742
+ return _has_import(ctx.imports, sct_imports) or _has_dependency(ctx.deps, sct_deps)
1743
+
1744
+
1745
+ def _check_supply_chain_local_tool(*args) -> bool:
1746
+ """Activate for CLI tools with release pipelines or install scripts."""
1747
+ ctx = _DomainContext.from_args(args)
1748
+
1749
+ sc_infra = [
1750
+ "goreleaser.yml",
1751
+ ".goreleaser.yml",
1752
+ "release.yml",
1753
+ "install.sh",
1754
+ "install.ps1",
1755
+ ]
1756
+ has_bin = any("bin" in str(f).lower() for f in ctx.infra_files)
1757
+
1758
+ return _has_infra_file(ctx.infra_files, sc_infra) or has_bin or ctx.has_cli
1759
+
1760
+
1761
+ def _check_web_frontend(*args) -> bool:
1762
+ """Activate for browser-based frontends (React, Vue, Angular, Svelte)."""
1763
+ ctx = _DomainContext.from_args(args)
1764
+
1765
+ wf_deps = [
1766
+ "react",
1767
+ "react-dom",
1768
+ "vue",
1769
+ "@vue/cli",
1770
+ "@angular/core",
1771
+ "svelte",
1772
+ "solid-js",
1773
+ "preact",
1774
+ "lit",
1775
+ "ember-cli",
1776
+ ]
1777
+ wf_imports = [
1778
+ "ReactDOM",
1779
+ "createRoot",
1780
+ "Vue.createApp",
1781
+ "angular.module",
1782
+ "Component",
1783
+ "OnInit",
1784
+ "svelte.mount",
1785
+ "render",
1786
+ ]
1787
+ wf_infra = [
1788
+ "webpack.config",
1789
+ "vite.config",
1790
+ "rollup.config",
1791
+ "next.config",
1792
+ "nuxt.config",
1793
+ "angular.json",
1794
+ "tsconfig.json",
1795
+ "index.html",
1796
+ ]
1797
+
1798
+ return (
1799
+ _has_import(ctx.imports, wf_imports)
1800
+ or _has_dependency(ctx.deps, wf_deps)
1801
+ or _has_infra_file(ctx.infra_files, wf_infra)
1802
+ )
1803
+
1804
+
1805
+ def _check_webrtc_communication(*args) -> bool:
1806
+ """Activate for projects running TURN servers or WebRTC media/signaling."""
1807
+ ctx = _DomainContext.from_args(args)
1808
+
1809
+ wrtc_deps = [
1810
+ "simple-peer",
1811
+ "peerjs",
1812
+ "werift",
1813
+ "pion/webrtc",
1814
+ "mediasoup",
1815
+ "janus",
1816
+ "kurento",
1817
+ "coturn",
1818
+ ]
1819
+ wrtc_imports = [
1820
+ "RTCPeerConnection",
1821
+ "RTCSessionDescription",
1822
+ "webrtc",
1823
+ "PeerConnection",
1824
+ "MediaStream",
1825
+ "createOffer",
1826
+ "createAnswer",
1827
+ "addIceCandidate",
1828
+ ]
1829
+
1830
+ return _has_import(ctx.imports, wrtc_imports) or _has_dependency(ctx.deps, wrtc_deps)
1831
+
1832
+
1833
+ def _check_security_scanner_tool(*args) -> bool:
1834
+ """Activate for security scanner/analysis tools (SAST, DAST, SCA, agents)."""
1835
+ ctx = _DomainContext.from_args(args)
1836
+
1837
+ scanner_imports = [
1838
+ "scan",
1839
+ "detect",
1840
+ "finding",
1841
+ "vulnerability",
1842
+ "cve",
1843
+ "semgrep",
1844
+ "bandit",
1845
+ "safety",
1846
+ "trivy",
1847
+ "grype",
1848
+ "BaseAgent",
1849
+ "AgentInput",
1850
+ "AgentResult",
1851
+ "coordinator",
1852
+ "governor",
1853
+ "dispatcher",
1854
+ "domain_activator",
1855
+ "domain_loader",
1856
+ "security_probe",
1857
+ "security_config",
1858
+ ]
1859
+ scanner_deps = [
1860
+ "semgrep",
1861
+ "bandit",
1862
+ "safety",
1863
+ "trivy",
1864
+ "grype",
1865
+ "syft",
1866
+ "cosign",
1867
+ "checkov",
1868
+ "tfsec",
1869
+ "kics",
1870
+ ]
1871
+
1872
+ has_scanner_code = _has_import(ctx.imports, scanner_imports) or _has_dependency(
1873
+ ctx.deps, scanner_deps
1874
+ )
1875
+ has_scanner_structure = any(
1876
+ any(seg in f.lower() for seg in ["scan", "detect", "finding", "agent", "security"])
1877
+ for f in ctx.infra_files
1878
+ )
1879
+
1880
+ return has_scanner_code or has_scanner_structure
1881
+
1882
+
1883
+ def _check_websocket_security(*args) -> bool:
1884
+ """Activate for projects using WebSocket server or client libraries."""
1885
+ ctx = _DomainContext.from_args(args)
1886
+
1887
+ ws_deps = [
1888
+ "ws",
1889
+ "socket.io",
1890
+ "sockjs",
1891
+ "websocket",
1892
+ "channels",
1893
+ "django-channels",
1894
+ "signalr",
1895
+ ]
1896
+ ws_imports = [
1897
+ "WebSocket",
1898
+ "Socket.IO",
1899
+ "socket.io",
1900
+ "ws.Server",
1901
+ "socketio",
1902
+ "SignalR",
1903
+ "channels",
1904
+ "on('connection')",
1905
+ "wss://",
1906
+ ]
1907
+
1908
+ return _has_import(ctx.imports, ws_imports) or _has_dependency(ctx.deps, ws_deps)
1909
+
1910
+
1911
+ # ---------------------------------------------------------------------------
1912
+
1913
+ # Type alias for clarity
1914
+ _DomainCheckerFn = Callable[..., bool]
1915
+
1916
+ _DOMAIN_CHECKERS: dict[str, _DomainCheckerFn] = {
1917
+ # Original 13 technology-stack domains
1918
+ "native-code-safety": _check_native_code_safety,
1919
+ "go-concurrency": _check_go_concurrency,
1920
+ "jvm-hardening": _check_jvm_hardening,
1921
+ "mobile-native": _check_mobile_native,
1922
+ "ruby-rails": _check_ruby_rails,
1923
+ "svelte-ssr": _check_svelte_ssr,
1924
+ "cargo-supply-chain": _check_cargo_supply_chain,
1925
+ "nodejs-runtime": _check_nodejs_runtime,
1926
+ "express-web": _check_express_web,
1927
+ "nextjs-app": _check_nextjs_app,
1928
+ "python-runtime": _check_python_runtime,
1929
+ "django-hardening": _check_django_hardening,
1930
+ "flask-hardening": _check_flask_hardening,
1931
+ # 8 security-domain agents (added 2026-07-12)
1932
+ "cdn-cache-security": _check_cdn_cache_security,
1933
+ "dns-security": _check_dns_security,
1934
+ "email-authentication": _check_email_authentication,
1935
+ "push-notification-security": _check_push_notification_security,
1936
+ "saml-sso-security": _check_saml_sso_security,
1937
+ "secrets-runtime-management": _check_secrets_runtime_management,
1938
+ "service-mesh-security": _check_service_mesh_security,
1939
+ "kubernetes-hardening": _check_kubernetes_hardening,
1940
+ # 27 additional technology-stack domains (added 2026-07-12)
1941
+ "access-control-authz": _check_access_control_authz,
1942
+ "agent-orchestration": _check_agent_orchestration,
1943
+ "auth-session": _check_auth_session,
1944
+ "cicd-pipeline": _check_cicd_pipeline,
1945
+ "configuration-hardening": _check_configuration_hardening,
1946
+ "container-infra": _check_container_infra,
1947
+ "data-layer": _check_data_layer,
1948
+ "data-protection-privacy": _check_data_protection_privacy,
1949
+ "desktop-app": _check_desktop_app,
1950
+ "file-handling": _check_file_handling,
1951
+ "general-cryptography": _check_general_cryptography,
1952
+ "github-app-bot": _check_github_app_bot,
1953
+ "graphql-api-security": _check_graphql_api_security,
1954
+ "input-validation-business-logic": _check_input_validation_business_logic,
1955
+ "llm-integration": _check_llm_integration,
1956
+ "logging-error-handling": _check_logging_error_handling,
1957
+ "mcp-tool-surface": _check_mcp_tool_surface,
1958
+ "message-queue-event-driven": _check_message_queue_event_driven,
1959
+ "mobile": _check_mobile,
1960
+ "oauth-oidc": _check_oauth_oidc,
1961
+ "secure-coding-architecture": _check_secure_coding_architecture,
1962
+ "secure-communication-tls": _check_secure_communication_tls,
1963
+ "self-contained-tokens": _check_self_contained_tokens,
1964
+ "supply-chain-local-tool": _check_supply_chain_local_tool,
1965
+ "web-frontend": _check_web_frontend,
1966
+ "webrtc-communication": _check_webrtc_communication,
1967
+ "websocket-security": _check_websocket_security,
1968
+ "security-scanner-tool": _check_security_scanner_tool,
1969
+ }
1970
+
1971
+ # Merge auto-generated checkers (253 domains) — existing entries take precedence
1972
+ _DOMAIN_CHECKERS.update({k: v for k, v in _GENERATED_CHECKERS.items() if k not in _DOMAIN_CHECKERS})
1973
+
1974
+
1975
+ def activate_domains(
1976
+ args: Sequence,
1977
+ only: Iterable[str] | None = None,
1978
+ ) -> list[str]:
1979
+ """Return the sorted list of domain IDs that should activate for ``args``.
1980
+
1981
+ Parameters
1982
+ ----------
1983
+ args:
1984
+ The 11-element tuple as described in :class:`_DomainContext`.
1985
+ only:
1986
+ Optional iterable of domain IDs to restrict the check to. ``None``
1987
+ means check all registered domains.
1988
+ """
1989
+ candidate_ids = list(only) if only is not None else list(_DOMAIN_CHECKERS)
1990
+ activated: list[str] = []
1991
+ for domain_id in candidate_ids:
1992
+ checker = _DOMAIN_CHECKERS.get(domain_id)
1993
+ if checker is None:
1994
+ continue
1995
+ try:
1996
+ if checker(*args):
1997
+ activated.append(domain_id)
1998
+ except Exception as e:
1999
+ # A single checker failure must not block other domains.
2000
+ # Log to the patchi worklog in a production deployment.
2001
+ _log.warning("activate_domains failed: %s", e)
2002
+ continue
2003
+ return sorted(activated)
2004
+
2005
+
2006
+ # ---------------------------------------------------------------------------
2007
+ # Legacy compatibility layer
2008
+ # ---------------------------------------------------------------------------
2009
+
2010
+ _LANG_EXT_MAP: dict[str, str] = {
2011
+ ".py": "python",
2012
+ ".pyw": "python",
2013
+ ".pyi": "python",
2014
+ ".js": "javascript",
2015
+ ".jsx": "javascript",
2016
+ ".ts": "typescript",
2017
+ ".tsx": "typescript",
2018
+ ".java": "java",
2019
+ ".kt": "kotlin",
2020
+ ".kts": "kotlin",
2021
+ ".go": "golang",
2022
+ ".rs": "rust",
2023
+ ".c": "c",
2024
+ ".h": "c",
2025
+ ".cpp": "cpp",
2026
+ ".hpp": "cpp",
2027
+ ".rb": "ruby",
2028
+ ".swift": "swift",
2029
+ ".svelte": "svelte",
2030
+ ".vue": "vue",
2031
+ ".dart": "dart",
2032
+ }
2033
+
2034
+
2035
+ def _infer_primary_language(exts: Iterable[str]) -> str:
2036
+ """Guess the primary language from observed file extensions."""
2037
+ counts: dict[str, int] = {}
2038
+ for ext in exts:
2039
+ lang = _LANG_EXT_MAP.get(ext.lower())
2040
+ if lang:
2041
+ counts[lang] = counts.get(lang, 0) + 1
2042
+ if not counts:
2043
+ return ""
2044
+ return max(counts, key=counts.get)
2045
+
2046
+
2047
+ def _infer_app_type(
2048
+ active_domains: list[str],
2049
+ has_web: bool,
2050
+ has_cli: bool,
2051
+ has_mobile: bool,
2052
+ ) -> str:
2053
+ """Infer the project's application type from activated domains."""
2054
+ if has_mobile or "mobile" in active_domains or "mobile-native" in active_domains:
2055
+ return "mobile-app"
2056
+ if has_web:
2057
+ return "web-app"
2058
+ if has_cli:
2059
+ return "cli-tool"
2060
+ return "library"
2061
+
2062
+
2063
+ def _infer_deployment(active_domains: list[str], infra_files: list[str]) -> str:
2064
+ """Infer deployment model from activated domains and infra files."""
2065
+ if "container-infra" in active_domains:
2066
+ return "containerized"
2067
+ if "cicd-pipeline" in active_domains:
2068
+ return "pipeline"
2069
+ has_kubernetes = any("k8s" in f.lower() or "kubernetes" in f.lower() for f in infra_files)
2070
+ if has_kubernetes:
2071
+ return "kubernetes"
2072
+ has_docker = any("docker" in f.lower() for f in infra_files)
2073
+ if has_docker:
2074
+ return "docker"
2075
+ return "local"
2076
+
2077
+
2078
+ def build_project_context(
2079
+ file_infos: Any = None,
2080
+ detected_imports: set[str] | None = None,
2081
+ dependency_names: set[str] | None = None,
2082
+ route_paths: list[str] | None = None,
2083
+ config_keys: set[str] | None = None,
2084
+ infrastructure_files: list[str] | None = None,
2085
+ has_web_framework: bool = False,
2086
+ has_cli_framework: bool = False,
2087
+ has_mobile_code: bool = False,
2088
+ file_extensions: set[str] | None = None,
2089
+ **kwargs: Any,
2090
+ ) -> dict[str, Any]:
2091
+ """Build a rich project context dict with discovered info and active domains.
2092
+
2093
+ Legacy compatibility wrapper around :func:`activate_domains`.
2094
+ Accepts the same keyword parameters as the original
2095
+ ``patchi.core.brain.domain_activator.build_project_context``.
2096
+ """
2097
+ exts = sorted(file_extensions or [])
2098
+ args = [
2099
+ _infer_primary_language(exts), # language
2100
+ "", # framework (inferred elsewhere)
2101
+ list(detected_imports or []), # imports
2102
+ list(dependency_names or []), # deps
2103
+ list(route_paths or []), # routes
2104
+ list(config_keys or []), # config_keys
2105
+ list(infrastructure_files or []), # infra_files
2106
+ bool(has_web_framework), # has_web
2107
+ bool(has_cli_framework), # has_cli
2108
+ bool(has_mobile_code), # has_mobile
2109
+ exts, # exts
2110
+ ]
2111
+ active = activate_domains(args)
2112
+
2113
+ context: dict[str, Any] = {
2114
+ "relevant_domains": active,
2115
+ "app_type": _infer_app_type(active, has_web_framework, has_cli_framework, has_mobile_code),
2116
+ "deployment_model": _infer_deployment(active, infrastructure_files or []),
2117
+ "has_user_auth": "auth-session" in active or "oauth-oidc" in active,
2118
+ "active_domain_count": len(active),
2119
+ }
2120
+ return context
2121
+
2122
+
2123
+ __all__ = [
2124
+ "_DomainContext",
2125
+ "_DOMAIN_CHECKERS",
2126
+ "_has_import",
2127
+ "_has_dependency",
2128
+ "_has_ext",
2129
+ "_has_infra_file",
2130
+ "_has_config_key",
2131
+ "activate_domains",
2132
+ # Original 13
2133
+ "_check_native_code_safety",
2134
+ "_check_go_concurrency",
2135
+ "_check_jvm_hardening",
2136
+ "_check_mobile_native",
2137
+ "_check_ruby_rails",
2138
+ "_check_svelte_ssr",
2139
+ "_check_cargo_supply_chain",
2140
+ "_check_nodejs_runtime",
2141
+ "_check_express_web",
2142
+ "_check_nextjs_app",
2143
+ "_check_python_runtime",
2144
+ "_check_django_hardening",
2145
+ "_check_flask_hardening",
2146
+ # 8 security-domain agents
2147
+ "_check_cdn_cache_security",
2148
+ "_check_dns_security",
2149
+ "_check_email_authentication",
2150
+ "_check_push_notification_security",
2151
+ "_check_saml_sso_security",
2152
+ "_check_secrets_runtime_management",
2153
+ "_check_service_mesh_security",
2154
+ "_check_kubernetes_hardening",
2155
+ # 27 additional technology-stack domains
2156
+ "_check_access_control_authz",
2157
+ "_check_agent_orchestration",
2158
+ "_check_auth_session",
2159
+ "_check_cicd_pipeline",
2160
+ "_check_configuration_hardening",
2161
+ "_check_container_infra",
2162
+ "_check_data_layer",
2163
+ "_check_data_protection_privacy",
2164
+ "_check_desktop_app",
2165
+ "_check_file_handling",
2166
+ "_check_general_cryptography",
2167
+ "_check_github_app_bot",
2168
+ "_check_graphql_api_security",
2169
+ "_check_input_validation_business_logic",
2170
+ "_check_llm_integration",
2171
+ "_check_logging_error_handling",
2172
+ "_check_mcp_tool_surface",
2173
+ "_check_message_queue_event_driven",
2174
+ "_check_mobile",
2175
+ "_check_oauth_oidc",
2176
+ "_check_secure_coding_architecture",
2177
+ "_check_secure_communication_tls",
2178
+ "_check_self_contained_tokens",
2179
+ "_check_supply_chain_local_tool",
2180
+ "_check_web_frontend",
2181
+ "_check_webrtc_communication",
2182
+ "_check_websocket_security",
2183
+ ]