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,616 @@
1
+ """
2
+ DependencyVulnerabilityAgent — checks packages against CVE databases.
3
+
4
+ Scans dependency files for known vulnerabilities:
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
+ and other vulnerability feeds.
16
+
17
+ Does NOT call AI.
18
+ Does NOT write to disk.
19
+ Does NOT touch the queue.
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ import json
25
+ from pathlib import Path
26
+
27
+ from ..agents.base import (
28
+ AgentDomain,
29
+ AgentGroup,
30
+ AgentInput,
31
+ AgentResult,
32
+ AgentStatus,
33
+ BaseAgent,
34
+ Finding,
35
+ Severity,
36
+ make_finding,
37
+ register,
38
+ safe_rglob,
39
+ )
40
+ from .cve_monitor import cached_osv_query
41
+
42
+
43
+ @register
44
+ class DependencyVulnerabilityAgent(BaseAgent):
45
+ """Agent for checking dependencies against CVE databases."""
46
+
47
+ group = AgentGroup.SECURITY
48
+ domain = AgentDomain.SECURITY
49
+ name = "DependencyVulnerabilityAgent"
50
+ description = "Dependency CVEs: checks packages against OSV database"
51
+
52
+ def _run(self, inp: AgentInput, result: AgentResult) -> None:
53
+ """Scan dependency files for known vulnerabilities."""
54
+ self._root = inp.root
55
+ findings = []
56
+
57
+ # Define dependency file patterns
58
+ dep_files = [
59
+ "package.json",
60
+ "requirements.txt",
61
+ "Pipfile",
62
+ "poetry.lock",
63
+ "Cargo.toml",
64
+ "Cargo.lock",
65
+ "go.mod",
66
+ "go.sum",
67
+ "composer.json",
68
+ "Gemfile",
69
+ "Gemfile.lock",
70
+ "yarn.lock",
71
+ "pnpm-lock.yaml",
72
+ ]
73
+
74
+ # Search for dependency files
75
+ total_vulns = 0
76
+ for dep_file in dep_files:
77
+ for file_path in safe_rglob(inp.root, dep_file):
78
+ if file_path.is_file():
79
+ rel_path = file_path.relative_to(inp.root).as_posix()
80
+ if not self._should_skip_file(rel_path, inp):
81
+ file_findings, file_vulns = self._scan_dep_file(file_path, rel_path)
82
+ findings.extend(file_findings)
83
+ total_vulns += file_vulns
84
+
85
+ # Add summary
86
+ findings.append(
87
+ make_finding(
88
+ severity=Severity.INFO,
89
+ file="__summary__",
90
+ line_start=0,
91
+ title=f"Dependency Vulnerabilities: {total_vulns} found",
92
+ description=f"Checked dependencies for known CVEs, found {total_vulns} vulnerabilities",
93
+ evidence=f"Scanned {len([f for f in findings if 'Dependency' in f.title and 'VULNERABILITY' in f.title])} vulnerable packages",
94
+ )
95
+ )
96
+
97
+ result.status = AgentStatus.SUCCEEDED
98
+ result.findings = findings
99
+ result.data.update(
100
+ {
101
+ "total_vulnerabilities": total_vulns,
102
+ "vulnerable_packages": len([f for f in findings if "VULNERABILITY" in f.title]),
103
+ "needs_ai": False,
104
+ }
105
+ )
106
+ return
107
+
108
+ def _should_skip_file(self, file_path: str, inp: AgentInput) -> bool:
109
+ """Check if file should be skipped based on restrictions."""
110
+ from pathlib import PurePosixPath
111
+
112
+ # Check restrictions
113
+ restrictions = inp.config.get("restrictions", [])
114
+ for r in restrictions:
115
+ if r.get("enabled", True):
116
+ path = r["path"]
117
+ if file_path.startswith(path) or PurePosixPath(file_path).match(path):
118
+ if r["type"] == "NO_TOUCH":
119
+ return True
120
+ elif r["type"] == "SCAN_ONLY" and self.__class__.__name__ == "FixAgent":
121
+ return True
122
+ return False
123
+
124
+ def _scan_dep_file(self, file_path: Path, rel_path: str) -> tuple[list[Finding], int]:
125
+ """Scan a dependency file for vulnerabilities."""
126
+ findings = []
127
+ vuln_count = 0
128
+
129
+ try:
130
+ content = file_path.read_text(encoding="utf-8")
131
+
132
+ if file_path.name == "package.json":
133
+ findings, vuln_count = self._scan_package_json(file_path, rel_path, content)
134
+ elif file_path.name == "requirements.txt":
135
+ findings, vuln_count = self._scan_requirements_txt(file_path, rel_path, content)
136
+ elif file_path.name == "Pipfile":
137
+ findings, vuln_count = self._scan_pipfile(file_path, rel_path, content)
138
+ elif file_path.name == "poetry.lock":
139
+ findings, vuln_count = self._scan_poetry_lock(file_path, rel_path, content)
140
+ elif file_path.name in ["Cargo.toml", "Cargo.lock"]:
141
+ findings, vuln_count = self._scan_cargo_file(file_path, rel_path, content)
142
+ elif file_path.name == "go.mod":
143
+ findings, vuln_count = self._scan_go_mod(file_path, rel_path, content)
144
+ elif file_path.name == "composer.json":
145
+ findings, vuln_count = self._scan_composer_json(file_path, rel_path, content)
146
+ elif file_path.name in ["Gemfile", "Gemfile.lock"]:
147
+ findings, vuln_count = self._scan_gemfile(file_path, rel_path, content)
148
+ elif file_path.name in ["yarn.lock", "pnpm-lock.yaml"]:
149
+ findings, vuln_count = self._scan_lock_file(file_path, rel_path, content)
150
+
151
+ findings.append(
152
+ make_finding(
153
+ severity=Severity.INFO,
154
+ file=rel_path,
155
+ line_start=0,
156
+ title=f"Dependency File Scanned: {file_path.name}",
157
+ description=f"Checked {file_path.name} for vulnerabilities",
158
+ evidence=f"File: {file_path.name}",
159
+ )
160
+ )
161
+
162
+ except Exception as e:
163
+ findings.append(
164
+ make_finding(
165
+ severity=Severity.LOW,
166
+ file=rel_path,
167
+ line_start=0,
168
+ title="Dependency file read error",
169
+ description=f"Could not read dependency file {file_path.name}: {str(e)}",
170
+ evidence=str(e),
171
+ )
172
+ )
173
+
174
+ return findings, vuln_count
175
+
176
+ def _scan_package_json(
177
+ self, file_path: Path, rel_path: str, content: str
178
+ ) -> tuple[list[Finding], int]:
179
+ """Scan package.json for dependencies and vulnerabilities."""
180
+ findings = []
181
+ vuln_count = 0
182
+
183
+ try:
184
+ data = json.loads(content)
185
+ deps = data.get("dependencies", {})
186
+ dev_deps = data.get("devDependencies", {})
187
+
188
+ # Combine all dependencies
189
+ all_deps = {}
190
+ all_deps.update(dict(deps.items()))
191
+ all_deps.update(dict(dev_deps.items()))
192
+
193
+ for name, version in all_deps.items():
194
+ findings.append(
195
+ make_finding(
196
+ severity=Severity.INFO,
197
+ file=rel_path,
198
+ line_start=0,
199
+ title=f"Dependency: {name}@{version}",
200
+ description="JavaScript dependency",
201
+ evidence=f"Version: {version}",
202
+ )
203
+ )
204
+
205
+ # Check for vulnerabilities via OSV API
206
+ vulns = self._check_npm_vulnerabilities(name, version)
207
+ for vuln in vulns:
208
+ vuln_count += 1
209
+ severity = self._map_osv_severity(
210
+ vuln.get("severity", [{}])[0].get("score", "UNKNOWN")
211
+ )
212
+ findings.append(
213
+ make_finding(
214
+ severity=severity,
215
+ file=rel_path,
216
+ line_start=0,
217
+ title=f"VULNERABILITY: {name}@{version}",
218
+ description=vuln.get("summary", "Unknown vulnerability"),
219
+ evidence=json.dumps(vuln),
220
+ )
221
+ )
222
+
223
+ except json.JSONDecodeError as e:
224
+ findings.append(
225
+ make_finding(
226
+ severity=Severity.HIGH,
227
+ file=rel_path,
228
+ line_start=e.lineno,
229
+ title="Invalid package.json",
230
+ description=f"JSON syntax error: {str(e)}",
231
+ evidence=e.msg,
232
+ )
233
+ )
234
+
235
+ return findings, vuln_count
236
+
237
+ def _scan_requirements_txt(
238
+ self, file_path: Path, rel_path: str, content: str
239
+ ) -> tuple[list[Finding], int]:
240
+ """Scan requirements.txt for dependencies."""
241
+ findings = []
242
+ vuln_count = 0
243
+
244
+ lines = content.splitlines()
245
+ for i, line in enumerate(lines, 1):
246
+ line = line.strip()
247
+ if line and not line.startswith("#") and not line.startswith("-"):
248
+ # Extract package name and version
249
+ if "==" in line:
250
+ pkg_name, version = line.split("==", 1)
251
+ elif ">=" in line:
252
+ pkg_name, version = line.split(">=", 1)
253
+ elif "<=" in line:
254
+ pkg_name, version = line.split("<=", 1)
255
+ elif ">" in line:
256
+ pkg_name, version = line.split(">", 1)
257
+ elif "<" in line:
258
+ pkg_name, version = line.split("<", 1)
259
+ else:
260
+ pkg_name, version = line, "unknown"
261
+
262
+ pkg_name = pkg_name.strip()
263
+ version = version.strip()
264
+
265
+ findings.append(
266
+ make_finding(
267
+ severity=Severity.INFO,
268
+ file=rel_path,
269
+ line_start=i,
270
+ title=f"Dependency: {pkg_name}@{version}",
271
+ description="Python dependency",
272
+ evidence=line,
273
+ )
274
+ )
275
+
276
+ # Check for vulnerabilities via OSV API
277
+ vulns = self._check_pypi_vulnerabilities(pkg_name, version)
278
+ for vuln in vulns:
279
+ vuln_count += 1
280
+ severity = self._map_osv_severity(
281
+ vuln.get("severity", [{}])[0].get("score", "UNKNOWN")
282
+ )
283
+ findings.append(
284
+ make_finding(
285
+ severity=severity,
286
+ file=rel_path,
287
+ line_start=i,
288
+ title=f"VULNERABILITY: {pkg_name}@{version}",
289
+ description=vuln.get("summary", "Unknown vulnerability"),
290
+ evidence=json.dumps(vuln),
291
+ )
292
+ )
293
+
294
+ return findings, vuln_count
295
+
296
+ def _scan_pipfile(
297
+ self, file_path: Path, rel_path: str, content: str
298
+ ) -> tuple[list[Finding], int]:
299
+ """Scan Pipfile for dependencies."""
300
+ findings = []
301
+ vuln_count = 0
302
+
303
+ # Pipfiles are TOML format, but Python < 3.11 doesn't have built-in TOML support
304
+ # So we'll use regex for basic parsing
305
+ lines = content.splitlines()
306
+ current_section = ""
307
+
308
+ for i, line in enumerate(lines, 1):
309
+ line = line.strip()
310
+
311
+ # Identify section
312
+ if line.startswith("["):
313
+ current_section = line.strip("[]").strip()
314
+ continue
315
+
316
+ # Look for package declarations in the right sections
317
+ if current_section in ["packages", "dev-packages"]:
318
+ import re
319
+
320
+ pkg_match = re.match(r"^(\w[-\w]*)\s*=\s*", line)
321
+ if pkg_match:
322
+ pkg_name = pkg_match.group(1)
323
+ # Extract version from the rest of the line
324
+ version_part = line[len(pkg_name) :].strip("= ").strip('"')
325
+ findings.append(
326
+ make_finding(
327
+ severity=Severity.INFO,
328
+ file=rel_path,
329
+ line_start=i,
330
+ title=f"Dependency: {pkg_name}@{version_part}",
331
+ description="Python dependency from Pipfile",
332
+ evidence=line,
333
+ )
334
+ )
335
+
336
+ return findings, vuln_count
337
+
338
+ def _scan_poetry_lock(
339
+ self, file_path: Path, rel_path: str, content: str
340
+ ) -> tuple[list[Finding], int]:
341
+ """Scan poetry.lock for dependencies."""
342
+ findings = []
343
+ vuln_count = 0
344
+
345
+ # Poetry lock files are TOML format
346
+ lines = content.splitlines()
347
+ current_package = {}
348
+
349
+ for i, line in enumerate(lines, 1):
350
+ line = line.strip()
351
+
352
+ # Look for package entries
353
+ if line.startswith("[[package]]"):
354
+ # Process previous package if exists
355
+ if current_package:
356
+ pkg_name = current_package.get("name", "unknown")
357
+ version = current_package.get("version", "unknown")
358
+ findings.append(
359
+ make_finding(
360
+ severity=Severity.INFO,
361
+ file=rel_path,
362
+ line_start=i,
363
+ title=f"Dependency: {pkg_name}@{version}",
364
+ description="Python dependency from Poetry lock",
365
+ evidence=f"{pkg_name}=={version}",
366
+ )
367
+ )
368
+
369
+ current_package = {}
370
+ elif "=" in line and current_package is not None:
371
+ # Parse key=value pairs
372
+ key, value = line.split("=", 1)
373
+ key = key.strip().strip('"')
374
+ value = value.strip().strip('"')
375
+ current_package[key] = value
376
+
377
+ # Process the last package
378
+ if current_package:
379
+ pkg_name = current_package.get("name", "unknown")
380
+ version = current_package.get("version", "unknown")
381
+ findings.append(
382
+ make_finding(
383
+ severity=Severity.INFO,
384
+ file=rel_path,
385
+ line_start=len(lines),
386
+ title=f"Dependency: {pkg_name}@{version}",
387
+ description="Python dependency from Poetry lock",
388
+ evidence=f"{pkg_name}=={version}",
389
+ )
390
+ )
391
+
392
+ return findings, vuln_count
393
+
394
+ def _scan_cargo_file(
395
+ self, file_path: Path, rel_path: str, content: str
396
+ ) -> tuple[list[Finding], int]:
397
+ """Scan Cargo.toml or Cargo.lock for dependencies."""
398
+ findings = []
399
+ vuln_count = 0
400
+
401
+ lines = content.splitlines()
402
+ in_deps_section = False
403
+
404
+ for i, line in enumerate(lines, 1):
405
+ line = line.strip()
406
+
407
+ # Identify dependencies section
408
+ if line.startswith("[dependencies]") or line.startswith("[dev-dependencies]"):
409
+ in_deps_section = True
410
+ continue
411
+ elif line.startswith("[") and in_deps_section:
412
+ in_deps_section = False
413
+
414
+ # Look for package declarations in dependencies section
415
+ if in_deps_section and "=" in line:
416
+ import re
417
+
418
+ pkg_match = re.match(r"^(\w[\w\-_]*)\s*=", line)
419
+ if pkg_match:
420
+ pkg_name = pkg_match.group(1)
421
+ # Extract version from the rest of the line
422
+ version_part = line[len(pkg_name) :].strip("= ").strip('"')
423
+ findings.append(
424
+ make_finding(
425
+ severity=Severity.INFO,
426
+ file=rel_path,
427
+ line_start=i,
428
+ title=f"Dependency: {pkg_name}@{version_part}",
429
+ description="Rust dependency",
430
+ evidence=line,
431
+ )
432
+ )
433
+
434
+ return findings, vuln_count
435
+
436
+ def _scan_go_mod(
437
+ self, file_path: Path, rel_path: str, content: str
438
+ ) -> tuple[list[Finding], int]:
439
+ """Scan go.mod for dependencies."""
440
+ findings = []
441
+ vuln_count = 0
442
+
443
+ lines = content.splitlines()
444
+
445
+ for i, line in enumerate(lines, 1):
446
+ line = line.strip()
447
+
448
+ # Look for require statements
449
+ if line.startswith("require "):
450
+ # Extract package and version
451
+ req_parts = line[8:].strip().split() # Remove "require " prefix
452
+ if len(req_parts) >= 2:
453
+ pkg_name = req_parts[0]
454
+ version = req_parts[1]
455
+ findings.append(
456
+ make_finding(
457
+ severity=Severity.INFO,
458
+ file=rel_path,
459
+ line_start=i,
460
+ title=f"Dependency: {pkg_name}@{version}",
461
+ description="Go dependency",
462
+ evidence=line,
463
+ )
464
+ )
465
+
466
+ return findings, vuln_count
467
+
468
+ def _scan_composer_json(
469
+ self, file_path: Path, rel_path: str, content: str
470
+ ) -> tuple[list[Finding], int]:
471
+ """Scan composer.json for PHP dependencies."""
472
+ findings = []
473
+ vuln_count = 0
474
+
475
+ try:
476
+ data = json.loads(content)
477
+ deps = data.get("require", {})
478
+ dev_deps = data.get("require-dev", {})
479
+
480
+ # Combine all dependencies
481
+ all_deps = {}
482
+ all_deps.update(dict(deps.items()))
483
+ all_deps.update(dict(dev_deps.items()))
484
+
485
+ for name, version in all_deps.items():
486
+ findings.append(
487
+ make_finding(
488
+ severity=Severity.INFO,
489
+ file=rel_path,
490
+ line_start=0,
491
+ title=f"Dependency: {name}@{version}",
492
+ description="PHP dependency",
493
+ evidence=f"Version: {version}",
494
+ )
495
+ )
496
+
497
+ except json.JSONDecodeError as e:
498
+ findings.append(
499
+ make_finding(
500
+ severity=Severity.HIGH,
501
+ file=rel_path,
502
+ line_start=e.lineno,
503
+ title="Invalid composer.json",
504
+ description=f"JSON syntax error: {str(e)}",
505
+ evidence=e.msg,
506
+ )
507
+ )
508
+
509
+ return findings, vuln_count
510
+
511
+ def _scan_gemfile(
512
+ self, file_path: Path, rel_path: str, content: str
513
+ ) -> tuple[list[Finding], int]:
514
+ """Scan Gemfile or Gemfile.lock for Ruby dependencies."""
515
+ findings = []
516
+ vuln_count = 0
517
+
518
+ lines = content.splitlines()
519
+
520
+ for i, line in enumerate(lines, 1):
521
+ line = line.strip()
522
+
523
+ # Look for gem statements
524
+ if "gem " in line and not line.startswith("#"):
525
+ import re
526
+
527
+ # Extract gem name
528
+ gem_match = re.search(r'gem\s+["\']([^"\']+)["\']', line)
529
+ if gem_match:
530
+ gem_name = gem_match.group(1)
531
+ findings.append(
532
+ make_finding(
533
+ severity=Severity.INFO,
534
+ file=rel_path,
535
+ line_start=i,
536
+ title=f"Dependency: {gem_name}",
537
+ description="Ruby dependency",
538
+ evidence=line,
539
+ )
540
+ )
541
+
542
+ return findings, vuln_count
543
+
544
+ def _scan_lock_file(
545
+ self, file_path: Path, rel_path: str, content: str
546
+ ) -> tuple[list[Finding], int]:
547
+ """Scan lock files (yarn.lock, pnpm-lock.yaml) for dependencies."""
548
+ findings = []
549
+ vuln_count = 0
550
+
551
+ lines = content.splitlines()
552
+
553
+ # Different parsing for different lock file types
554
+ if file_path.name == "yarn.lock":
555
+ # Yarn lock files have "package@version" entries
556
+ for i, line in enumerate(lines, 1):
557
+ if line.strip().endswith(":") and "@" in line:
558
+ # Extract package and version
559
+ pkg_part = line.strip().rstrip(":")
560
+ if "@" in pkg_part:
561
+ pkg_name = pkg_part.split("@")[0]
562
+ findings.append(
563
+ make_finding(
564
+ severity=Severity.INFO,
565
+ file=rel_path,
566
+ line_start=i,
567
+ title=f"Dependency: {pkg_name}",
568
+ description="JavaScript dependency from lock file",
569
+ evidence=pkg_part,
570
+ )
571
+ )
572
+ elif file_path.name == "pnpm-lock.yaml":
573
+ # PNPM lock files have similar format to yarn
574
+ for i, line in enumerate(lines, 1):
575
+ if line.strip().endswith(":") and "/" in line:
576
+ # Extract package name
577
+ pkg_part = line.strip().rstrip(":")
578
+ if "/" in pkg_part and "@" in pkg_part:
579
+ pkg_name = pkg_part.split("/")[-1].split("@")[0]
580
+ findings.append(
581
+ make_finding(
582
+ severity=Severity.INFO,
583
+ file=rel_path,
584
+ line_start=i,
585
+ title=f"Dependency: {pkg_name}",
586
+ description="JavaScript dependency from pnpm lock",
587
+ evidence=pkg_part,
588
+ )
589
+ )
590
+
591
+ return findings, vuln_count
592
+
593
+ def _check_npm_vulnerabilities(self, package_name: str, version: str) -> list[dict]:
594
+ """Check npm package for vulnerabilities via OSV API (cached)."""
595
+ return cached_osv_query(package_name, version, "npm", self._root)
596
+
597
+ def _check_pypi_vulnerabilities(self, package_name: str, version: str) -> list[dict]:
598
+ """Check PyPI package for vulnerabilities via OSV API (cached)."""
599
+ return cached_osv_query(package_name, version, "PyPI", self._root)
600
+
601
+ def _map_osv_severity(self, osv_severity: str) -> Severity:
602
+ """Map OSV severity to our severity levels."""
603
+ if not osv_severity or osv_severity == "UNKNOWN":
604
+ return Severity.LOW
605
+
606
+ # Convert to uppercase for comparison
607
+ sev = osv_severity.upper()
608
+
609
+ if sev == "CRITICAL":
610
+ return Severity.CRITICAL
611
+ elif sev == "HIGH":
612
+ return Severity.HIGH
613
+ elif sev == "MEDIUM":
614
+ return Severity.MEDIUM
615
+ else:
616
+ return Severity.LOW