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,319 @@
1
+ """
2
+ InjectionAgent — SQLi, XSS, command injection, path traversal.
3
+
4
+ Detects injection vulnerabilities:
5
+ - SQL injection
6
+ - Cross-site scripting (XSS)
7
+ - Command injection
8
+ - Path traversal
9
+ - LDAP injection
10
+ - XPath injection
11
+ - Expression language injection
12
+
13
+ Uses AST analysis via ast_utils for function-call patterns;
14
+ regex for literal text patterns (path traversal, XSS assignments).
15
+ Does NOT call AI.
16
+ Does NOT write to disk.
17
+ Does NOT touch the queue.
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ from pathlib import Path
23
+
24
+ from ..agents.base import (
25
+ AgentDomain,
26
+ AgentGroup,
27
+ AgentInput,
28
+ AgentResult,
29
+ AgentStatus,
30
+ BaseAgent,
31
+ Finding,
32
+ Severity,
33
+ make_finding,
34
+ register,
35
+ safe_rglob,
36
+ )
37
+ from ..brain.ast_utils import (
38
+ EXEC_SINKS,
39
+ SQL_SINKS,
40
+ find_assignments,
41
+ find_calls,
42
+ get_call_arg,
43
+ )
44
+ from ..brain.languages import Lang, detect_language
45
+ from ..brain.trace_log import trace_agent
46
+ from .pattern_loader import get_injection_sinks, load_patterns
47
+
48
+ # ── Helpers ────────────────────────────────────────────────────────────────────
49
+
50
+ # Load sinks from YAML config once at module level
51
+ _PATTERNS = load_patterns()
52
+ _INJ_SINKS = get_injection_sinks(_PATTERNS)
53
+
54
+
55
+ def _has_concat(arg_text: str, lang: Lang | None = None) -> bool:
56
+ """Check if an argument expression contains string concatenation/interpolation."""
57
+ if not arg_text:
58
+ return False
59
+ if "+" in arg_text:
60
+ return True
61
+ if arg_text.startswith("f") or "{" in arg_text:
62
+ return True
63
+ # PHP uses "." (not "+") for concatenation
64
+ if lang == Lang.PHP and "." in arg_text:
65
+ return True
66
+ return False
67
+
68
+
69
+ # ── Agent ──────────────────────────────────────────────────────────────────────
70
+
71
+
72
+ @register
73
+ class InjectionAgent(BaseAgent):
74
+ """Agent for detecting injection vulnerabilities."""
75
+
76
+ group = AgentGroup.SECURITY
77
+ domain = AgentDomain.SECURITY
78
+ name = "InjectionAgent"
79
+ description = "SQLi, XSS, command injection, path traversal, LDAP, XPath"
80
+
81
+ def _run(self, inp: AgentInput, result: AgentResult) -> None:
82
+ findings = []
83
+ source_patterns = [
84
+ "*.py",
85
+ "*.js",
86
+ "*.jsx",
87
+ "*.ts",
88
+ "*.tsx",
89
+ "*.java",
90
+ "*.php",
91
+ "*.rb",
92
+ "*.go",
93
+ "*.rs",
94
+ "*.cpp",
95
+ "*.cxx",
96
+ "*.cc",
97
+ "*.c",
98
+ "*.h",
99
+ "*.hpp",
100
+ "*.cs",
101
+ ]
102
+ with trace_agent(self.name, inp.root) as trace:
103
+ for pattern in source_patterns:
104
+ for file_path in safe_rglob(inp.root, pattern):
105
+ if file_path.is_file():
106
+ rel_path = file_path.relative_to(inp.root).as_posix()
107
+ if not self._should_skip_file(rel_path, inp):
108
+ findings.extend(self._scan_file_injection(file_path, rel_path))
109
+ trace.findings = len(findings)
110
+ trace.files_scanned = len(list(inp.root.rglob("*.py")))
111
+ result.status = AgentStatus.SUCCEEDED
112
+ result.findings = findings
113
+ result.data.update(
114
+ {
115
+ "injection_findings": len([f for f in findings if "injection" in f.title.lower()]),
116
+ "needs_ai": False,
117
+ }
118
+ )
119
+ return
120
+
121
+ def _should_skip_file(self, file_path: str, inp: AgentInput) -> bool:
122
+ from pathlib import PurePosixPath
123
+
124
+ restrictions = inp.config.get("restrictions", [])
125
+ for r in restrictions:
126
+ if r.get("enabled", True):
127
+ path = r["path"]
128
+ if file_path.startswith(path) or PurePosixPath(file_path).match(path):
129
+ if r["type"] == "NO_TOUCH":
130
+ return True
131
+ if r["type"] == "SCAN_ONLY" and self.__class__.__name__ == "FixAgent":
132
+ return True
133
+ return False
134
+
135
+ def _scan_file_injection(self, file_path: Path, rel_path: str) -> list[Finding]:
136
+ findings = []
137
+ try:
138
+ content = file_path.read_text(encoding="utf-8")
139
+ lang = detect_language(file_path)
140
+ findings.extend(self._check_sql_injection(content, lang, rel_path))
141
+ findings.extend(self._check_command_injection(content, lang, rel_path))
142
+ findings.extend(self._check_xss(content, lang, rel_path))
143
+ findings.extend(self._check_path_traversal(content, lang, rel_path))
144
+ except Exception as e:
145
+ findings.append(
146
+ make_finding(
147
+ severity=Severity.LOW,
148
+ file=rel_path,
149
+ line_start=0,
150
+ title="Injection scanner file read error",
151
+ description=f"Could not analyze {file_path.name} for injection issues: {str(e)}",
152
+ evidence=str(e),
153
+ )
154
+ )
155
+ return findings
156
+
157
+ # ── SQL injection ──────────────────────────────────────────────────────────
158
+
159
+ def _check_sql_injection(self, content: str, lang: Lang, rel_path: str) -> list[Finding]:
160
+ findings = []
161
+ lang_key = lang.value
162
+ yaml_sinks = set(_INJ_SINKS.get("sql_sinks", {}).get(lang_key, []))
163
+ sinks = SQL_SINKS | yaml_sinks
164
+
165
+ for call in find_calls(content, lang, sinks):
166
+ arg = get_call_arg(call["full_text"])
167
+ if arg and _has_concat(arg, lang):
168
+ findings.append(
169
+ make_finding(
170
+ severity=Severity.CRITICAL,
171
+ file=rel_path,
172
+ line_start=call["line"],
173
+ title="SQL Injection",
174
+ description="SQL query constructed with string concatenation",
175
+ evidence=call["full_text"][:100],
176
+ )
177
+ )
178
+ return findings
179
+
180
+ # ── Command injection ──────────────────────────────────────────────────────
181
+
182
+ def _check_command_injection(self, content: str, lang: Lang, rel_path: str) -> list[Finding]:
183
+ findings = []
184
+ lang_key = lang.value
185
+ yaml_exec = set(_INJ_SINKS.get("exec_sinks", {}).get(lang_key, []))
186
+ sinks = EXEC_SINKS | yaml_exec
187
+ for call in find_calls(content, lang, sinks):
188
+ arg = get_call_arg(call["full_text"])
189
+ if arg and _has_concat(arg, lang):
190
+ findings.append(
191
+ make_finding(
192
+ severity=Severity.CRITICAL,
193
+ file=rel_path,
194
+ line_start=call["line"],
195
+ title="Command Injection",
196
+ description="External command executed with dynamic input",
197
+ evidence=call["full_text"][:100],
198
+ )
199
+ )
200
+ return findings
201
+
202
+ # ── Cross-site scripting ───────────────────────────────────────────────────
203
+
204
+ _XSS_CALL_SINKS: dict[Lang, set[str]] = {}
205
+ _XSS_ASSIGN_SINKS: dict[Lang, set[str]] = {}
206
+
207
+ @classmethod
208
+ def _get_xss_sinks(cls):
209
+ """Build XSS sinks from YAML + static defaults, cached on class."""
210
+ if cls._XSS_CALL_SINKS:
211
+ return cls._XSS_CALL_SINKS, cls._XSS_ASSIGN_SINKS
212
+ # Static defaults
213
+ cls._XSS_CALL_SINKS = {
214
+ Lang.JAVASCRIPT: {"document.write", "document.writeln"},
215
+ Lang.TYPESCRIPT: {"document.write", "document.writeln"},
216
+ Lang.PHP: {"echo", "print"},
217
+ }
218
+ cls._XSS_ASSIGN_SINKS = {
219
+ Lang.JAVASCRIPT: {"innerHTML", "outerHTML"},
220
+ Lang.TYPESCRIPT: {"innerHTML", "outerHTML"},
221
+ }
222
+ # Merge YAML overrides
223
+ yaml_xss_calls = _INJ_SINKS.get("xss_call_sinks", {})
224
+ yaml_xss_assigns = _INJ_SINKS.get("xss_assign_sinks", {})
225
+ for lang_key, funcs in yaml_xss_calls.items():
226
+ try:
227
+ lang = Lang(lang_key)
228
+ except ValueError:
229
+ continue
230
+ cls._XSS_CALL_SINKS.setdefault(lang, set()).update(funcs)
231
+ for lang_key, attrs in yaml_xss_assigns.items():
232
+ try:
233
+ lang = Lang(lang_key)
234
+ except ValueError:
235
+ continue
236
+ cls._XSS_ASSIGN_SINKS.setdefault(lang, set()).update(attrs)
237
+ return cls._XSS_CALL_SINKS, cls._XSS_ASSIGN_SINKS
238
+
239
+ def _check_xss(self, content: str, lang: Lang, rel_path: str) -> list[Finding]:
240
+ findings = []
241
+ call_sinks, assign_sinks = self._get_xss_sinks()
242
+
243
+ langs_call = call_sinks.get(lang, set())
244
+ if langs_call:
245
+ for call in find_calls(content, lang, langs_call):
246
+ arg = get_call_arg(call["full_text"])
247
+ if arg and _has_concat(arg, lang):
248
+ findings.append(
249
+ make_finding(
250
+ severity=Severity.HIGH,
251
+ file=rel_path,
252
+ line_start=call["line"],
253
+ title="Cross-Site Scripting (XSS)",
254
+ description="Dynamic content inserted without sanitization",
255
+ evidence=call["full_text"][:120],
256
+ )
257
+ )
258
+
259
+ langs_assign = assign_sinks.get(lang, set())
260
+ if langs_assign:
261
+ for assign in find_assignments(content, lang):
262
+ if assign["target"] in langs_assign and _has_concat(assign["value"], lang):
263
+ findings.append(
264
+ make_finding(
265
+ severity=Severity.HIGH,
266
+ file=rel_path,
267
+ line_start=assign["line"],
268
+ title="Cross-Site Scripting (XSS)",
269
+ description="Dynamic content assigned to DOM sink without sanitization",
270
+ evidence=assign["full_text"][:120],
271
+ )
272
+ )
273
+
274
+ return findings
275
+
276
+ # ── Path traversal ─────────────────────────────────────────────────────────
277
+
278
+ _PATH_TRAVERSAL_SINKS: dict[Lang, set[str]] = {}
279
+
280
+ @classmethod
281
+ def _get_path_traversal_sinks(cls):
282
+ """Build path traversal sinks from YAML + static defaults."""
283
+ if cls._PATH_TRAVERSAL_SINKS:
284
+ return cls._PATH_TRAVERSAL_SINKS
285
+ cls._PATH_TRAVERSAL_SINKS = {
286
+ Lang.PYTHON: {"open", "os.path.join"},
287
+ Lang.JAVASCRIPT: {"fs.readFile", "fs.readFileSync", "path.join"},
288
+ Lang.TYPESCRIPT: {"fs.readFile", "fs.readFileSync", "path.join"},
289
+ Lang.PHP: {"file_get_contents", "fopen", "include", "require"},
290
+ }
291
+ yaml_pts = _INJ_SINKS.get("path_traversal_sinks", {})
292
+ for lang_key, funcs in yaml_pts.items():
293
+ try:
294
+ lang = Lang(lang_key)
295
+ except ValueError:
296
+ continue
297
+ cls._PATH_TRAVERSAL_SINKS.setdefault(lang, set()).update(funcs)
298
+ return cls._PATH_TRAVERSAL_SINKS
299
+
300
+ def _check_path_traversal(self, content: str, lang: Lang, rel_path: str) -> list[Finding]:
301
+ findings = []
302
+
303
+ sinks = self._get_path_traversal_sinks().get(lang, set())
304
+ if sinks:
305
+ for call in find_calls(content, lang, sinks):
306
+ arg = get_call_arg(call["full_text"])
307
+ if arg and _has_concat(arg, lang):
308
+ findings.append(
309
+ make_finding(
310
+ severity=Severity.HIGH,
311
+ file=rel_path,
312
+ line_start=call["line"],
313
+ title="Path Traversal",
314
+ description="File path constructed with dynamic input",
315
+ evidence=call["full_text"][:120],
316
+ )
317
+ )
318
+
319
+ return findings
@@ -0,0 +1,288 @@
1
+ """
2
+ InsecureRandomnessAgent — Predictable random number generation.
3
+
4
+ Detects use of non-cryptographic random number generators in security-sensitive
5
+ contexts (tokens, passwords, keys, sessions, CSRF, etc.) and recommends
6
+ cryptographically secure alternatives.
7
+
8
+ Covers all 11 languages:
9
+ - JS/TS: Math.random(), Math.floor(Math.random()
10
+ - Python: random.random(), random.randint(), random.choice(), random.choices()
11
+ - Java: java.util.Random, Math.random()
12
+ - Go: math/rand
13
+ - Rust: rand::random, rand::thread_rng
14
+ - C/C++: rand(), srand()
15
+ - Ruby: rand, Random.new
16
+ - Swift: Int.random, Double.random, GKRandomSource
17
+
18
+ Uses tree-sitter AST + Python `ast` for call/import detection (no regex).
19
+ Does NOT call AI.
20
+ Does NOT write to disk.
21
+ Does NOT touch the queue.
22
+ """
23
+
24
+ from __future__ import annotations
25
+
26
+ from pathlib import Path
27
+
28
+ from patchi.core.brain.ast_utils import find_calls, find_imports
29
+ from patchi.core.brain.languages import EXTENSION_MAP, Lang
30
+
31
+ from ..agents.base import (
32
+ AgentDomain,
33
+ AgentGroup,
34
+ AgentInput,
35
+ AgentResult,
36
+ AgentStatus,
37
+ BaseAgent,
38
+ Finding,
39
+ Severity,
40
+ make_finding,
41
+ register,
42
+ safe_rglob,
43
+ )
44
+
45
+
46
+ @register
47
+ class InsecureRandomnessAgent(BaseAgent):
48
+ """Agent for detecting insecure random number generation."""
49
+
50
+ group = AgentGroup.SECURITY
51
+ domain = AgentDomain.SECURITY
52
+ name = "InsecureRandomnessAgent"
53
+ description = "Predictable RNG in security-sensitive contexts (tokens, passwords, keys)"
54
+
55
+ # Security-sensitive keywords that elevate RNG usage from INFO to HIGH
56
+ SENSITIVE_CONTEXT_KEYWORDS = [
57
+ "token",
58
+ "password",
59
+ "secret",
60
+ "key",
61
+ "session",
62
+ "csrf",
63
+ "nonce",
64
+ "otp",
65
+ "auth",
66
+ "reset",
67
+ "salt",
68
+ "iv",
69
+ "challenge",
70
+ "jwt",
71
+ "api_key",
72
+ "apikey",
73
+ "access_token",
74
+ "refresh_token",
75
+ "verification",
76
+ "recovery",
77
+ "mfa",
78
+ "2fa",
79
+ "tfa",
80
+ ]
81
+
82
+ # Per-language insecure RNG call sets (matched by leaf name)
83
+ RNG_CALLS: dict[Lang, set[str]] = {
84
+ Lang.JAVASCRIPT: {"Math.random", "Math.floor"},
85
+ Lang.TYPESCRIPT: {"Math.random", "Math.floor"},
86
+ Lang.PYTHON: {
87
+ "random.random",
88
+ "random.randint",
89
+ "random.choice",
90
+ "random.choices",
91
+ "random.uniform",
92
+ "random.shuffle",
93
+ "random.sample",
94
+ "random.randrange",
95
+ "random.getrandbits",
96
+ "random.getstate",
97
+ "random.seed",
98
+ },
99
+ Lang.JAVA: {"Random", "Math.random"},
100
+ Lang.GO: {
101
+ "rand.Intn",
102
+ "rand.Int",
103
+ "rand.Float64",
104
+ "rand.Perm",
105
+ "rand.Shuffle",
106
+ "rand.Seed",
107
+ },
108
+ Lang.RUST: {"rand::random", "rand::thread_rng", "rand::Rng.gen", "rand::Rng.gen_range"},
109
+ Lang.C: {"rand", "srand"},
110
+ Lang.CPP: {"rand", "srand"},
111
+ Lang.RUBY: {"rand", "Random.new", "Random.rand"},
112
+ Lang.SWIFT: {
113
+ "Int.random",
114
+ "Double.random",
115
+ "CGFloat.random",
116
+ "randomElement",
117
+ "GKRandomSource",
118
+ },
119
+ Lang.PHP: {"mt_rand", "rand", "array_rand"},
120
+ Lang.KOTLIN: {"Random.nextInt", "Random.nextDouble", "Random.nextBoolean"},
121
+ Lang.C_SHARP: {"Random.Next", "Random.NextDouble", "Random.NextBytes"},
122
+ }
123
+
124
+ # Import / module patterns that indicate insecure RNG usage
125
+ RNG_IMPORTS: dict[Lang, set[str]] = {
126
+ Lang.PYTHON: {"random"},
127
+ Lang.JAVA: {"java.util.Random"},
128
+ Lang.GO: {"math/rand"},
129
+ Lang.RUST: {"rand"},
130
+ Lang.KOTLIN: {"kotlin.random.Random"},
131
+ }
132
+
133
+ # Secure alternative suggestions per language
134
+ SECURE_ALTERNATIVES: dict[str, str] = {
135
+ "JavaScript": "crypto.randomBytes() or crypto.randomUUID() (Node.js) / window.crypto.getRandomValues() (browser)",
136
+ "Python": "secrets.token_bytes(), secrets.token_hex(), secrets.choice(), or os.urandom()",
137
+ "Java": "java.security.SecureRandom",
138
+ "Go": "crypto/rand.Read()",
139
+ "Rust": "rand::rngs::OsRng or getrandom::getrandom()",
140
+ "C/C++": "getrandom() (Linux) / BCryptGenRandom() (Windows) / arc4random() (BSD)",
141
+ "Ruby": "SecureRandom.hex(), SecureRandom.urlsafe_base64(), or SecureRandom.random_bytes()",
142
+ "Swift": "SecRandomCopyBytes() or CryptoKit's SecureRandom",
143
+ }
144
+
145
+ def _run(self, inp: AgentInput, result: AgentResult) -> None:
146
+ findings = []
147
+
148
+ source_patterns = [
149
+ "*.py",
150
+ "*.js",
151
+ "*.jsx",
152
+ "*.ts",
153
+ "*.tsx",
154
+ "*.java",
155
+ "*.go",
156
+ "*.rs",
157
+ "*.c",
158
+ "*.h",
159
+ "*.cpp",
160
+ "*.cxx",
161
+ "*.cc",
162
+ "*.hpp",
163
+ "*.rb",
164
+ "*.swift",
165
+ "*.php",
166
+ "*.kt",
167
+ "*.cs",
168
+ ]
169
+
170
+ for pattern in source_patterns:
171
+ for file_path in safe_rglob(inp.root, pattern):
172
+ if file_path.is_file():
173
+ rel_path = file_path.relative_to(inp.root).as_posix()
174
+ if not self._should_skip_file(rel_path, inp):
175
+ findings.extend(self._scan_file_rng(file_path, rel_path))
176
+
177
+ result.status = AgentStatus.SUCCEEDED
178
+ result.findings = findings
179
+ result.data.update(
180
+ {
181
+ "rng_findings": len(findings),
182
+ "needs_ai": False,
183
+ }
184
+ )
185
+ return
186
+
187
+ def _should_skip_file(self, file_path: str, inp: AgentInput) -> bool:
188
+ from pathlib import PurePosixPath
189
+
190
+ restrictions = inp.config.get("restrictions", [])
191
+ for r in restrictions:
192
+ if r.get("enabled", True):
193
+ path = r["path"]
194
+ if file_path.startswith(path) or PurePosixPath(file_path).match(path):
195
+ if r["type"] == "NO_TOUCH":
196
+ return True
197
+ if r["type"] == "SCAN_ONLY" and self.__class__.__name__ == "FixAgent":
198
+ return True
199
+ return False
200
+
201
+ def _scan_file_rng(self, file_path: Path, rel_path: str) -> list[Finding]:
202
+ findings = []
203
+ try:
204
+ content = file_path.read_text(encoding="utf-8")
205
+ lang = EXTENSION_MAP.get(file_path.suffix.lower())
206
+ if lang is None:
207
+ return findings
208
+
209
+ lines = content.splitlines()
210
+ call_set = self.RNG_CALLS.get(lang, set())
211
+ import_set = self.RNG_IMPORTS.get(lang, set())
212
+
213
+ # 1. Call-based detection (imports of the rand module count as INFO)
214
+ if call_set:
215
+ for call in find_calls(content, lang, call_set):
216
+ line_num = call["line"] or 1
217
+ line = lines[line_num - 1] if 0 < line_num <= len(lines) else ""
218
+ self._add_call_finding(findings, rel_path, line_num, line, lang, call["name"])
219
+
220
+ # 2. Import-based detection (e.g. `import random`, `math/rand`)
221
+ if import_set:
222
+ for imp in find_imports(content, lang, import_set):
223
+ line_num = imp["line"] or 1
224
+ line = lines[line_num - 1] if 0 < line_num <= len(lines) else ""
225
+ is_sensitive = any(kw in line.lower() for kw in self.SENSITIVE_CONTEXT_KEYWORDS)
226
+ secure_alt = self.SECURE_ALTERNATIVES.get(
227
+ lang.value if hasattr(lang, "value") else str(lang),
228
+ "a cryptographically secure alternative",
229
+ )
230
+ findings.append(
231
+ make_finding(
232
+ severity=Severity.HIGH if is_sensitive else Severity.INFO,
233
+ file=rel_path,
234
+ line_start=line_num,
235
+ title=f"Insecure RNG import ({str(lang)})",
236
+ description=(
237
+ f"Non-cryptographic random module imported"
238
+ f"{' in a security-sensitive context' if is_sensitive else ''}. "
239
+ f"Use {secure_alt} instead."
240
+ ),
241
+ evidence=line.strip(),
242
+ cwe="CWE-338" if is_sensitive else "CWE-330",
243
+ )
244
+ )
245
+
246
+ except Exception as e:
247
+ findings.append(
248
+ make_finding(
249
+ severity=Severity.LOW,
250
+ file=rel_path,
251
+ line_start=0,
252
+ title="RNG scanner file read error",
253
+ description=f"Could not analyze {file_path.name}: {e}",
254
+ evidence=str(e),
255
+ )
256
+ )
257
+
258
+ return findings
259
+
260
+ def _add_call_finding(
261
+ self,
262
+ findings: list[Finding],
263
+ rel_path: str,
264
+ line_num: int,
265
+ line: str,
266
+ lang: Lang,
267
+ rng_name: str,
268
+ ) -> None:
269
+ is_sensitive = any(kw in line.lower() for kw in self.SENSITIVE_CONTEXT_KEYWORDS)
270
+ secure_alt = self.SECURE_ALTERNATIVES.get(
271
+ lang.value if hasattr(lang, "value") else str(lang),
272
+ "a cryptographically secure alternative",
273
+ )
274
+ findings.append(
275
+ make_finding(
276
+ severity=Severity.HIGH if is_sensitive else Severity.MEDIUM,
277
+ file=rel_path,
278
+ line_start=line_num,
279
+ title=f"Insecure RNG in {'security-sensitive' if is_sensitive else ''} context ({str(lang)})",
280
+ description=(
281
+ f"Non-cryptographic random generator {rng_name} detected"
282
+ f"{' in a security-sensitive context' if is_sensitive else ''}. "
283
+ f"Use {secure_alt} instead."
284
+ ),
285
+ evidence=line.strip(),
286
+ cwe="CWE-338" if is_sensitive else "CWE-330",
287
+ )
288
+ )