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,725 @@
1
+ """
2
+ DependencyScanner — package files, CVE status via OSV API.
3
+
4
+ Scans dependency files including:
5
+ - package.json (npm/yarn)
6
+ - requirements.txt (pip)
7
+ - Pipfile (pipenv)
8
+ - poetry.lock (Poetry)
9
+ - Cargo.toml (Cargo/Rust)
10
+ - go.mod (Go modules)
11
+ - composer.json (Composer/PHP)
12
+ - Gemfile (Bundler/Ruby)
13
+
14
+ Checks versions against OSV (Open Source Vulnerability) database
15
+ for known CVEs and security issues.
16
+
17
+ Does NOT call AI.
18
+ Does NOT write to disk.
19
+ Does NOT touch the queue.
20
+ """
21
+ from __future__ import annotations
22
+
23
+ import json
24
+ import logging
25
+ import re
26
+ import urllib.error
27
+ import urllib.request
28
+ from pathlib import Path
29
+
30
+ from patchi.core.brain.languages import DEFAULT_IGNORE_DIRS
31
+ from patchi.core.constants import is_offline
32
+
33
+ from .base import (
34
+ AgentGroup,
35
+ AgentInput,
36
+ AgentResult,
37
+ AgentStatus,
38
+ BaseAgent,
39
+ Finding,
40
+ Severity,
41
+ make_finding,
42
+ register,
43
+ safe_rglob,
44
+ )
45
+
46
+ # Keep a direct alias to the original make_finding to avoid accidental
47
+ # recursive replacement when updating calls to use the compatibility wrapper.
48
+ _base_make_finding = make_finding
49
+
50
+
51
+ _log = logging.getLogger("patchi.agents.dependency_scanner")
52
+
53
+
54
+ @register
55
+ class DependencyScanner(BaseAgent):
56
+ """Scanner for project dependencies and CVEs."""
57
+
58
+ group = AgentGroup.SCANNER
59
+ name = "DependencyScanner"
60
+ description = "Dependency files, CVE status via OSV API"
61
+
62
+ def _mkf(self, *args, **kwargs) -> Finding:
63
+ """Compatibility wrapper for legacy make_finding calls in this scanner."""
64
+ # Old positional style: (Severity, file, line_start, title, description, evidence)
65
+ if args and isinstance(args[0], Severity):
66
+ severity = args[0]
67
+ file = args[1] if len(args) > 1 else ""
68
+ line = args[2] if len(args) > 2 else 0
69
+ title = args[3] if len(args) > 3 else ""
70
+ message = args[4] if len(args) > 4 else ""
71
+ evidence = args[5] if len(args) > 5 else ""
72
+ finding_type = kwargs.pop("finding_type", None) or title.lower().replace(
73
+ " ", "_"
74
+ ).replace(":", "").replace("'", "").replace("-", "_")
75
+ return _base_make_finding(
76
+ self.name,
77
+ finding_type,
78
+ severity,
79
+ file,
80
+ message,
81
+ line=line,
82
+ evidence=evidence,
83
+ **kwargs,
84
+ )
85
+
86
+ # Keyword style: severity=..., file=..., title=..., description=..., evidence=...
87
+ if "severity" in kwargs and ("file" in kwargs or "file_path" in kwargs):
88
+ severity = kwargs.pop("severity")
89
+ file = kwargs.pop("file", kwargs.pop("file_path", ""))
90
+ line = kwargs.pop("line_start", kwargs.pop("line", 0))
91
+ title = kwargs.pop("title", "")
92
+ message = kwargs.pop("description", title)
93
+ evidence = kwargs.pop("evidence", "")
94
+ finding_type = kwargs.pop("finding_type", None) or title.lower().replace(
95
+ " ", "_"
96
+ ).replace(":", "").replace("'", "").replace("-", "_")
97
+ return _base_make_finding(
98
+ self.name,
99
+ finding_type,
100
+ severity,
101
+ file,
102
+ message,
103
+ line=line,
104
+ evidence=evidence,
105
+ **kwargs,
106
+ )
107
+
108
+ return _base_make_finding(*args, **kwargs)
109
+
110
+ def _run(self, inp: AgentInput, result: AgentResult) -> None:
111
+ """Scan dependency files for vulnerabilities.
112
+
113
+ Populates `result.data['dependencies']` and `result.data['total_deps']`.
114
+ """
115
+ findings: list[Finding] = []
116
+ dependencies: dict[str, str] = {}
117
+ vulns_count = 0
118
+
119
+ # Define dependency file patterns
120
+ dep_files = [
121
+ "package.json",
122
+ "requirements.txt",
123
+ "Pipfile",
124
+ "poetry.lock",
125
+ "Cargo.toml",
126
+ "Cargo.lock",
127
+ "go.mod",
128
+ "go.sum",
129
+ "composer.json",
130
+ "Gemfile",
131
+ "Gemfile.lock",
132
+ "yarn.lock",
133
+ "pnpm-lock.yaml",
134
+ ]
135
+
136
+ # Search for dependency files and build dependency map
137
+ found_any = False
138
+ for dep_file in dep_files:
139
+ for file_path in safe_rglob(inp.root, dep_file):
140
+ if file_path.is_file():
141
+ rel_path = file_path.relative_to(inp.root).as_posix()
142
+ if not self._should_skip_file(rel_path, inp):
143
+ found_any = True
144
+ file_findings = self._scan_dep_file(file_path, rel_path)
145
+ findings.extend(file_findings)
146
+
147
+ # Lightweight parsing to populate dependencies dict for tests
148
+ try:
149
+ content = file_path.read_text(encoding="utf-8")
150
+ if file_path.name == "package.json":
151
+ data = json.loads(content)
152
+ deps = data.get("dependencies", {})
153
+ dev_deps = data.get("devDependencies", {})
154
+ for n, v in {**deps, **dev_deps}.items():
155
+ dependencies[n] = v
156
+ elif file_path.name == "requirements.txt":
157
+ for line in content.splitlines():
158
+ ln = line.strip()
159
+ if not ln or ln.startswith("#"):
160
+ continue
161
+ pkg_name = ln
162
+ version = "unknown"
163
+ for sep in ["==", ">=", "<=", ">", "<"]:
164
+ if sep in ln:
165
+ pkg_name, version = ln.split(sep, 1)
166
+ break
167
+ dependencies[pkg_name.strip()] = version.strip()
168
+ except Exception as e:
169
+ _log.warning("DependencyScanner._run failed: %s", e)
170
+
171
+ # If no dependency files were found, emit an informational finding
172
+ if not found_any:
173
+ result.add_finding(
174
+ self._mkf(
175
+ severity=Severity.INFO,
176
+ file="",
177
+ line_start=0,
178
+ title="No dependency files found",
179
+ description="Project contains no recognized dependency files",
180
+ finding_type="no_dependency_file",
181
+ evidence="",
182
+ )
183
+ )
184
+
185
+ # Aggregate dependencies and vulnerability counts from findings
186
+ for f in findings:
187
+ # Titles created by legacy code use formats like "Dependency: name@version" or "VULNERABILITY: name@version"
188
+ getattr(f, "message", "") or getattr(f, "type", "") or ""
189
+ # Prefer finding.type if it encodes vulnerability info
190
+ if f.message.startswith("VULNERABILITY:") or f.type.startswith("vulnerability"):
191
+ vulns_count += 1
192
+ if f.message.startswith("Dependency:"):
193
+ # message may be like "Dependency: name@version" if compat preserved
194
+ dep_text = f.message[len("Dependency:") :].strip()
195
+ if "@" in dep_text:
196
+ name, version = dep_text.split("@", 1)
197
+ else:
198
+ name, version = dep_text, "unknown"
199
+ dependencies[name.strip()] = version.strip()
200
+
201
+ # Add findings to result
202
+ for f in findings:
203
+ result.add_finding(f)
204
+
205
+ result.data["dependencies"] = dependencies
206
+ result.data["total_deps"] = len(dependencies)
207
+ result.data["vulnerable_deps"] = vulns_count
208
+ result.files_scanned = sum(1 for _ in safe_rglob(inp.root, "*"))
209
+ result.data["needs_ai"] = False
210
+ if result.status == AgentStatus.RUNNING:
211
+ result.status = AgentStatus.DONE
212
+
213
+ def _should_skip_file(self, file_path: str, inp: AgentInput) -> bool:
214
+ """Check if file should be skipped based on restrictions."""
215
+ # Skip ignored directories (node_modules, .venv, etc.)
216
+ from pathlib import PurePosixPath
217
+
218
+ if any(p in DEFAULT_IGNORE_DIRS for p in PurePosixPath(file_path).parts):
219
+ return True
220
+
221
+ from pathlib import PurePosixPath
222
+
223
+ # Check restrictions
224
+ restrictions = inp.config.get("restrictions", [])
225
+ for r in restrictions:
226
+ if r.get("enabled", True):
227
+ path = r["path"]
228
+ if file_path.startswith(path) or PurePosixPath(file_path).match(path):
229
+ if r["type"] == "NO_TOUCH":
230
+ return True
231
+ elif r["type"] == "SCAN_ONLY" and self.__class__.__name__ == "FixAgent":
232
+ return True
233
+ return False
234
+
235
+ def _scan_dep_file(self, file_path: Path, rel_path: str) -> list[Finding]:
236
+ """Scan a dependency file."""
237
+ findings = []
238
+
239
+ try:
240
+ content = file_path.read_text(encoding="utf-8")
241
+
242
+ if file_path.name == "package.json":
243
+ findings.extend(self._scan_package_json(file_path, rel_path, content))
244
+ elif file_path.name == "requirements.txt":
245
+ findings.extend(self._scan_requirements_txt(file_path, rel_path, content))
246
+ elif file_path.name == "Pipfile":
247
+ findings.extend(self._scan_pipfile(file_path, rel_path, content))
248
+ elif file_path.name == "poetry.lock":
249
+ findings.extend(self._scan_poetry_lock(file_path, rel_path, content))
250
+ elif file_path.name in ["Cargo.toml", "Cargo.lock"]:
251
+ findings.extend(self._scan_cargo_file(file_path, rel_path, content))
252
+ elif file_path.name == "go.mod":
253
+ findings.extend(self._scan_go_mod(file_path, rel_path, content))
254
+ elif file_path.name == "composer.json":
255
+ findings.extend(self._scan_composer_json(file_path, rel_path, content))
256
+ elif file_path.name in ["Gemfile", "Gemfile.lock"]:
257
+ findings.extend(self._scan_gemfile(file_path, rel_path, content))
258
+ elif file_path.name in ["yarn.lock", "pnpm-lock.yaml"]:
259
+ findings.extend(self._scan_lock_file(file_path, rel_path, content))
260
+
261
+ findings.append(
262
+ self._mkf(
263
+ severity=Severity.INFO,
264
+ file=rel_path,
265
+ line_start=0,
266
+ title=f"Dependency File: {file_path.name}",
267
+ description="Found dependency file with vulnerability checking",
268
+ evidence=f"File: {file_path.name}",
269
+ )
270
+ )
271
+
272
+ except Exception as e:
273
+ findings.append(
274
+ self._mkf(
275
+ severity=Severity.LOW,
276
+ file=rel_path,
277
+ line_start=0,
278
+ title="Dependency file read error",
279
+ description=f"Could not read dependency file {file_path.name}: {str(e)}",
280
+ evidence=str(e),
281
+ )
282
+ )
283
+
284
+ return findings
285
+
286
+ def _scan_package_json(self, file_path: Path, rel_path: str, content: str) -> list[Finding]:
287
+ """Scan package.json for dependencies and vulnerabilities."""
288
+ findings = []
289
+
290
+ try:
291
+ data = json.loads(content)
292
+ deps = data.get("dependencies", {})
293
+ dev_deps = data.get("devDependencies", {})
294
+
295
+ # Combine all dependencies
296
+ all_deps = {}
297
+ all_deps.update(dict(deps.items()))
298
+ all_deps.update(dict(dev_deps.items()))
299
+
300
+ for name, version in all_deps.items():
301
+ findings.append(
302
+ self._mkf(
303
+ severity=Severity.INFO,
304
+ file=rel_path,
305
+ line_start=0,
306
+ title=f"Dependency: {name}@{version}",
307
+ description="JavaScript dependency",
308
+ evidence=f"Version: {version}",
309
+ )
310
+ )
311
+
312
+ # Check for vulnerabilities via OSV API
313
+ vulns = self._check_npm_vulnerabilities(name, version)
314
+ for vuln in vulns:
315
+ severity = self._map_osv_severity(
316
+ vuln.get("severity", [{}])[0].get("score", "UNKNOWN")
317
+ )
318
+ findings.append(
319
+ self._mkf(
320
+ severity=severity,
321
+ file=rel_path,
322
+ line_start=0,
323
+ title=f"VULNERABILITY: {name}@{version}",
324
+ description=vuln.get("summary", "Unknown vulnerability"),
325
+ evidence=json.dumps(vuln),
326
+ )
327
+ )
328
+
329
+ except json.JSONDecodeError as e:
330
+ findings.append(
331
+ self._mkf(
332
+ severity=Severity.HIGH,
333
+ file=rel_path,
334
+ line_start=e.lineno,
335
+ title="Invalid package.json",
336
+ description=f"JSON syntax error: {str(e)}",
337
+ evidence=e.msg,
338
+ )
339
+ )
340
+
341
+ return findings
342
+
343
+ def _scan_requirements_txt(self, file_path: Path, rel_path: str, content: str) -> list[Finding]:
344
+ """Scan requirements.txt for dependencies."""
345
+ findings = []
346
+
347
+ lines = content.splitlines()
348
+ for i, line in enumerate(lines, 1):
349
+ line = line.strip()
350
+ if line and not line.startswith("#") and not line.startswith("-"):
351
+ # Extract package name and version
352
+ if "==" in line:
353
+ pkg_name, version = line.split("==", 1)
354
+ elif ">=" in line:
355
+ pkg_name, version = line.split(">=", 1)
356
+ elif "<=" in line:
357
+ pkg_name, version = line.split("<=", 1)
358
+ elif ">" in line:
359
+ pkg_name, version = line.split(">", 1)
360
+ elif "<" in line:
361
+ pkg_name, version = line.split("<", 1)
362
+ else:
363
+ pkg_name, version = line, "unknown"
364
+
365
+ pkg_name = pkg_name.strip()
366
+ version = version.strip()
367
+
368
+ findings.append(
369
+ self._mkf(
370
+ severity=Severity.INFO,
371
+ file=rel_path,
372
+ line_start=i,
373
+ title=f"Dependency: {pkg_name}@{version}",
374
+ description="Python dependency",
375
+ evidence=line,
376
+ )
377
+ )
378
+
379
+ # Check for vulnerabilities via OSV API
380
+ vulns = self._check_pypi_vulnerabilities(pkg_name, version)
381
+ for vuln in vulns:
382
+ severity = self._map_osv_severity(
383
+ vuln.get("severity", [{}])[0].get("score", "UNKNOWN")
384
+ )
385
+ findings.append(
386
+ self._mkf(
387
+ severity=severity,
388
+ file=rel_path,
389
+ line_start=i,
390
+ title=f"VULNERABILITY: {pkg_name}@{version}",
391
+ description=vuln.get("summary", "Unknown vulnerability"),
392
+ evidence=json.dumps(vuln),
393
+ )
394
+ )
395
+
396
+ return findings
397
+
398
+ def _scan_pipfile(self, file_path: Path, rel_path: str, content: str) -> list[Finding]:
399
+ """Scan Pipfile for dependencies."""
400
+ findings = []
401
+
402
+ # Pipfiles are TOML format, but Python < 3.11 doesn't have built-in TOML support
403
+ # So we'll use regex for basic parsing
404
+ lines = content.splitlines()
405
+ current_section = ""
406
+
407
+ for i, line in enumerate(lines, 1):
408
+ line = line.strip()
409
+
410
+ # Identify section
411
+ if line.startswith("["):
412
+ current_section = line.strip("[]").strip()
413
+ continue
414
+
415
+ # Look for package declarations in the right sections
416
+ if current_section in ["packages", "dev-packages"]:
417
+ pkg_match = re.match(r"^(\w[-\w]*)\s*=\s*", line)
418
+ if pkg_match:
419
+ pkg_name = pkg_match.group(1)
420
+ # Extract version from the rest of the line
421
+ version_part = line[len(pkg_name) :].strip("= ").strip('"')
422
+ findings.append(
423
+ self._mkf(
424
+ severity=Severity.INFO,
425
+ file=rel_path,
426
+ line_start=i,
427
+ title=f"Dependency: {pkg_name}@{version_part}",
428
+ description="Python dependency from Pipfile",
429
+ evidence=line,
430
+ )
431
+ )
432
+
433
+ return findings
434
+
435
+ def _scan_poetry_lock(self, file_path: Path, rel_path: str, content: str) -> list[Finding]:
436
+ """Scan poetry.lock for dependencies."""
437
+ findings = []
438
+
439
+ # Poetry lock files are TOML format
440
+ lines = content.splitlines()
441
+ current_package = {}
442
+
443
+ for i, line in enumerate(lines, 1):
444
+ line = line.strip()
445
+
446
+ # Look for package entries
447
+ if line.startswith("[[package]]"):
448
+ # Process previous package if exists
449
+ if current_package:
450
+ pkg_name = current_package.get("name", "unknown")
451
+ version = current_package.get("version", "unknown")
452
+ findings.append(
453
+ self._mkf(
454
+ severity=Severity.INFO,
455
+ file=rel_path,
456
+ line_start=i,
457
+ title=f"Dependency: {pkg_name}@{version}",
458
+ description="Python dependency from Poetry lock",
459
+ evidence=f"{pkg_name}=={version}",
460
+ )
461
+ )
462
+
463
+ current_package = {}
464
+ elif "=" in line and current_package is not None:
465
+ # Parse key=value pairs
466
+ key, value = line.split("=", 1)
467
+ key = key.strip().strip('"')
468
+ value = value.strip().strip('"')
469
+ current_package[key] = value
470
+
471
+ # Process the last package
472
+ if current_package:
473
+ pkg_name = current_package.get("name", "unknown")
474
+ version = current_package.get("version", "unknown")
475
+ findings.append(
476
+ self._mkf(
477
+ severity=Severity.INFO,
478
+ file=rel_path,
479
+ line_start=len(lines),
480
+ title=f"Dependency: {pkg_name}@{version}",
481
+ description="Python dependency from Poetry lock",
482
+ evidence=f"{pkg_name}=={version}",
483
+ )
484
+ )
485
+
486
+ return findings
487
+
488
+ def _scan_cargo_file(self, file_path: Path, rel_path: str, content: str) -> list[Finding]:
489
+ """Scan Cargo.toml or Cargo.lock for dependencies."""
490
+ findings = []
491
+
492
+ lines = content.splitlines()
493
+ in_deps_section = False
494
+
495
+ for i, line in enumerate(lines, 1):
496
+ line = line.strip()
497
+
498
+ # Identify dependencies section
499
+ if line.startswith("[dependencies]") or line.startswith("[dev-dependencies]"):
500
+ in_deps_section = True
501
+ continue
502
+ elif line.startswith("[") and in_deps_section:
503
+ in_deps_section = False
504
+
505
+ # Look for package declarations in dependencies section
506
+ if in_deps_section and "=" in line:
507
+ pkg_match = re.match(r"^(\w[\w\-_]*)\s*=", line)
508
+ if pkg_match:
509
+ pkg_name = pkg_match.group(1)
510
+ # Extract version from the rest of the line
511
+ version_part = line[len(pkg_name) :].strip("= ").strip('"')
512
+ findings.append(
513
+ self._mkf(
514
+ severity=Severity.INFO,
515
+ file=rel_path,
516
+ line_start=i,
517
+ title=f"Dependency: {pkg_name}@{version_part}",
518
+ description="Rust dependency",
519
+ evidence=line,
520
+ )
521
+ )
522
+
523
+ return findings
524
+
525
+ def _scan_go_mod(self, file_path: Path, rel_path: str, content: str) -> list[Finding]:
526
+ """Scan go.mod for dependencies."""
527
+ findings = []
528
+
529
+ lines = content.splitlines()
530
+
531
+ for i, line in enumerate(lines, 1):
532
+ line = line.strip()
533
+
534
+ # Look for require statements
535
+ if line.startswith("require "):
536
+ # Extract package and version
537
+ req_parts = line[8:].strip().split() # Remove "require " prefix
538
+ if len(req_parts) >= 2:
539
+ pkg_name = req_parts[0]
540
+ version = req_parts[1]
541
+ findings.append(
542
+ self._mkf(
543
+ severity=Severity.INFO,
544
+ file=rel_path,
545
+ line_start=i,
546
+ title=f"Dependency: {pkg_name}@{version}",
547
+ description="Go dependency",
548
+ evidence=line,
549
+ )
550
+ )
551
+
552
+ return findings
553
+
554
+ def _scan_composer_json(self, file_path: Path, rel_path: str, content: str) -> list[Finding]:
555
+ """Scan composer.json for PHP dependencies."""
556
+ findings = []
557
+
558
+ try:
559
+ data = json.loads(content)
560
+ deps = data.get("require", {})
561
+ dev_deps = data.get("require-dev", {})
562
+
563
+ # Combine all dependencies
564
+ all_deps = {}
565
+ all_deps.update(dict(deps.items()))
566
+ all_deps.update(dict(dev_deps.items()))
567
+
568
+ for name, version in all_deps.items():
569
+ findings.append(
570
+ self._mkf(
571
+ severity=Severity.INFO,
572
+ file=rel_path,
573
+ line_start=0,
574
+ title=f"Dependency: {name}@{version}",
575
+ description="PHP dependency",
576
+ evidence=f"Version: {version}",
577
+ )
578
+ )
579
+
580
+ except json.JSONDecodeError as e:
581
+ findings.append(
582
+ self._mkf(
583
+ severity=Severity.HIGH,
584
+ file=rel_path,
585
+ line_start=e.lineno,
586
+ title="Invalid composer.json",
587
+ description=f"JSON syntax error: {str(e)}",
588
+ evidence=e.msg,
589
+ )
590
+ )
591
+
592
+ return findings
593
+
594
+ def _scan_gemfile(self, file_path: Path, rel_path: str, content: str) -> list[Finding]:
595
+ """Scan Gemfile or Gemfile.lock for Ruby dependencies."""
596
+ findings = []
597
+
598
+ lines = content.splitlines()
599
+
600
+ for i, line in enumerate(lines, 1):
601
+ line = line.strip()
602
+
603
+ # Look for gem statements
604
+ if "gem " in line and not line.startswith("#"):
605
+ # Extract gem name
606
+ gem_match = re.search(r'gem\s+["\']([^"\']+)["\']', line)
607
+ if gem_match:
608
+ gem_name = gem_match.group(1)
609
+ findings.append(
610
+ self._mkf(
611
+ severity=Severity.INFO,
612
+ file=rel_path,
613
+ line_start=i,
614
+ title=f"Dependency: {gem_name}",
615
+ description="Ruby dependency",
616
+ evidence=line,
617
+ )
618
+ )
619
+
620
+ return findings
621
+
622
+ def _scan_lock_file(self, file_path: Path, rel_path: str, content: str) -> list[Finding]:
623
+ """Scan lock files (yarn.lock, pnpm-lock.yaml) for dependencies."""
624
+ findings = []
625
+
626
+ lines = content.splitlines()
627
+
628
+ # Different parsing for different lock file types
629
+ if file_path.name == "yarn.lock":
630
+ # Yarn lock files have "package@version" entries
631
+ for i, line in enumerate(lines, 1):
632
+ if line.strip().endswith(":") and "@" in line:
633
+ # Extract package and version
634
+ pkg_part = line.strip().rstrip(":")
635
+ if "@" in pkg_part:
636
+ pkg_name = pkg_part.split("@")[0]
637
+ findings.append(
638
+ self._mkf(
639
+ severity=Severity.INFO,
640
+ file=rel_path,
641
+ line_start=i,
642
+ title=f"Dependency: {pkg_name}",
643
+ description="JavaScript dependency from lock file",
644
+ evidence=pkg_part,
645
+ )
646
+ )
647
+ elif file_path.name == "pnpm-lock.yaml":
648
+ # PNPM lock files have similar format to yarn
649
+ for i, line in enumerate(lines, 1):
650
+ if line.strip().endswith(":") and "/" in line:
651
+ # Extract package name
652
+ pkg_part = line.strip().rstrip(":")
653
+ if "/" in pkg_part and "@" in pkg_part:
654
+ pkg_name = pkg_part.split("/")[-1].split("@")[0]
655
+ findings.append(
656
+ self._mkf(
657
+ severity=Severity.INFO,
658
+ file=rel_path,
659
+ line_start=i,
660
+ title=f"Dependency: {pkg_name}",
661
+ description="JavaScript dependency from pnpm lock",
662
+ evidence=pkg_part,
663
+ )
664
+ )
665
+
666
+ return findings
667
+
668
+ def _check_npm_vulnerabilities(self, package_name: str, version: str) -> list[dict]:
669
+ """Check npm package for vulnerabilities via OSV API."""
670
+ if is_offline():
671
+ return []
672
+ try:
673
+ url = "https://api.osv.dev/v1/query"
674
+ query = {"package": {"name": package_name, "ecosystem": "npm"}, "version": version}
675
+
676
+ req = urllib.request.Request(
677
+ url,
678
+ data=json.dumps(query).encode("utf-8"),
679
+ headers={"Content-Type": "application/json"},
680
+ )
681
+ response = urllib.request.urlopen(req, timeout=15)
682
+ data = json.loads(response.read().decode("utf-8"))
683
+
684
+ return data.get("vulns", [])
685
+ except Exception as e:
686
+ # Return empty list if API call fails
687
+ _log.warning("DependencyScanner._check_npm_vulnerabilities failed: %s", e)
688
+ return []
689
+
690
+ def _check_pypi_vulnerabilities(self, package_name: str, version: str) -> list[dict]:
691
+ """Check PyPI package for vulnerabilities via OSV API."""
692
+ if is_offline():
693
+ return []
694
+ try:
695
+ url = "https://api.osv.dev/v1/query"
696
+ query = {"package": {"name": package_name, "ecosystem": "PyPI"}, "version": version}
697
+
698
+ req = urllib.request.Request(
699
+ url,
700
+ data=json.dumps(query).encode("utf-8"),
701
+ headers={"Content-Type": "application/json"},
702
+ )
703
+ response = urllib.request.urlopen(req, timeout=15)
704
+ data = json.loads(response.read().decode("utf-8"))
705
+
706
+ return data.get("vulns", [])
707
+ except Exception as e:
708
+ # Return empty list if API call fails
709
+ _log.warning("DependencyScanner._check_pypi_vulnerabilities failed: %s", e)
710
+ return []
711
+
712
+ def _map_osv_severity(self, osv_severity: str) -> Severity:
713
+ """Map OSV severity to our severity levels."""
714
+ if not osv_severity or osv_severity == "UNKNOWN":
715
+ return Severity.LOW
716
+
717
+ # Convert to uppercase for comparison
718
+ sev = osv_severity.upper()
719
+
720
+ if sev in ["CRITICAL", "HIGH"]:
721
+ return Severity.CRITICAL
722
+ elif sev == "MEDIUM":
723
+ return Severity.MEDIUM
724
+ else:
725
+ return Severity.LOW