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,282 @@
1
+ """
2
+ Container Scanner — scans container images and Dockerfiles for vulnerabilities.
3
+
4
+ Wraps Trivy (optional subprocess) for container image CVE scanning.
5
+ Falls back to static Dockerfile analysis when Trivy is not available.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import json
11
+ import logging
12
+ import re
13
+ import subprocess
14
+
15
+ from patchi.core.agents.base import (
16
+ AgentGroup,
17
+ AgentInput,
18
+ AgentResult,
19
+ BaseAgent,
20
+ Finding,
21
+ Severity,
22
+ register,
23
+ safe_rglob,
24
+ )
25
+
26
+
27
+ @register
28
+ class ContainerScannerAgent(BaseAgent):
29
+ """Scans container images and Dockerfiles for CVEs and misconfigurations."""
30
+
31
+ name = "ContainerScannerAgent"
32
+ group = AgentGroup.SECURITY
33
+ timeout = 120
34
+
35
+ def _run(self, inp: AgentInput, result: AgentResult) -> None:
36
+ scanned = 0
37
+
38
+ # Try Trivy image scan if available
39
+ trivy_available = self._check_trivy()
40
+ if trivy_available:
41
+ scanned += self._scan_with_trivy(inp, result)
42
+
43
+ # Always do static Dockerfile analysis
44
+ scanned += self._scan_dockerfiles(inp, result)
45
+
46
+ # Scan docker-compose for image references
47
+ scanned += self._scan_compose_images(inp, result)
48
+
49
+ result.files_scanned = scanned
50
+
51
+ def _check_trivy(self) -> bool:
52
+ try:
53
+ proc = subprocess.run(
54
+ ["trivy", "--version"],
55
+ capture_output=True,
56
+ text=True,
57
+ timeout=5,
58
+ )
59
+ return proc.returncode == 0
60
+ except (FileNotFoundError, subprocess.TimeoutExpired):
61
+ return False
62
+
63
+ def _scan_with_trivy(self, inp: AgentInput, result: AgentResult) -> int:
64
+ """Scan Dockerfiles with Trivy config scanner."""
65
+ count = 0
66
+ for fpath in safe_rglob(inp.root, "Dockerfile*"):
67
+ if not fpath.is_file():
68
+ continue
69
+ rel = fpath.relative_to(inp.root).as_posix()
70
+ try:
71
+ proc = subprocess.run(
72
+ ["trivy", "config", "--format=json", str(fpath)],
73
+ capture_output=True,
74
+ text=True,
75
+ timeout=60,
76
+ )
77
+ if proc.stdout.strip():
78
+ data = json.loads(proc.stdout)
79
+ for target in data.get("Results", []):
80
+ for vuln in target.get("Vulnerabilities", []):
81
+ sev = vuln.get("Severity", "MEDIUM").upper()
82
+ result.add_finding(
83
+ Finding(
84
+ agent=self.name,
85
+ type="container_cve",
86
+ severity=Severity(sev.lower())
87
+ if sev.lower() in ("critical", "high", "medium", "low")
88
+ else Severity.MEDIUM,
89
+ file=rel,
90
+ message=f"{vuln.get('VulnerabilityID', '')}: {vuln.get('Title', 'Container vulnerability')}",
91
+ cwe=vuln.get("CweIDs", [""])[0] if vuln.get("CweIDs") else "",
92
+ extra={
93
+ "vuln_id": vuln.get("VulnerabilityID", ""),
94
+ "pkg": vuln.get("PkgName", ""),
95
+ "installed": vuln.get("InstalledVersion", ""),
96
+ "fixed": vuln.get("FixedVersion", ""),
97
+ },
98
+ )
99
+ )
100
+ except (subprocess.TimeoutExpired, json.JSONDecodeError, Exception):
101
+ pass
102
+ count += 1
103
+ return count
104
+
105
+ def _check_layer_bloat(self, content: str, rel: str, result: AgentResult) -> None:
106
+ """§8.3.3 dive / scout heuristic: layer bloat detection §8.3.3."""
107
+ import shutil as _sh
108
+ import subprocess as _sp
109
+
110
+ # Try dive CI if installed
111
+ if _sh.which("dive"):
112
+ try:
113
+ proc = _sp.run(
114
+ ["dive", "--ci", "--json", rel],
115
+ capture_output=True,
116
+ text=True,
117
+ timeout=30,
118
+ )
119
+ if proc.stdout:
120
+ import json as _js
121
+
122
+ data = _js.loads(proc.stdout)
123
+ eff = data.get("efficiencyScore", 1.0)
124
+ if eff < 0.9:
125
+ result.add_finding(
126
+ Finding(
127
+ agent=self.name,
128
+ type="layer_bloat",
129
+ severity=Severity.MEDIUM,
130
+ file=rel,
131
+ message=f"Dive efficiency {eff:.0%} — image has wasted space / duplicate layers",
132
+ )
133
+ )
134
+ return # dive succeeded, skip heuristic
135
+ except Exception as _exc:
136
+ logging.getLogger("patchi").debug('suppressed: %s', _exc)
137
+ # Heuristic fallback: count RUN, check multi-stage, large base
138
+ runs = len(re.findall(r"^\s*RUN\s+", content, re.MULTILINE | re.I))
139
+ froms = re.findall(r"^\s*FROM\s+(\S+)", content, re.MULTILINE | re.I)
140
+ if runs > 7:
141
+ result.add_finding(
142
+ Finding(
143
+ agent=self.name,
144
+ type="layer_bloat",
145
+ severity=Severity.LOW,
146
+ file=rel,
147
+ message=f"{runs} RUN layers — consolidate with && \\ to reduce layers (dive: layer bloat)",
148
+ )
149
+ )
150
+ if len(froms) == 1 and runs > 5:
151
+ result.add_finding(
152
+ Finding(
153
+ agent=self.name,
154
+ type="no_multistage",
155
+ severity=Severity.LOW,
156
+ file=rel,
157
+ message="Single-stage build with many RUNs — consider multi-stage to slim final image",
158
+ )
159
+ )
160
+ for base in froms:
161
+ if any(b in base.lower() for b in (":latest", "ubuntu:latest", "alpine:latest")) or ":" not in base:
162
+ result.add_finding(
163
+ Finding(
164
+ agent=self.name,
165
+ type="unpinned_base",
166
+ severity=Severity.MEDIUM,
167
+ file=rel,
168
+ message=f"Unpinned base image: {base} — pin to digest or specific tag",
169
+ )
170
+ )
171
+
172
+ def _scan_dockerfiles(self, inp: AgentInput, result: AgentResult) -> int:
173
+ """Static Dockerfile analysis when Trivy is not available."""
174
+ count = 0
175
+ for fpath in safe_rglob(inp.root, "Dockerfile*"):
176
+ if not fpath.is_file():
177
+ continue
178
+ rel = fpath.relative_to(inp.root).as_posix()
179
+ try:
180
+ content = fpath.read_text(encoding="utf-8", errors="ignore")
181
+ except OSError:
182
+ continue
183
+ count += 1
184
+
185
+ lines = content.splitlines()
186
+ has_healthcheck = False
187
+ has_copy_add = False
188
+
189
+ for i, line in enumerate(lines, 1):
190
+ stripped = line.strip()
191
+ if stripped.startswith("#"):
192
+ continue
193
+
194
+ upper = stripped.upper()
195
+
196
+ # HEALTHCHECK
197
+ if upper.startswith("HEALTHCHECK"):
198
+ has_healthcheck = True
199
+
200
+ # COPY vs ADD
201
+ if upper.startswith("ADD ") and not has_copy_add:
202
+ has_copy_add = True
203
+ result.add_finding(
204
+ Finding(
205
+ agent=self.name,
206
+ type="use_copy_over_add",
207
+ severity=Severity.LOW,
208
+ file=rel,
209
+ line=i,
210
+ message="Consider using COPY instead of ADD (ADD has implicit tar extraction)",
211
+ )
212
+ )
213
+
214
+ # Secrets in ENV
215
+ if upper.startswith("ENV "):
216
+ secretish = re.search(
217
+ r"(?:password|secret|key|token|api_key)\s*=\s*\S+",
218
+ stripped,
219
+ re.I,
220
+ )
221
+ if secretish:
222
+ result.add_finding(
223
+ Finding(
224
+ agent=self.name,
225
+ type="env_secret",
226
+ severity=Severity.CRITICAL,
227
+ file=rel,
228
+ line=i,
229
+ message="Secret in ENV — use build-time secrets or runtime injection",
230
+ cwe="CWE-798",
231
+ )
232
+ )
233
+
234
+ if not has_healthcheck and lines:
235
+ result.add_finding(
236
+ Finding(
237
+ agent=self.name,
238
+ type="no_healthcheck",
239
+ severity=Severity.LOW,
240
+ file=rel,
241
+ message="No HEALTHCHECK instruction — container health unknown to orchestrator",
242
+ )
243
+ )
244
+
245
+ # Layer bloat check §8.3.3
246
+ self._check_layer_bloat(content, rel, result)
247
+
248
+ return count
249
+
250
+ def _scan_compose_images(self, inp: AgentInput, result: AgentResult) -> int:
251
+ """Check docker-compose for :latest tags and unpinned images."""
252
+ count = 0
253
+ for pattern in [
254
+ "docker-compose*.yml",
255
+ "docker-compose*.yaml",
256
+ "compose.yml",
257
+ "compose.yaml",
258
+ ]:
259
+ for fpath in safe_rglob(inp.root, pattern):
260
+ if not fpath.is_file():
261
+ continue
262
+ rel = fpath.relative_to(inp.root).as_posix()
263
+ try:
264
+ content = fpath.read_text(encoding="utf-8", errors="ignore")
265
+ except OSError:
266
+ continue
267
+ count += 1
268
+
269
+ for m in re.finditer(r'image:\s*["\']?([^"\'\s]+)["\']?', content):
270
+ img = m.group(1)
271
+ if ":latest" in img or ":" not in img:
272
+ result.add_finding(
273
+ Finding(
274
+ agent=self.name,
275
+ type="unpinned_image",
276
+ severity=Severity.MEDIUM,
277
+ file=rel,
278
+ message=f"Unpinned or :latest image: {img}",
279
+ )
280
+ )
281
+
282
+ return count
@@ -0,0 +1,378 @@
1
+ """
2
+ CryptoAgent — cryptographic implementation issues.
3
+
4
+ Detects crypto-related security issues:
5
+ - Weak hashing algorithms (MD5, SHA1)
6
+ - Weak encryption algorithms (DES, RC4)
7
+ - Hardcoded cryptographic keys
8
+ - Improper salt usage
9
+ - Predictable random number generation
10
+ - Padding oracle vulnerabilities
11
+ - Weak key sizes
12
+ - Insecure PRNG usage
13
+
14
+ Uses pattern matching and code analysis.
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
+ import re
23
+ from pathlib import Path
24
+
25
+ from ..agents.base import (
26
+ AgentDomain,
27
+ AgentGroup,
28
+ AgentInput,
29
+ AgentResult,
30
+ AgentStatus,
31
+ BaseAgent,
32
+ Finding,
33
+ Severity,
34
+ make_finding,
35
+ register,
36
+ safe_rglob,
37
+ )
38
+
39
+
40
+ @register
41
+ class CryptoAgent(BaseAgent):
42
+ """Agent for detecting cryptographic implementation issues."""
43
+
44
+ group = AgentGroup.SECURITY
45
+ domain = AgentDomain.SECURITY
46
+ name = "CryptoAgent"
47
+ description = "Cryptographic issues: weak algos, hardcoded keys, improper salt, PRNG"
48
+
49
+ def _run(self, inp: AgentInput, result: AgentResult) -> None:
50
+ """Run cryptographic security detection."""
51
+ findings = []
52
+
53
+ # Define source file patterns to scan
54
+ source_patterns = [
55
+ "*.py",
56
+ "*.js",
57
+ "*.jsx",
58
+ "*.ts",
59
+ "*.tsx",
60
+ "*.java",
61
+ "*.php",
62
+ "*.rb",
63
+ "*.go",
64
+ "*.rs",
65
+ "*.cpp",
66
+ "*.cxx",
67
+ "*.cc",
68
+ "*.c",
69
+ "*.h",
70
+ "*.hpp",
71
+ "*.cs",
72
+ ]
73
+
74
+ # Search for source files
75
+ for pattern in source_patterns:
76
+ for file_path in safe_rglob(inp.root, pattern):
77
+ if file_path.is_file():
78
+ rel_path = file_path.relative_to(inp.root).as_posix()
79
+ if not self._should_skip_file(rel_path, inp):
80
+ findings.extend(self._scan_file_crypto_security(file_path, rel_path))
81
+
82
+ result.status = AgentStatus.SUCCEEDED
83
+ result.findings = findings
84
+ result.data.update(
85
+ {
86
+ "crypto_findings": len(
87
+ [
88
+ f
89
+ for f in findings
90
+ if any(
91
+ word in f.title.lower()
92
+ for word in [
93
+ "crypto",
94
+ "hash",
95
+ "encrypt",
96
+ "random",
97
+ "salt",
98
+ "key",
99
+ "prng",
100
+ ]
101
+ )
102
+ ]
103
+ ),
104
+ "needs_ai": False,
105
+ }
106
+ )
107
+ return
108
+
109
+ def _should_skip_file(self, file_path: str, inp: AgentInput) -> bool:
110
+ """Check if file should be skipped based on restrictions."""
111
+ from pathlib import PurePosixPath
112
+
113
+ # Check restrictions
114
+ restrictions = inp.config.get("restrictions", [])
115
+ for r in restrictions:
116
+ if r.get("enabled", True):
117
+ path = r["path"]
118
+ if file_path.startswith(path) or PurePosixPath(file_path).match(path):
119
+ if r["type"] == "NO_TOUCH":
120
+ return True
121
+ elif r["type"] == "SCAN_ONLY" and self.__class__.__name__ == "FixAgent":
122
+ return True
123
+ return False
124
+
125
+ def _scan_file_crypto_security(self, file_path: Path, rel_path: str) -> list[Finding]:
126
+ """Scan a file for cryptographic security issues."""
127
+ findings = []
128
+
129
+ try:
130
+ content = file_path.read_text(encoding="utf-8")
131
+
132
+ # Check for weak hashing algorithms
133
+ findings.extend(self._scan_weak_hashing(content, rel_path))
134
+
135
+ # Check for weak encryption algorithms
136
+ findings.extend(self._scan_weak_encryption(content, rel_path))
137
+
138
+ # Check for hardcoded cryptographic keys
139
+ findings.extend(self._scan_hardcoded_keys(content, rel_path))
140
+
141
+ # Check for improper salt usage
142
+ findings.extend(self._scan_salt_issues(content, rel_path))
143
+
144
+ # Check for predictable random generation
145
+ findings.extend(self._scan_random_generation(content, rel_path))
146
+
147
+ # Check for other crypto issues
148
+ findings.extend(self._scan_other_crypto_issues(content, rel_path))
149
+
150
+ except Exception as e:
151
+ findings.append(
152
+ make_finding(
153
+ severity=Severity.LOW,
154
+ file=rel_path,
155
+ line_start=0,
156
+ title="Crypto security scanner file read error",
157
+ description=f"Could not analyze {file_path.name} for cryptographic issues: {str(e)}",
158
+ evidence=str(e),
159
+ )
160
+ )
161
+
162
+ return findings
163
+
164
+ def _scan_weak_hashing(self, content: str, rel_path: str) -> list[Finding]:
165
+ """Scan for weak hashing algorithm usage."""
166
+ findings = []
167
+
168
+ # Look for weak hashing algorithms
169
+ weak_hash_patterns = [
170
+ (r"MD5|md5", "Weak Hash Algorithm (MD5)", Severity.HIGH),
171
+ (r"SHA1|sha1", "Weak Hash Algorithm (SHA1)", Severity.HIGH),
172
+ (r"hash.*md5|digest.*md5", "MD5 Hash Usage", Severity.HIGH),
173
+ (r"hash.*sha1|digest.*sha1", "SHA1 Hash Usage", Severity.HIGH),
174
+ ]
175
+
176
+ lines = content.splitlines()
177
+ for i, line in enumerate(lines, 1):
178
+ for pattern, description, severity in weak_hash_patterns:
179
+ matches = re.finditer(pattern, line, re.IGNORECASE)
180
+ for match in matches:
181
+ findings.append(
182
+ make_finding(
183
+ severity=severity,
184
+ file=rel_path,
185
+ line_start=i,
186
+ title=description,
187
+ description=f"Weak hashing algorithm found: {match.group(0)}",
188
+ evidence=line.strip(),
189
+ )
190
+ )
191
+
192
+ return findings
193
+
194
+ def _scan_weak_encryption(self, content: str, rel_path: str) -> list[Finding]:
195
+ """Scan for weak encryption algorithm usage."""
196
+ findings = []
197
+
198
+ # Look for weak encryption algorithms
199
+ weak_crypto_patterns = [
200
+ (r"\bDES\b", "Weak Encryption Algorithm (DES)", Severity.HIGH),
201
+ (r"\bRC4\b", "Weak Stream Cipher (RC4)", Severity.HIGH),
202
+ (r"\bblowfish\b", "Weak Block Cipher (Blowfish)", Severity.MEDIUM),
203
+ (r"encrypt.*\bDES\b|cipher.*\bDES\b", "DES Encryption Usage", Severity.HIGH),
204
+ (r"encrypt.*\bRC4\b|cipher.*\bRC4\b", "RC4 Encryption Usage", Severity.HIGH),
205
+ ]
206
+
207
+ lines = content.splitlines()
208
+ for i, line in enumerate(lines, 1):
209
+ for pattern, description, severity in weak_crypto_patterns:
210
+ matches = re.finditer(pattern, line, re.IGNORECASE)
211
+ for match in matches:
212
+ findings.append(
213
+ make_finding(
214
+ severity=severity,
215
+ file=rel_path,
216
+ line_start=i,
217
+ title=description,
218
+ description=f"Weak encryption algorithm found: {match.group(0)}",
219
+ evidence=line.strip(),
220
+ )
221
+ )
222
+
223
+ return findings
224
+
225
+ def _scan_hardcoded_keys(self, content: str, rel_path: str) -> list[Finding]:
226
+ """Scan for hardcoded cryptographic keys."""
227
+ findings = []
228
+
229
+ # Look for hardcoded keys (require key-like context to reduce false positives)
230
+ key_patterns = [
231
+ (
232
+ r'(?:\bsecret\b|\bkey\b|\btoken\b|\bpassword)\s*[:=]\s*["\'][A-Za-z0-9+/=]{20,}["\']',
233
+ "Hardcoded Cryptographic Key",
234
+ Severity.HIGH,
235
+ ),
236
+ (
237
+ r'(?:SECRET_KEY|API_KEY|PRIVATE_KEY|ENCRYPTION_KEY)\s*[:=]\s*["\'][^"\']{8,}["\']',
238
+ "Hardcoded Key Constant",
239
+ Severity.HIGH,
240
+ ),
241
+ ]
242
+
243
+ lines = content.splitlines()
244
+ for i, line in enumerate(lines, 1):
245
+ for pattern, description, severity in key_patterns:
246
+ matches = re.finditer(pattern, line)
247
+ for match in matches:
248
+ # Additional validation to reduce false positives
249
+ matched_text = match.group(0)
250
+ # Check if it looks like a real key (not just a long hex string in other context)
251
+ if len(matched_text) > 20 or ("key" in line.lower() and len(matched_text) > 10):
252
+ findings.append(
253
+ make_finding(
254
+ severity=severity,
255
+ file=rel_path,
256
+ line_start=i,
257
+ title=description,
258
+ description=f"Potentially hardcoded cryptographic key found: {matched_text[:20]}...",
259
+ evidence=line.strip(),
260
+ )
261
+ )
262
+
263
+ return findings
264
+
265
+ def _scan_salt_issues(self, content: str, rel_path: str) -> list[Finding]:
266
+ """Scan for improper salt usage."""
267
+ findings = []
268
+
269
+ # Look for missing or weak salt usage
270
+ salt_patterns = [
271
+ (r"hash.*password(?![^=]*salt)", "Hashing Password Without Salt", Severity.HIGH),
272
+ (r"pbkdf2(?![^=]*salt)", "PBKDF2 Without Salt", Severity.HIGH),
273
+ (r"bcrypt(?![^=]*salt)", "bcrypt Without Salt", Severity.HIGH),
274
+ ]
275
+
276
+ lines = content.splitlines()
277
+ for i, line in enumerate(lines, 1):
278
+ for pattern, description, severity in salt_patterns:
279
+ matches = re.finditer(pattern, line, re.IGNORECASE)
280
+ for _match in matches:
281
+ findings.append(
282
+ make_finding(
283
+ severity=severity,
284
+ file=rel_path,
285
+ line_start=i,
286
+ title=description,
287
+ description="Cryptographic function used without proper salt",
288
+ evidence=line.strip(),
289
+ )
290
+ )
291
+
292
+ # Look for hardcoded salts
293
+ hardcoded_salt_patterns = [
294
+ (r'\bsalt\b\s*[=:]\s*["\'][^"\']+["\']', "Hardcoded Salt", Severity.HIGH),
295
+ (
296
+ r"const.*\bsalt\b|var.*\bsalt\b|let.*\bsalt\b",
297
+ "Possible Hardcoded Salt Variable",
298
+ Severity.MEDIUM,
299
+ ),
300
+ ]
301
+
302
+ for i, line in enumerate(lines, 1):
303
+ for pattern, description, severity in hardcoded_salt_patterns:
304
+ matches = re.finditer(pattern, line, re.IGNORECASE)
305
+ for _match in matches:
306
+ findings.append(
307
+ make_finding(
308
+ severity=severity,
309
+ file=rel_path,
310
+ line_start=i,
311
+ title=description,
312
+ description="Possible hardcoded salt detected",
313
+ evidence=line.strip(),
314
+ )
315
+ )
316
+
317
+ return findings
318
+
319
+ def _scan_random_generation(self, content: str, rel_path: str) -> list[Finding]:
320
+ """Scan for predictable random number generation."""
321
+ findings = []
322
+
323
+ # Look for insecure random generation (only in crypto-relevant contexts)
324
+ random_patterns = [
325
+ (r"Math\.random\(\)", "Predictable Random Number (JavaScript)", Severity.HIGH),
326
+ (
327
+ r"(?:random\.random|random\.randint|random\.choice|random\.randrange)\b",
328
+ "Non-crypto PRNG (Python) — fine for games, risky for tokens/secrets",
329
+ Severity.MEDIUM,
330
+ ),
331
+ (r"System\.Random|Random\.Next", "Potentially Weak Random (C#)", Severity.MEDIUM),
332
+ ]
333
+
334
+ lines = content.splitlines()
335
+ for i, line in enumerate(lines, 1):
336
+ for pattern, description, severity in random_patterns:
337
+ matches = re.finditer(pattern, line)
338
+ for match in matches:
339
+ findings.append(
340
+ make_finding(
341
+ severity=severity,
342
+ file=rel_path,
343
+ line_start=i,
344
+ title=description,
345
+ description=f"Insecure random number generation: {match.group(0)}",
346
+ evidence=line.strip(),
347
+ )
348
+ )
349
+
350
+ return findings
351
+
352
+ def _scan_other_crypto_issues(self, content: str, rel_path: str) -> list[Finding]:
353
+ """Scan for other cryptographic issues."""
354
+ findings = []
355
+
356
+ # Look for weak key sizes
357
+ key_size_patterns = [
358
+ (r"key_size.*512|rsa.*512", "Weak RSA Key Size (512 bits)", Severity.HIGH),
359
+ (r"key_size.*1024|rsa.*1024", "Weak RSA Key Size (1024 bits)", Severity.MEDIUM),
360
+ ]
361
+
362
+ lines = content.splitlines()
363
+ for i, line in enumerate(lines, 1):
364
+ for pattern, description, severity in key_size_patterns:
365
+ matches = re.finditer(pattern, line, re.IGNORECASE)
366
+ for match in matches:
367
+ findings.append(
368
+ make_finding(
369
+ severity=severity,
370
+ file=rel_path,
371
+ line_start=i,
372
+ title=description,
373
+ description=f"Weak cryptographic key size: {match.group(0)}",
374
+ evidence=line.strip(),
375
+ )
376
+ )
377
+
378
+ return findings