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.
- patchi/__init__.py +1 -0
- patchi/__main__.py +13 -0
- patchi/cli/__init__.py +0 -0
- patchi/cli/command_families.py +235 -0
- patchi/cli/commands/__init__.py +0 -0
- patchi/cli/commands/access_cmd.py +65 -0
- patchi/cli/commands/agent_stats_cmd.py +273 -0
- patchi/cli/commands/agents_cmd.py +187 -0
- patchi/cli/commands/ai_cmd.py +308 -0
- patchi/cli/commands/assure_cmd.py +538 -0
- patchi/cli/commands/audit_cmd.py +286 -0
- patchi/cli/commands/auto_cmd.py +110 -0
- patchi/cli/commands/blame_cmd.py +53 -0
- patchi/cli/commands/chains_cmd.py +327 -0
- patchi/cli/commands/charter_cmd.py +173 -0
- patchi/cli/commands/chat_cmd.py +597 -0
- patchi/cli/commands/check_cmd.py +232 -0
- patchi/cli/commands/cleanup_cmd.py +328 -0
- patchi/cli/commands/cockpit_cmd.py +208 -0
- patchi/cli/commands/commands_cmd.py +160 -0
- patchi/cli/commands/cross_repo_cmd.py +113 -0
- patchi/cli/commands/deps_cmd.py +110 -0
- patchi/cli/commands/dev_check_cmd.py +342 -0
- patchi/cli/commands/dev_cmd.py +445 -0
- patchi/cli/commands/doctor_cmd.py +457 -0
- patchi/cli/commands/findings_cmd.py +187 -0
- patchi/cli/commands/fix_cmd.py +648 -0
- patchi/cli/commands/fix_review_cmd.py +273 -0
- patchi/cli/commands/heatmap_cmd.py +62 -0
- patchi/cli/commands/help_cmd.py +159 -0
- patchi/cli/commands/hosted_cmd.py +980 -0
- patchi/cli/commands/init.py +456 -0
- patchi/cli/commands/key_cmd.py +571 -0
- patchi/cli/commands/learn_cmd.py +140 -0
- patchi/cli/commands/link_cmd.py +104 -0
- patchi/cli/commands/log_cmd.py +57 -0
- patchi/cli/commands/memory_cmd.py +215 -0
- patchi/cli/commands/model_cmd.py +207 -0
- patchi/cli/commands/notify_cmd.py +213 -0
- patchi/cli/commands/notify_prompts.py +29 -0
- patchi/cli/commands/patch_cmd.py +231 -0
- patchi/cli/commands/plan_cmd.py +128 -0
- patchi/cli/commands/plugins_cmd.py +307 -0
- patchi/cli/commands/queue_cmd.py +152 -0
- patchi/cli/commands/quick_cmd.py +159 -0
- patchi/cli/commands/ready_cmd.py +292 -0
- patchi/cli/commands/report_cmd.py +416 -0
- patchi/cli/commands/restrict_cmd.py +127 -0
- patchi/cli/commands/review_cmd.py +225 -0
- patchi/cli/commands/rules_cmd.py +233 -0
- patchi/cli/commands/scan_cmd.py +1908 -0
- patchi/cli/commands/settings_cmd.py +45 -0
- patchi/cli/commands/status_cmd.py +679 -0
- patchi/cli/commands/test_cmd.py +1052 -0
- patchi/cli/commands/trend_cmd.py +296 -0
- patchi/cli/commands/undo_cmd.py +235 -0
- patchi/cli/commands/update_cmd.py +345 -0
- patchi/cli/commands/verify_cmd.py +56 -0
- patchi/cli/commands/vr_cmd.py +440 -0
- patchi/cli/commands/watch_cmd.py +304 -0
- patchi/cli/commands/web_cmd.py +114 -0
- patchi/cli/console.py +101 -0
- patchi/cli/display/__init__.py +1 -0
- patchi/cli/display/live_audit.py +269 -0
- patchi/cli/display/live_progress.py +169 -0
- patchi/cli/framework.py +241 -0
- patchi/cli/logo.py +128 -0
- patchi/cli/main.py +222 -0
- patchi/cli/registry.py +1171 -0
- patchi/cli/style.py +68 -0
- patchi/cli/themes.py +187 -0
- patchi/cli/ux.py +444 -0
- patchi/core/__init__.py +0 -0
- patchi/core/agents/__init__.py +0 -0
- patchi/core/agents/api_fuzzer_agent.py +141 -0
- patchi/core/agents/attack_agent.py +242 -0
- patchi/core/agents/base.py +1012 -0
- patchi/core/agents/bug_predictor_agent.py +58 -0
- patchi/core/agents/build_tool_validator.py +150 -0
- patchi/core/agents/cache.py +192 -0
- patchi/core/agents/chaos_agent.py +133 -0
- patchi/core/agents/cicd_generator.py +240 -0
- patchi/core/agents/comment_scanner.py +280 -0
- patchi/core/agents/console_logging_agent.py +156 -0
- patchi/core/agents/coordinator.py +858 -0
- patchi/core/agents/core_scanner.py +269 -0
- patchi/core/agents/coverage_prioritizer.py +181 -0
- patchi/core/agents/dead_code_hygiene.py +296 -0
- patchi/core/agents/dead_code_scanner.py +692 -0
- patchi/core/agents/dependency_scanner.py +725 -0
- patchi/core/agents/doc_claim_agent.py +573 -0
- patchi/core/agents/duplicate_scanner.py +568 -0
- patchi/core/agents/env_scanner.py +410 -0
- patchi/core/agents/feature_flag_agent.py +89 -0
- patchi/core/agents/frontend_framework_agent.py +133 -0
- patchi/core/agents/goroutine_agent.py +82 -0
- patchi/core/agents/governor.py +1972 -0
- patchi/core/agents/i18n_agent.py +95 -0
- patchi/core/agents/license_compliance.py +247 -0
- patchi/core/agents/linking_agent.py +251 -0
- patchi/core/agents/memory_profiler_agent.py +102 -0
- patchi/core/agents/modernization_agent.py +84 -0
- patchi/core/agents/mutation_agent.py +174 -0
- patchi/core/agents/nl_query_agent.py +74 -0
- patchi/core/agents/promise_rejection_agent.py +100 -0
- patchi/core/agents/refactoring_agent.py +286 -0
- patchi/core/agents/resource_leak_agent.py +91 -0
- patchi/core/agents/route_graph_scanner.py +951 -0
- patchi/core/agents/run_agent.py +106 -0
- patchi/core/agents/rust_unwrap_agent.py +100 -0
- patchi/core/agents/sbom_generator.py +284 -0
- patchi/core/agents/scanners.py +67 -0
- patchi/core/agents/side/__init__.py +1 -0
- patchi/core/agents/side/build_agent.py +65 -0
- patchi/core/agents/side/format_agent.py +50 -0
- patchi/core/agents/side/install_agent.py +63 -0
- patchi/core/agents/side_file_scanner.py +549 -0
- patchi/core/agents/snapshot_drift_detector.py +146 -0
- patchi/core/agents/spa_route_inventory.py +203 -0
- patchi/core/agents/test_scanner.py +442 -0
- patchi/core/agents/tool_health.py +109 -0
- patchi/core/agents/tool_runner.py +162 -0
- patchi/core/agents/type_scanner.py +168 -0
- patchi/core/agents/ui_scanner.py +558 -0
- patchi/core/ai/__init__.py +20 -0
- patchi/core/ai/agent_audit.py +153 -0
- patchi/core/ai/agent_profiler.py +277 -0
- patchi/core/ai/client.py +499 -0
- patchi/core/ai/cost_tracker.py +107 -0
- patchi/core/ai/model_router.py +318 -0
- patchi/core/ai/orchestrator.py +625 -0
- patchi/core/ai/prompts.py +681 -0
- patchi/core/ai/smart.py +302 -0
- patchi/core/ai/tool_executor.py +24 -0
- patchi/core/ai/tools/__init__.py +10 -0
- patchi/core/ai/tools/executor.py +621 -0
- patchi/core/ai/tools/realize.py +1160 -0
- patchi/core/ai/tools/registry.py +1577 -0
- patchi/core/assurance/__init__.py +33 -0
- patchi/core/assurance/builder.py +211 -0
- patchi/core/assurance/graph.py +342 -0
- patchi/core/assurance/invariants.py +168 -0
- patchi/core/atomic.py +61 -0
- patchi/core/attackers/__init__.py +17 -0
- patchi/core/attackers/api_abuse.py +89 -0
- patchi/core/attackers/auth_bypass.py +73 -0
- patchi/core/attackers/base.py +91 -0
- patchi/core/attackers/business_logic.py +88 -0
- patchi/core/attackers/planner.py +108 -0
- patchi/core/attackers/priv_escalation.py +88 -0
- patchi/core/attackers/recon.py +87 -0
- patchi/core/brain/__init__.py +0 -0
- patchi/core/brain/ast_utils/__init__.py +81 -0
- patchi/core/brain/ast_utils/assignments.py +219 -0
- patchi/core/brain/ast_utils/calls.py +139 -0
- patchi/core/brain/ast_utils/config.py +132 -0
- patchi/core/brain/ast_utils/control_flow.py +74 -0
- patchi/core/brain/ast_utils/dead_symbols.py +205 -0
- patchi/core/brain/ast_utils/decorators.py +108 -0
- patchi/core/brain/ast_utils/helpers.py +144 -0
- patchi/core/brain/ast_utils/imports.py +169 -0
- patchi/core/brain/ast_utils/scan.py +143 -0
- patchi/core/brain/ast_utils/taint.py +310 -0
- patchi/core/brain/audit.py +379 -0
- patchi/core/brain/baseline.py +195 -0
- patchi/core/brain/blast_radius.py +312 -0
- patchi/core/brain/body_tags.py +144 -0
- patchi/core/brain/brain.py +1161 -0
- patchi/core/brain/brain_context.py +393 -0
- patchi/core/brain/brain_watcher.py +340 -0
- patchi/core/brain/charter.py +473 -0
- patchi/core/brain/classifier.py +214 -0
- patchi/core/brain/cockpit.py +350 -0
- patchi/core/brain/code_query.py +710 -0
- patchi/core/brain/contract.py +889 -0
- patchi/core/brain/contract_diff.py +155 -0
- patchi/core/brain/council.py +800 -0
- patchi/core/brain/cross_repo.py +104 -0
- patchi/core/brain/dead_code_tools.py +157 -0
- patchi/core/brain/doc_validator.py +197 -0
- patchi/core/brain/domain_activator.py +2183 -0
- patchi/core/brain/enriched_context.py +220 -0
- patchi/core/brain/file_corpus.py +220 -0
- patchi/core/brain/framework.py +953 -0
- patchi/core/brain/freshness.py +273 -0
- patchi/core/brain/git_aware.py +206 -0
- patchi/core/brain/heatmap.py +104 -0
- patchi/core/brain/ignore_parser.py +153 -0
- patchi/core/brain/import_graph.py +486 -0
- patchi/core/brain/languages.py +397 -0
- patchi/core/brain/layered_brain.py +419 -0
- patchi/core/brain/learning.py +229 -0
- patchi/core/brain/orphaned_endpoints.py +313 -0
- patchi/core/brain/personas/__init__.py +648 -0
- patchi/core/brain/personas/bad_user.py +165 -0
- patchi/core/brain/personas/base.py +349 -0
- patchi/core/brain/proactive.py +807 -0
- patchi/core/brain/project_context.py +121 -0
- patchi/core/brain/project_reader.py +521 -0
- patchi/core/brain/reasoning.py +269 -0
- patchi/core/brain/route_detector/__init__.py +15 -0
- patchi/core/brain/route_detector/base.py +31 -0
- patchi/core/brain/route_detector/csharp.py +209 -0
- patchi/core/brain/route_detector/file_based.py +81 -0
- patchi/core/brain/route_detector/go.py +152 -0
- patchi/core/brain/route_detector/java.py +195 -0
- patchi/core/brain/route_detector/javascript.py +197 -0
- patchi/core/brain/route_detector/php.py +158 -0
- patchi/core/brain/route_detector/python.py +193 -0
- patchi/core/brain/route_detector/registry.py +25 -0
- patchi/core/brain/route_detector/ruby.py +134 -0
- patchi/core/brain/route_detector/rust.py +216 -0
- patchi/core/brain/route_detector/swift.py +118 -0
- patchi/core/brain/route_mapper.py +265 -0
- patchi/core/brain/scanner.py +2136 -0
- patchi/core/brain/secrets.py +280 -0
- patchi/core/brain/symbol_graph.py +1955 -0
- patchi/core/brain/team.py +28 -0
- patchi/core/brain/trace_log.py +132 -0
- patchi/core/brain/type_checker/__init__.py +65 -0
- patchi/core/brain/type_checker/base.py +60 -0
- patchi/core/brain/type_checker/csharp.py +59 -0
- patchi/core/brain/type_checker/dart.py +59 -0
- patchi/core/brain/type_checker/go.py +81 -0
- patchi/core/brain/type_checker/java.py +75 -0
- patchi/core/brain/type_checker/kotlin.py +75 -0
- patchi/core/brain/type_checker/php.py +88 -0
- patchi/core/brain/type_checker/python.py +87 -0
- patchi/core/brain/type_checker/rust.py +81 -0
- patchi/core/brain/type_checker/swift.py +59 -0
- patchi/core/brain/type_checker/typescript.py +298 -0
- patchi/core/brain/understander.py +130 -0
- patchi/core/brain/verify.py +379 -0
- patchi/core/campaigns/__init__.py +14 -0
- patchi/core/campaigns/base.py +62 -0
- patchi/core/campaigns/data_flow_abuse.py +153 -0
- patchi/core/campaigns/orchestrator.py +140 -0
- patchi/core/campaigns/state_transitions.py +178 -0
- patchi/core/ci_bundle.py +104 -0
- patchi/core/config.py +311 -0
- patchi/core/constants.py +245 -0
- patchi/core/debug/__init__.py +25 -0
- patchi/core/debug/adapters/__init__.py +11 -0
- patchi/core/debug/adapters/codelldb.py +379 -0
- patchi/core/debug/adapters/node.py +222 -0
- patchi/core/debug/adapters/powershell.py +210 -0
- patchi/core/debug/adapters/python.py +244 -0
- patchi/core/debug/capture.py +210 -0
- patchi/core/debug/dap_client.py +268 -0
- patchi/core/detector/__init__.py +0 -0
- patchi/core/detector/audit.py +194 -0
- patchi/core/detector/bus.py +226 -0
- patchi/core/detector/dispatcher.py +275 -0
- patchi/core/detector/event.py +254 -0
- patchi/core/detector/sigma_engine.py +378 -0
- patchi/core/detector/triage.py +289 -0
- patchi/core/export/sarif.py +113 -0
- patchi/core/fix/__init__.py +17 -0
- patchi/core/fix/applier.py +574 -0
- patchi/core/fix/base.py +510 -0
- patchi/core/fix/code_fixer.py +169 -0
- patchi/core/fix/dead_code_remover.py +131 -0
- patchi/core/fix/fix_agents.py +775 -0
- patchi/core/fix/patch.py +444 -0
- patchi/core/fix/risk_gate.py +383 -0
- patchi/core/fix/security_fixer.py +93 -0
- patchi/core/fix/verify_loop.py +204 -0
- patchi/core/fuzz/__init__.py +8 -0
- patchi/core/fuzz/corpus.py +137 -0
- patchi/core/fuzz/input_fuzzer.py +256 -0
- patchi/core/fuzz/sequence_fuzzer.py +162 -0
- patchi/core/fuzz/state_fuzzer.py +236 -0
- patchi/core/health.py +407 -0
- patchi/core/hosted/__init__.py +1 -0
- patchi/core/hosted/anomaly.py +298 -0
- patchi/core/hosted/audit_log.py +102 -0
- patchi/core/hosted/compliance_report.py +148 -0
- patchi/core/hosted/ip_reputation.py +169 -0
- patchi/core/hosted/log_parsers.py +251 -0
- patchi/core/hosted/tokens.py +127 -0
- patchi/core/hosted/watchlist.py +164 -0
- patchi/core/hosted/webhooks.py +156 -0
- patchi/core/memory.py +437 -0
- patchi/core/notifications/__init__.py +18 -0
- patchi/core/notifications/channels.py +192 -0
- patchi/core/notifications/digest.py +141 -0
- patchi/core/notifications/escalation.py +158 -0
- patchi/core/notifications/notifier.py +182 -0
- patchi/core/notifications/quiet_hours.py +69 -0
- patchi/core/plugins/__init__.py +48 -0
- patchi/core/plugins/analyzer.py +326 -0
- patchi/core/plugins/analyzers/__init__.py +6 -0
- patchi/core/plugins/analyzers/dead_code_analyzer.py +120 -0
- patchi/core/plugins/analyzers/secret_scanner.py +158 -0
- patchi/core/plugins/analyzers/todo_scanner.py +115 -0
- patchi/core/plugins/registry.py +335 -0
- patchi/core/queue.py +375 -0
- patchi/core/reliability/__init__.py +13 -0
- patchi/core/reliability/chaos.py +163 -0
- patchi/core/reliability/fault_injector.py +172 -0
- patchi/core/reliability/idempotency.py +128 -0
- patchi/core/reliability/recovery.py +147 -0
- patchi/core/runtime/__init__.py +5 -0
- patchi/core/runtime/tracer.py +230 -0
- patchi/core/scan_bus.py +156 -0
- patchi/core/security/__init__.py +1 -0
- patchi/core/security/ai_validator.py +462 -0
- patchi/core/security/app_mapper.py +216 -0
- patchi/core/security/app_profile.py +464 -0
- patchi/core/security/attack_feedback.py +141 -0
- patchi/core/security/attack_scenarios/__init__.py +1 -0
- patchi/core/security/attack_tree.py +176 -0
- patchi/core/security/auth_audit_agent.py +344 -0
- patchi/core/security/authz_agent.py +602 -0
- patchi/core/security/auto_fix_proactive.py +620 -0
- patchi/core/security/auto_fixer.py +660 -0
- patchi/core/security/auto_ticket.py +53 -0
- patchi/core/security/bandit_agent.py +152 -0
- patchi/core/security/blast_radius.py +226 -0
- patchi/core/security/browser_tester.py +333 -0
- patchi/core/security/business_logic_agent.py +263 -0
- patchi/core/security/catch_block_auditor.py +395 -0
- patchi/core/security/cdn_cache_agent.py +357 -0
- patchi/core/security/chain_analyzer.py +321 -0
- patchi/core/security/chain_to_assurance.py +168 -0
- patchi/core/security/charter.py +510 -0
- patchi/core/security/cloud_waf_detector.py +295 -0
- patchi/core/security/codeql_agent.py +233 -0
- patchi/core/security/compliance_agent.py +348 -0
- patchi/core/security/confidence_gate.py +423 -0
- patchi/core/security/container_scanner.py +282 -0
- patchi/core/security/crypto_agent.py +378 -0
- patchi/core/security/cve_monitor.py +262 -0
- patchi/core/security/dast_agent.py +865 -0
- patchi/core/security/defectdojo.py +238 -0
- patchi/core/security/defenders/__init__.py +75 -0
- patchi/core/security/defenders/auth_middleware.py +115 -0
- patchi/core/security/defenders/base.py +97 -0
- patchi/core/security/defenders/block_ip.py +71 -0
- patchi/core/security/defenders/block_ws_origin.py +36 -0
- patchi/core/security/defenders/crypto_fix.py +118 -0
- patchi/core/security/defenders/enforce_rate_limit.py +108 -0
- patchi/core/security/defenders/fix_code.py +93 -0
- patchi/core/security/defenders/invalidate_session.py +40 -0
- patchi/core/security/defenders/patch_config.py +70 -0
- patchi/core/security/defenders/rotate_secret.py +54 -0
- patchi/core/security/defenders/suspend_account.py +41 -0
- patchi/core/security/defenders/update_dependency.py +160 -0
- patchi/core/security/defense_layer.py +217 -0
- patchi/core/security/dependency_vulnerability_agent.py +616 -0
- patchi/core/security/detection_pipeline.py +289 -0
- patchi/core/security/dns_security_agent.py +497 -0
- patchi/core/security/domain_activator_v2.py +913 -0
- patchi/core/security/domain_loader.py +1004 -0
- patchi/core/security/domains/__init__.py +0 -0
- patchi/core/security/email_authentication_agent.py +417 -0
- patchi/core/security/env_var_validator.py +304 -0
- patchi/core/security/evidence.py +100 -0
- patchi/core/security/falco_runtime_agent.py +387 -0
- patchi/core/security/fix-playbooks/__init__.py +0 -0
- patchi/core/security/gated_finding.py +85 -0
- patchi/core/security/git_diff_activator.py +367 -0
- patchi/core/security/governance.py +208 -0
- patchi/core/security/gradual_gate.py +34 -0
- patchi/core/security/history.py +315 -0
- patchi/core/security/iac_scanner.py +250 -0
- patchi/core/security/ignore_expiry.py +111 -0
- patchi/core/security/ignore_learner.py +416 -0
- patchi/core/security/injection_agent.py +319 -0
- patchi/core/security/insecure_randomness_agent.py +288 -0
- patchi/core/security/intent_analyzer.py +326 -0
- patchi/core/security/jwt_agent.py +453 -0
- patchi/core/security/kubernetes_agent.py +638 -0
- patchi/core/security/layer2_orchestrator.py +341 -0
- patchi/core/security/llm_security_agent.py +252 -0
- patchi/core/security/misconfig_agent.py +600 -0
- patchi/core/security/mobile_security_agent.py +474 -0
- patchi/core/security/network_agent.py +376 -0
- patchi/core/security/noise_filter.py +274 -0
- patchi/core/security/orchestrator.py +313 -0
- patchi/core/security/pattern_context.py +125 -0
- patchi/core/security/pattern_loader.py +101 -0
- patchi/core/security/pentest/__init__.py +4 -0
- patchi/core/security/pentest/base.py +58 -0
- patchi/core/security/pentest/dalfox_adapter.py +40 -0
- patchi/core/security/pentest/ffuf_adapter.py +48 -0
- patchi/core/security/pentest/nuclei_adapter.py +50 -0
- patchi/core/security/pentest/registry.py +80 -0
- patchi/core/security/pentest/shannon_adapter.py +107 -0
- patchi/core/security/pentest/sqlmap_adapter.py +56 -0
- patchi/core/security/pentest/zap_adapter.py +52 -0
- patchi/core/security/plan_auditor.py +276 -0
- patchi/core/security/policy_engine.py +272 -0
- patchi/core/security/prechecks.py +172 -0
- patchi/core/security/precommit_hook.py +247 -0
- patchi/core/security/privacy_agent.py +361 -0
- patchi/core/security/push_notification_agent.py +417 -0
- patchi/core/security/pysa_agent.py +143 -0
- patchi/core/security/reasoning.py +442 -0
- patchi/core/security/red_team_agent.py +184 -0
- patchi/core/security/red_team_engine.py +1142 -0
- patchi/core/security/remediation.py +360 -0
- patchi/core/security/request_interceptor.py +294 -0
- patchi/core/security/runtime_validator.py +245 -0
- patchi/core/security/saml_sso_agent.py +401 -0
- patchi/core/security/sast_agent.py +213 -0
- patchi/core/security/sast_gate.py +95 -0
- patchi/core/security/scheduler.py +287 -0
- patchi/core/security/secrets_runtime_agent.py +476 -0
- patchi/core/security/security_agents.py +175 -0
- patchi/core/security/security_config.py +551 -0
- patchi/core/security/security_probe.py +762 -0
- patchi/core/security/security_taint.py +1005 -0
- patchi/core/security/sensitive_data_agent.py +271 -0
- patchi/core/security/service_mesh_agent.py +412 -0
- patchi/core/security/session_management_agent.py +245 -0
- patchi/core/security/ssrf_agent.py +422 -0
- patchi/core/security/supply_chain.py +449 -0
- patchi/core/security/threat_model_generator.py +378 -0
- patchi/core/security/threat_model_updater.py +196 -0
- patchi/core/security/tool_adapters.py +135 -0
- patchi/core/security/tool_verify.py +173 -0
- patchi/core/security/websocket_security_agent.py +188 -0
- patchi/core/snapshot.py +242 -0
- patchi/core/tenant.py +386 -0
- patchi/core/testing/__init__.py +17 -0
- patchi/core/testing/_browser.py +147 -0
- patchi/core/testing/ads_agent.py +141 -0
- patchi/core/testing/api_contract_agent.py +450 -0
- patchi/core/testing/app_discovery_agent.py +641 -0
- patchi/core/testing/app_launcher.py +184 -0
- patchi/core/testing/browser_test_agent.py +516 -0
- patchi/core/testing/e2e_flow_agent.py +354 -0
- patchi/core/testing/flake_detector_agent.py +292 -0
- patchi/core/testing/gate.py +31 -0
- patchi/core/testing/live_test_runner.py +304 -0
- patchi/core/testing/live_v2/__init__.py +1 -0
- patchi/core/testing/live_v2/browser_pool.py +349 -0
- patchi/core/testing/live_v2/browser_test_runner.py +313 -0
- patchi/core/testing/live_v2/runner.py +626 -0
- patchi/core/testing/live_v2/screenshot_manager.py +474 -0
- patchi/core/testing/live_v2/stress_orchestrator.py +523 -0
- patchi/core/testing/live_v2/video_recorder.py +303 -0
- patchi/core/testing/regression_agent.py +302 -0
- patchi/core/testing/security_test_agent.py +253 -0
- patchi/core/testing/stress_test_agent.py +203 -0
- patchi/core/testing/test_agents.py +50 -0
- patchi/core/testing/test_config.py +194 -0
- patchi/core/testing/ui_accessibility_agent.py +324 -0
- patchi/core/testing/ui_button_agent.py +294 -0
- patchi/core/testing/ui_layout_agent.py +330 -0
- patchi/core/testing/unit_test_agent.py +1149 -0
- patchi/core/testing/visual_regression_agent.py +260 -0
- patchi/web/__init__.py +1 -0
- patchi/web/api/__init__.py +1 -0
- patchi/web/api/brain_map.py +114 -0
- patchi/web/api/charts.py +47 -0
- patchi/web/api/chat.py +224 -0
- patchi/web/api/cicd.py +576 -0
- patchi/web/api/dast_evidence.py +49 -0
- patchi/web/api/dev_check.py +320 -0
- patchi/web/api/fix.py +134 -0
- patchi/web/api/guard.py +65 -0
- patchi/web/api/hosted.py +128 -0
- patchi/web/api/hosted_v2.py +234 -0
- patchi/web/api/live_testing.py +699 -0
- patchi/web/api/scan.py +582 -0
- patchi/web/api/smart.py +111 -0
- patchi/web/api/tenant.py +117 -0
- patchi/web/api/tools.py +85 -0
- patchi/web/api_legacy.py +2401 -0
- patchi/web/app.py +310 -0
- patchi/web/events.py +483 -0
- patchi/web/routes/__init__.py +1 -0
- patchi/web/routes/assurance.py +541 -0
- patchi/web/routes/brain.py +36 -0
- patchi/web/routes/charter.py +227 -0
- patchi/web/routes/chat.py +23 -0
- patchi/web/routes/dashboard.py +558 -0
- patchi/web/routes/dashboard_v2.py +402 -0
- patchi/web/routes/findings.py +330 -0
- patchi/web/routes/guard.py +35 -0
- patchi/web/routes/history.py +126 -0
- patchi/web/routes/landing.py +52 -0
- patchi/web/routes/live_testing.py +24 -0
- patchi/web/routes/review.py +57 -0
- patchi/web/routes/self_improvement.py +110 -0
- patchi/web/routes/settings.py +49 -0
- patchi/web/routes/tokens.py +29 -0
- patchi/web/spawn.py +139 -0
- patchi/web/ws.py +141 -0
- patchi-0.7.5.dist-info/METADATA +315 -0
- patchi-0.7.5.dist-info/RECORD +496 -0
- patchi-0.7.5.dist-info/WHEEL +5 -0
- patchi-0.7.5.dist-info/licenses/LICENSE +60 -0
- 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
|