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,135 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tool Adapters — shared normalization for external SAST tools (bandit, pysa, codeql).
|
|
3
|
+
|
|
4
|
+
Every wrapper agent used to hand-build Finding objects and crash on the
|
|
5
|
+
first finding (Severity("MEDIUM") vs lowercase enum; nonexistent
|
|
6
|
+
``confidence=`` kwarg). This module is the single honest conversion layer:
|
|
7
|
+
|
|
8
|
+
normalize_severity(raw) -> Severity (every known convention)
|
|
9
|
+
make_tool_finding(...) -> Finding (valid fields, provenance kept)
|
|
10
|
+
|
|
11
|
+
Max-potential details preserved instead of discarded:
|
|
12
|
+
- the TOOL's own confidence tier (bandit HIGH/MEDIUM/LOW) lands in
|
|
13
|
+
``extra["tool_confidence"]`` so the ConfidenceGate can weigh it;
|
|
14
|
+
- a code snippet rides along in ``code_snippet`` — the gate's
|
|
15
|
+
method-precision scorer needs it, and AI review gets real context;
|
|
16
|
+
- numeric SARIF security-severity (0.0-10.0) maps to severity bands.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
from typing import Any
|
|
22
|
+
|
|
23
|
+
from patchi.core.agents.base import Finding, Severity
|
|
24
|
+
|
|
25
|
+
# ── Severity normalization ───────────────────────────────────────────────────
|
|
26
|
+
|
|
27
|
+
_WORD_MAP: dict[str, str] = {
|
|
28
|
+
# bandit / generic uppercase
|
|
29
|
+
"LOW": "low",
|
|
30
|
+
"MEDIUM": "medium",
|
|
31
|
+
"HIGH": "high",
|
|
32
|
+
"CRITICAL": "critical",
|
|
33
|
+
# already-lowercase passthrough
|
|
34
|
+
"low": "low",
|
|
35
|
+
"medium": "medium",
|
|
36
|
+
"high": "high",
|
|
37
|
+
"critical": "critical",
|
|
38
|
+
"info": "info",
|
|
39
|
+
"informational": "info",
|
|
40
|
+
# compiler-style levels (pysa/codeql text output)
|
|
41
|
+
"ERROR": "high",
|
|
42
|
+
"WARNING": "medium",
|
|
43
|
+
"WARN": "medium",
|
|
44
|
+
"NOTE": "info",
|
|
45
|
+
"NOTICE": "info",
|
|
46
|
+
"SEGMENT": "info",
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
_DEFAULT = "medium"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def normalize_severity(raw: Any, default: str = _DEFAULT) -> Severity:
|
|
53
|
+
"""Map any tool's severity spelling to our Severity enum. Never raises."""
|
|
54
|
+
if isinstance(raw, (int, float)):
|
|
55
|
+
return Severity(_numeric_band(float(raw)))
|
|
56
|
+
value = str(raw).strip() if raw is not None else ""
|
|
57
|
+
mapped = _WORD_MAP.get(value)
|
|
58
|
+
if mapped is None:
|
|
59
|
+
# last resort: case-insensitive lookup
|
|
60
|
+
mapped = _WORD_MAP.get(value.upper(), default)
|
|
61
|
+
if mapped not in ("low", "medium", "high", "critical", "info"):
|
|
62
|
+
mapped = default
|
|
63
|
+
try:
|
|
64
|
+
return Severity(mapped)
|
|
65
|
+
except ValueError:
|
|
66
|
+
return Severity(default)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _numeric_band(score_0_to_10: float) -> str:
|
|
70
|
+
"""CodeQL SARIF security-severity banding."""
|
|
71
|
+
if score_0_to_10 >= 9.0:
|
|
72
|
+
return "critical"
|
|
73
|
+
if score_0_to_10 >= 7.0:
|
|
74
|
+
return "high"
|
|
75
|
+
if score_0_to_10 >= 4.0:
|
|
76
|
+
return "medium"
|
|
77
|
+
return "low"
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
# ── Tool confidence tiers ────────────────────────────────────────────────────
|
|
81
|
+
|
|
82
|
+
_TIER_MAP = {"HIGH": 0.9, "MEDIUM": 0.6, "LOW": 0.3}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def tool_confidence(raw: Any, default: float = 0.5) -> float:
|
|
86
|
+
"""Bandit-style HIGH/MEDIUM/LOW tier -> float; passthrough numbers."""
|
|
87
|
+
if isinstance(raw, (int, float)):
|
|
88
|
+
return max(0.0, min(1.0, float(raw)))
|
|
89
|
+
return _TIER_MAP.get(str(raw).strip().upper(), default)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
# ── Finding construction ─────────────────────────────────────────────────────
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def make_tool_finding(
|
|
96
|
+
agent: str,
|
|
97
|
+
ftype: str,
|
|
98
|
+
raw_severity: Any,
|
|
99
|
+
file: str,
|
|
100
|
+
line: int,
|
|
101
|
+
message: str,
|
|
102
|
+
cwe: str = "",
|
|
103
|
+
snippet: str = "",
|
|
104
|
+
confidence_raw: Any = None,
|
|
105
|
+
extra: dict | None = None,
|
|
106
|
+
) -> Finding:
|
|
107
|
+
"""Build one valid Finding from a raw tool record. Never raises for data."""
|
|
108
|
+
payload_extra: dict = {"tool": agent}
|
|
109
|
+
conf = None
|
|
110
|
+
if confidence_raw is not None:
|
|
111
|
+
conf = tool_confidence(confidence_raw)
|
|
112
|
+
payload_extra["tool_confidence"] = conf
|
|
113
|
+
if extra:
|
|
114
|
+
payload_extra.update(extra)
|
|
115
|
+
|
|
116
|
+
# Normalize bare-number / int CWEs ("89", 89) to "CWE-89"
|
|
117
|
+
cwe_str = str(cwe or "").strip()
|
|
118
|
+
if cwe_str and cwe_str.isdigit():
|
|
119
|
+
cwe_str = f"CWE-{int(cwe_str)}"
|
|
120
|
+
|
|
121
|
+
kwargs: dict[str, Any] = {}
|
|
122
|
+
if snippet:
|
|
123
|
+
kwargs["code_snippet"] = str(snippet)[:500]
|
|
124
|
+
|
|
125
|
+
return Finding(
|
|
126
|
+
agent=agent,
|
|
127
|
+
type=(ftype or f"{agent.lower()}_finding")[:80],
|
|
128
|
+
severity=normalize_severity(raw_severity),
|
|
129
|
+
file=file or "",
|
|
130
|
+
line=int(line or 0),
|
|
131
|
+
message=str(message or "")[:400],
|
|
132
|
+
cwe=cwe_str,
|
|
133
|
+
extra=payload_extra,
|
|
134
|
+
**kwargs,
|
|
135
|
+
)
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tool verification — close the fix loop with the same SAST tools that found the bug.
|
|
3
|
+
|
|
4
|
+
After the AutoFixer writes a patch, we want real evidence the vulnerability is
|
|
5
|
+
gone, not a simulated "True". This module runs Bandit or Semgrep against the
|
|
6
|
+
*patched* file and reports whether the original finding still appears.
|
|
7
|
+
|
|
8
|
+
Both tools are invoked through their existing Patchi agents so we inherit the
|
|
9
|
+
exact same severity/confidence/CWE normalization the scanner uses elsewhere.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import logging
|
|
15
|
+
import shutil
|
|
16
|
+
import tempfile
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
from patchi.core.agents.base import AgentInput, Finding, Severity
|
|
20
|
+
|
|
21
|
+
_log = logging.getLogger("patchi.security.tool_verify")
|
|
22
|
+
|
|
23
|
+
# Map the playbook `deterministic_tool` name -> registered agent name.
|
|
24
|
+
_TOOL_AGENT = {
|
|
25
|
+
"bandit": "BanditAgent",
|
|
26
|
+
"semgrep": "SemgrepAgent",
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _extract_identity(finding: Finding | object) -> tuple[str, str]:
|
|
31
|
+
"""Return (cwe, type) for a Finding or CorrelatedFinding-like object."""
|
|
32
|
+
cwe = ""
|
|
33
|
+
ftype = ""
|
|
34
|
+
for attr in ("cwe", "type"):
|
|
35
|
+
if hasattr(finding, attr):
|
|
36
|
+
val = getattr(finding, attr)
|
|
37
|
+
if attr == "cwe":
|
|
38
|
+
cwe = str(val or "")
|
|
39
|
+
else:
|
|
40
|
+
ftype = str(val or "")
|
|
41
|
+
elif hasattr(finding, "finding"):
|
|
42
|
+
val = getattr(finding.finding, attr, "")
|
|
43
|
+
if attr == "cwe":
|
|
44
|
+
cwe = str(val or "")
|
|
45
|
+
else:
|
|
46
|
+
ftype = str(val or "")
|
|
47
|
+
return cwe, ftype
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def run_tool_on_file(tool: str, file_path: Path) -> list[Finding]:
|
|
51
|
+
"""Run one SAST tool against a single file via an isolated temp copy.
|
|
52
|
+
|
|
53
|
+
Copying the file into a temp dir keeps the scan scoped to just that file
|
|
54
|
+
(fast, no project-wide noise) while still exercising the real tool binary
|
|
55
|
+
through its Patchi agent wrapper.
|
|
56
|
+
"""
|
|
57
|
+
import patchi.core.security.security_agents # noqa: F401 (registers agents)
|
|
58
|
+
from patchi.core.agents.base import list_agents
|
|
59
|
+
|
|
60
|
+
agent_name = _TOOL_AGENT.get(tool.lower())
|
|
61
|
+
if not agent_name:
|
|
62
|
+
raise ValueError(f"tool_verify: unsupported tool {tool!r}")
|
|
63
|
+
|
|
64
|
+
agents = {a.name: a for a in list_agents("security")}
|
|
65
|
+
cls = agents.get(agent_name)
|
|
66
|
+
if not cls:
|
|
67
|
+
_log.warning("tool_verify: agent %s not available", agent_name)
|
|
68
|
+
return []
|
|
69
|
+
|
|
70
|
+
src = Path(file_path)
|
|
71
|
+
if not src.exists():
|
|
72
|
+
return []
|
|
73
|
+
|
|
74
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
75
|
+
tmp_path = Path(tmp)
|
|
76
|
+
dest = tmp_path / src.name
|
|
77
|
+
shutil.copy2(src, dest)
|
|
78
|
+
res = cls().run(AgentInput(root=tmp_path, scope=[], brain={}, config={}, extra={}))
|
|
79
|
+
return list(res.findings or [])
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def finding_resolved(tool: str, file_path: Path, original: Finding | object) -> bool:
|
|
83
|
+
"""True if `original` no longer appears in the tool's output for the file.
|
|
84
|
+
|
|
85
|
+
Matching prefers CWE (most specific). When the tool reports no findings at
|
|
86
|
+
all we treat the issue as resolved.
|
|
87
|
+
"""
|
|
88
|
+
findings = run_tool_on_file(tool, file_path)
|
|
89
|
+
if not findings:
|
|
90
|
+
return True
|
|
91
|
+
|
|
92
|
+
orig_cwe, orig_type = _extract_identity(original)
|
|
93
|
+
orig_short = orig_type.split(".")[-1] if orig_type else ""
|
|
94
|
+
|
|
95
|
+
for f in findings:
|
|
96
|
+
if orig_cwe and f.cwe and f.cwe.upper() == orig_cwe.upper():
|
|
97
|
+
return False
|
|
98
|
+
if orig_short and f.type and orig_short in f.type:
|
|
99
|
+
return False
|
|
100
|
+
return True
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def high_findings_on_file(file_path: Path) -> list[Finding]:
|
|
104
|
+
"""Run Bandit + Semgrep on a single file; return only HIGH/CRITICAL findings.
|
|
105
|
+
|
|
106
|
+
Used to verify that an applied proactive fix did not introduce or leave a
|
|
107
|
+
serious vulnerability. Returns an empty list on tool failure (fail-open) so
|
|
108
|
+
a slow/missing tool never blocks the watch loop. The returned findings carry
|
|
109
|
+
the real (non-temp) file path.
|
|
110
|
+
"""
|
|
111
|
+
out: list[Finding] = []
|
|
112
|
+
real = str(file_path)
|
|
113
|
+
for tool in ("bandit", "semgrep"):
|
|
114
|
+
try:
|
|
115
|
+
findings = run_tool_on_file(tool, file_path)
|
|
116
|
+
except Exception as e:
|
|
117
|
+
_log.warning("high_findings_on_file: %s failed on %s: %s", tool, file_path, e)
|
|
118
|
+
continue
|
|
119
|
+
for f in findings:
|
|
120
|
+
if f.severity in (Severity.HIGH, Severity.CRITICAL):
|
|
121
|
+
try:
|
|
122
|
+
f.file = real
|
|
123
|
+
except Exception as _exc:
|
|
124
|
+
_log.warning('high_findings_on_file failed: %s', _exc)
|
|
125
|
+
out.append(f)
|
|
126
|
+
return out
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def verify_proactive_fixes(
|
|
130
|
+
root: Path,
|
|
131
|
+
applied_files: list[str],
|
|
132
|
+
pre_highs: dict[str, set],
|
|
133
|
+
) -> list[dict]:
|
|
134
|
+
"""Diff SAST HIGH/CRITICAL findings before vs after applied proactive fixes.
|
|
135
|
+
|
|
136
|
+
For each file a proactive fix touched, re-runs Bandit + Semgrep and reports
|
|
137
|
+
only findings that are NEW relative to ``pre_highs`` (so pre-existing
|
|
138
|
+
findings are never re-flagged on every save). Returns a list of issue dicts
|
|
139
|
+
ready for ``mem.save_issue``. Fail-open: any tool failure yields no issue.
|
|
140
|
+
"""
|
|
141
|
+
issues: list[dict] = []
|
|
142
|
+
for vf in sorted(set(applied_files)):
|
|
143
|
+
vpath = Path(root) / vf
|
|
144
|
+
if not vpath.exists() or vpath.suffix != ".py":
|
|
145
|
+
continue
|
|
146
|
+
try:
|
|
147
|
+
post_findings = high_findings_on_file(vpath)
|
|
148
|
+
except Exception as e:
|
|
149
|
+
_log.warning("verify_proactive_fixes skipped %s: %s", vf, e)
|
|
150
|
+
continue
|
|
151
|
+
post = {(h.type, h.cwe) for h in post_findings}
|
|
152
|
+
new_sigs = post - pre_highs.get(vf, set())
|
|
153
|
+
if not new_sigs:
|
|
154
|
+
continue
|
|
155
|
+
for hf in post_findings:
|
|
156
|
+
if (hf.type, hf.cwe) not in new_sigs:
|
|
157
|
+
continue
|
|
158
|
+
issues.append(
|
|
159
|
+
{
|
|
160
|
+
"source": "proactive_sast_verify",
|
|
161
|
+
"fix_type": "sast_regression",
|
|
162
|
+
"file": vf,
|
|
163
|
+
"name": hf.cwe or hf.type,
|
|
164
|
+
"line": hf.line,
|
|
165
|
+
"description": (
|
|
166
|
+
f"Proactive fix introduced a HIGH/CRITICAL SAST "
|
|
167
|
+
f"finding ({hf.type}{' ' + hf.cwe if hf.cwe else ''}) "
|
|
168
|
+
f"at line {hf.line}"
|
|
169
|
+
),
|
|
170
|
+
"severity": "high",
|
|
171
|
+
}
|
|
172
|
+
)
|
|
173
|
+
return issues
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
"""
|
|
2
|
+
WebSocketSecurityAgent — WebSocket security vulnerability detection.
|
|
3
|
+
|
|
4
|
+
Detects:
|
|
5
|
+
- Unencrypted WebSocket connections (ws:// instead of wss://)
|
|
6
|
+
- Missing origin validation
|
|
7
|
+
- Unvalidated message input (injection via WebSocket messages)
|
|
8
|
+
- Missing authentication in WebSocket handshake
|
|
9
|
+
- Unlimited message size / rate
|
|
10
|
+
- WebSocket message type confusion
|
|
11
|
+
- Sensitive data in WebSocket messages
|
|
12
|
+
|
|
13
|
+
Uses static pattern matching, NOT AI.
|
|
14
|
+
Does NOT write to disk. Does NOT touch the queue.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import re
|
|
20
|
+
|
|
21
|
+
from ..agents.base import (
|
|
22
|
+
AgentDomain,
|
|
23
|
+
AgentGroup,
|
|
24
|
+
AgentInput,
|
|
25
|
+
AgentResult,
|
|
26
|
+
BaseAgent,
|
|
27
|
+
Finding,
|
|
28
|
+
Severity,
|
|
29
|
+
register,
|
|
30
|
+
safe_rglob,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@register
|
|
35
|
+
class WebSocketSecurityAgent(BaseAgent):
|
|
36
|
+
"""Detects WebSocket security vulnerabilities."""
|
|
37
|
+
|
|
38
|
+
group = AgentGroup.SECURITY
|
|
39
|
+
domain = AgentDomain.SECURITY
|
|
40
|
+
name = "WebSocketSecurityAgent"
|
|
41
|
+
description = "WebSocket injection, origin validation, unencrypted WS, message validation"
|
|
42
|
+
|
|
43
|
+
def _run(self, inp: AgentInput, result: AgentResult) -> None:
|
|
44
|
+
# Only scan files likely to contain WebSocket code (not every .py in the project)
|
|
45
|
+
patterns = ["*.py", "*.js", "*.ts", "*.jsx", "*.tsx", "*.java", "*.go"]
|
|
46
|
+
|
|
47
|
+
# Pre-scan: skip files that lack any WebSocket keyword (fast path)
|
|
48
|
+
ws_guard = re.compile(
|
|
49
|
+
r"(?i)(?:websocket|wss?://|socket\.io|fastapi\.websocket|starlette\.websocket|flask-socketio|django\.channels|aiohttp\.web\.WebSocketResponse|tornado\.websocket|\.ws\.route)",
|
|
50
|
+
re.I,
|
|
51
|
+
)
|
|
52
|
+
for pat in patterns:
|
|
53
|
+
for fpath in safe_rglob(inp.root, pat):
|
|
54
|
+
if not fpath.is_file():
|
|
55
|
+
continue
|
|
56
|
+
rel = fpath.relative_to(inp.root).as_posix()
|
|
57
|
+
try:
|
|
58
|
+
content = fpath.read_text(encoding="utf-8", errors="ignore")
|
|
59
|
+
except OSError:
|
|
60
|
+
continue
|
|
61
|
+
if not ws_guard.search(content):
|
|
62
|
+
continue
|
|
63
|
+
result.files_scanned += 1
|
|
64
|
+
self._scan_websocket(content, rel, result)
|
|
65
|
+
|
|
66
|
+
def _scan_websocket(self, content: str, rel: str, result: AgentResult) -> None:
|
|
67
|
+
# ── Unencrypted WebSocket connection ────────────────────────────────
|
|
68
|
+
for m in re.finditer(r"""(?i)["']ws://""", content):
|
|
69
|
+
line = content[: m.start()].count("\n") + 1
|
|
70
|
+
result.add_finding(
|
|
71
|
+
Finding(
|
|
72
|
+
agent=self.name,
|
|
73
|
+
type="unencrypted_websocket",
|
|
74
|
+
severity=Severity.HIGH,
|
|
75
|
+
file=rel,
|
|
76
|
+
line=line,
|
|
77
|
+
message="Unencrypted WebSocket connection (ws://) — use wss:// for production",
|
|
78
|
+
suggestion="Replace ws:// with wss:// and ensure TLS is configured",
|
|
79
|
+
cwe="CWE-319",
|
|
80
|
+
extra={"skill": "api-security.skill"},
|
|
81
|
+
)
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
# ── Missing origin validation ───────────────────────────────────────
|
|
85
|
+
if not re.search(
|
|
86
|
+
r"(?i)(?:\borigin\b|check_origin|allowed_origins|validate_origin)", content
|
|
87
|
+
):
|
|
88
|
+
result.add_finding(
|
|
89
|
+
Finding(
|
|
90
|
+
agent=self.name,
|
|
91
|
+
type="missing_origin_validation",
|
|
92
|
+
severity=Severity.MEDIUM,
|
|
93
|
+
file=rel,
|
|
94
|
+
line=0,
|
|
95
|
+
message="WebSocket handler lacks origin validation — potential cross-origin hijacking",
|
|
96
|
+
suggestion="Validate Origin header on WebSocket upgrade, configure allowed_origins",
|
|
97
|
+
cwe="CWE-346",
|
|
98
|
+
extra={"skill": "api-security.skill"},
|
|
99
|
+
)
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
# ── Message without input validation ───────────────────────────────
|
|
103
|
+
for m in re.finditer(
|
|
104
|
+
r'(?i)(?:websocket|socketio)\.(?:on_message|receive|recv|on\s*\(\s*["\']message)',
|
|
105
|
+
content,
|
|
106
|
+
):
|
|
107
|
+
line_start = content[: m.start()].count("\n") + 1
|
|
108
|
+
surrounding = content[m.start() : m.start() + 500]
|
|
109
|
+
if not re.search(
|
|
110
|
+
r"(?i)(?:validate|sanitize|check|json\.loads|parse)", surrounding[:200]
|
|
111
|
+
):
|
|
112
|
+
result.add_finding(
|
|
113
|
+
Finding(
|
|
114
|
+
agent=self.name,
|
|
115
|
+
type="websocket_no_input_validation",
|
|
116
|
+
severity=Severity.HIGH,
|
|
117
|
+
file=rel,
|
|
118
|
+
line=line_start,
|
|
119
|
+
message="WebSocket message processed without input validation — injection risk",
|
|
120
|
+
suggestion="Validate/sanitize all WebSocket message content before processing",
|
|
121
|
+
cwe="CWE-20",
|
|
122
|
+
extra={"skill": "api-security.skill"},
|
|
123
|
+
)
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
# ── Missing authentication on WebSocket upgrade ────────────────────
|
|
127
|
+
for m in re.finditer(
|
|
128
|
+
r"(?i)(?:@(?:ws|websocket)\.route|\bdef\s+websocket\b|\basync\s+def\s+websocket\b|socketio\.on\b)",
|
|
129
|
+
content,
|
|
130
|
+
):
|
|
131
|
+
line_start = content[: m.start()].count("\n") + 1
|
|
132
|
+
surrounding = content[m.start() : m.start() + 300]
|
|
133
|
+
if not re.search(
|
|
134
|
+
r"(?i)(?:auth|token|jwt|session|api_key|authenticate)", surrounding[:150]
|
|
135
|
+
):
|
|
136
|
+
result.add_finding(
|
|
137
|
+
Finding(
|
|
138
|
+
agent=self.name,
|
|
139
|
+
type="websocket_no_auth",
|
|
140
|
+
severity=Severity.CRITICAL,
|
|
141
|
+
file=rel,
|
|
142
|
+
line=line_start,
|
|
143
|
+
message="WebSocket endpoint lacks authentication — unauthenticated access",
|
|
144
|
+
suggestion="Add authentication handshake (JWT, session, or API key) to WebSocket upgrade",
|
|
145
|
+
cwe="CWE-287",
|
|
146
|
+
extra={"skill": "api-security.skill"},
|
|
147
|
+
)
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
# ── Message size / rate limiting missing ───────────────────────────
|
|
151
|
+
for m in re.finditer(
|
|
152
|
+
r'(?i)(?:@app\.ws|\bwebsocket_route\b|\.on\s*\(\s*["\']?connect)', content
|
|
153
|
+
):
|
|
154
|
+
line_start = content[: m.start()].count("\n") + 1
|
|
155
|
+
surrounding = content[m.start() : m.start() + 300]
|
|
156
|
+
if not re.search(r"(?i)(?:max_size|max_length|limit|throttle|rate)", surrounding[:150]):
|
|
157
|
+
result.add_finding(
|
|
158
|
+
Finding(
|
|
159
|
+
agent=self.name,
|
|
160
|
+
type="websocket_no_limit",
|
|
161
|
+
severity=Severity.MEDIUM,
|
|
162
|
+
file=rel,
|
|
163
|
+
line=line_start,
|
|
164
|
+
message="WebSocket missing message size/rate limiting — DoS vector",
|
|
165
|
+
suggestion="Configure max_message_size and rate limiting for WebSocket messages",
|
|
166
|
+
cwe="CWE-770",
|
|
167
|
+
extra={"skill": "api-security.skill"},
|
|
168
|
+
)
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
# ── Sensitive data sent over WebSocket ─────────────────────────────
|
|
172
|
+
for m in re.finditer(
|
|
173
|
+
r'(?i)(?:send|emit)\(.*["\']?(?:\bpassword\b|\btoken\b|\bsecret\b|api_key|secret_key|\bssn\b|\bcredit\b)',
|
|
174
|
+
content,
|
|
175
|
+
):
|
|
176
|
+
result.add_finding(
|
|
177
|
+
Finding(
|
|
178
|
+
agent=self.name,
|
|
179
|
+
type="sensitive_data_in_websocket",
|
|
180
|
+
severity=Severity.HIGH,
|
|
181
|
+
file=rel,
|
|
182
|
+
line=content[: m.start()].count("\n") + 1,
|
|
183
|
+
message="Sensitive data potentially sent over WebSocket — data leak risk",
|
|
184
|
+
suggestion="Avoid sending secrets/tokens over WebSocket; use server-side operations",
|
|
185
|
+
cwe="CWE-200",
|
|
186
|
+
extra={"skill": "api-security.skill"},
|
|
187
|
+
)
|
|
188
|
+
)
|