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,295 @@
|
|
|
1
|
+
"""Cloud WAF Detector — AWS WAF & Cloudflare WAF misconfiguration and bypass detection.
|
|
2
|
+
|
|
3
|
+
Scans project infrastructure code for WAF misconfigurations:
|
|
4
|
+
- Missing WAF on public-facing ALB/CloudFront/API Gateway
|
|
5
|
+
- Rate limits too permissive or absent
|
|
6
|
+
- Missing OWASP core rule set or managed rules
|
|
7
|
+
- Geo-blocking not configured for admin endpoints
|
|
8
|
+
- Cloudflare: missing bot management, browser integrity check, or rate limiting
|
|
9
|
+
- Cloudflare: wide-open page rules, disabled security in page rules
|
|
10
|
+
- AWS WAF: missing IP set restrictions on admin paths
|
|
11
|
+
|
|
12
|
+
Also detects WAF bypass attempts in intercepted requests:
|
|
13
|
+
- HTTP parameter pollution
|
|
14
|
+
- Encoding bypasses (double URL encoding, unicode, comments)
|
|
15
|
+
- HTTP method tunneling
|
|
16
|
+
- Case manipulation
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import logging
|
|
22
|
+
import re
|
|
23
|
+
|
|
24
|
+
from ..agents.base import (
|
|
25
|
+
AgentGroup,
|
|
26
|
+
AgentInput,
|
|
27
|
+
AgentResult,
|
|
28
|
+
AgentStatus,
|
|
29
|
+
BaseAgent,
|
|
30
|
+
Finding,
|
|
31
|
+
Severity,
|
|
32
|
+
register,
|
|
33
|
+
safe_rglob,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
_log = logging.getLogger("patchi.security.cloud_waf_detector")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@register
|
|
40
|
+
class CloudWAFDetector(BaseAgent):
|
|
41
|
+
"""Detect AWS WAF & Cloudflare WAF misconfigurations and bypass patterns."""
|
|
42
|
+
|
|
43
|
+
group = AgentGroup.SECURITY
|
|
44
|
+
name = "CloudWAFDetector"
|
|
45
|
+
description = "Cloud WAF misconfiguration and bypass detection"
|
|
46
|
+
|
|
47
|
+
# ── Cloudflare page rule patterns that disable security ──
|
|
48
|
+
CF_INSECURE_PAGE_RULES = re.compile(
|
|
49
|
+
r"(security\s*=\s*\"off\"|disable_security|security_level\s*=\s*\"essentially_off\")",
|
|
50
|
+
re.IGNORECASE,
|
|
51
|
+
)
|
|
52
|
+
CF_WIDE_OPEN_CACHE = re.compile(
|
|
53
|
+
r"(edge_cache_ttl\s*=\s*0|cache_level\s*=\s*\"bypass\")",
|
|
54
|
+
re.IGNORECASE,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
# ── Terraform AWS WAF patterns (positive match = issue) ──
|
|
58
|
+
MISSING_WAF_PATTERNS: list[tuple[str, str, str, Severity]] = [
|
|
59
|
+
(
|
|
60
|
+
"aws_alb",
|
|
61
|
+
r"resource\s+\"aws_alb\"",
|
|
62
|
+
"ALB without WAF association — add aws_wafv2_web_acl_association",
|
|
63
|
+
Severity.HIGH,
|
|
64
|
+
),
|
|
65
|
+
(
|
|
66
|
+
"aws_lb",
|
|
67
|
+
r"resource\s+\"aws_lb\"[^}]*?internal\s*=\s*false",
|
|
68
|
+
"Public ALB without WAF association — add aws_wafv2_web_acl_association",
|
|
69
|
+
Severity.HIGH,
|
|
70
|
+
),
|
|
71
|
+
(
|
|
72
|
+
"aws_cloudfront_distribution",
|
|
73
|
+
r"resource\s+\"aws_cloudfront_distribution\"",
|
|
74
|
+
"CloudFront without WAF — add web_acl_id referencing WAFv2 ACL",
|
|
75
|
+
Severity.HIGH,
|
|
76
|
+
),
|
|
77
|
+
(
|
|
78
|
+
"aws_api_gateway_rest_api",
|
|
79
|
+
r"resource\s+\"aws_api_gateway_rest_api\"",
|
|
80
|
+
"API Gateway without WAF — add WAFv2 ACL association",
|
|
81
|
+
Severity.MEDIUM,
|
|
82
|
+
),
|
|
83
|
+
]
|
|
84
|
+
|
|
85
|
+
WAF_WEAKNESSES: list[tuple[str, str, Severity]] = [
|
|
86
|
+
(
|
|
87
|
+
"No rate limiting configured",
|
|
88
|
+
r"rate_based_statement|rate_limit",
|
|
89
|
+
Severity.MEDIUM,
|
|
90
|
+
),
|
|
91
|
+
(
|
|
92
|
+
"No AWS managed rules",
|
|
93
|
+
r"managed_rule_group_statement|vendor_name\s*=\s*\"aws\"",
|
|
94
|
+
Severity.MEDIUM,
|
|
95
|
+
),
|
|
96
|
+
(
|
|
97
|
+
"No OWASP CRS",
|
|
98
|
+
r"core_rule_set|owasp.*crs|managed_rule.*owasp",
|
|
99
|
+
Severity.MEDIUM,
|
|
100
|
+
),
|
|
101
|
+
]
|
|
102
|
+
|
|
103
|
+
WAF_MISCONFIG_PATTERNS: list[tuple[str, str, str, Severity]] = [
|
|
104
|
+
(
|
|
105
|
+
"allow_all_default",
|
|
106
|
+
r"default_action\s*\{\s*allow\s*\}",
|
|
107
|
+
"WAF default action is ALLOW — should be BLOCK with explicit allow rules",
|
|
108
|
+
Severity.HIGH,
|
|
109
|
+
),
|
|
110
|
+
(
|
|
111
|
+
"no_ip_restriction_admin",
|
|
112
|
+
r"geo_match_statement|ip_set_reference_statement",
|
|
113
|
+
"No Geo/IP restriction on admin paths detected",
|
|
114
|
+
Severity.MEDIUM,
|
|
115
|
+
),
|
|
116
|
+
]
|
|
117
|
+
|
|
118
|
+
# ── Bypass patterns in request data ──
|
|
119
|
+
WAF_BYPASS_PATTERNS: list[tuple[str, str, Severity]] = [
|
|
120
|
+
(
|
|
121
|
+
"HTTP parameter pollution",
|
|
122
|
+
r"(&|\?)[^=]+=[^&]*&[^=]+=[^&]*(&[^=]+=[^&]*)+",
|
|
123
|
+
Severity.MEDIUM,
|
|
124
|
+
),
|
|
125
|
+
(
|
|
126
|
+
"Double URL encoding",
|
|
127
|
+
r"%25[0-9a-fA-F]{2}",
|
|
128
|
+
Severity.MEDIUM,
|
|
129
|
+
),
|
|
130
|
+
(
|
|
131
|
+
"Unicode encoding bypass",
|
|
132
|
+
r"\\u[0-9a-fA-F]{4}|%u[0-9a-fA-F]{4}",
|
|
133
|
+
Severity.LOW,
|
|
134
|
+
),
|
|
135
|
+
(
|
|
136
|
+
"HTTP method tunneling",
|
|
137
|
+
r"(X-HTTP-Method-Override|X-HTTP-Method|X-Method-Override):\s*\w+",
|
|
138
|
+
Severity.MEDIUM,
|
|
139
|
+
),
|
|
140
|
+
(
|
|
141
|
+
"Case manipulation bypass",
|
|
142
|
+
r"(<[Ss][Cc][Rr][Ii][Pp][Tt]>|<[Ss][Vv][Gg]/[Oo][Nn][Ll][Oo][Aa][Dd])",
|
|
143
|
+
Severity.LOW,
|
|
144
|
+
),
|
|
145
|
+
]
|
|
146
|
+
|
|
147
|
+
def _run(self, inp: AgentInput, result: AgentResult) -> None:
|
|
148
|
+
root = inp.root
|
|
149
|
+
findings: list[Finding] = []
|
|
150
|
+
|
|
151
|
+
# Phase 1: Scan IaC files for WAF misconfigs
|
|
152
|
+
# safe_rglob yields generators — materialize before concatenating.
|
|
153
|
+
tf_files = list(safe_rglob(root, "*.tf"))
|
|
154
|
+
cf_files = list(safe_rglob(root, "*.yaml")) + list(safe_rglob(root, "*.yml"))
|
|
155
|
+
ia_files = tf_files + [
|
|
156
|
+
f for f in cf_files if "cloudflare" in str(f).lower() or "waf" in str(f).lower()
|
|
157
|
+
]
|
|
158
|
+
|
|
159
|
+
for fp in ia_files:
|
|
160
|
+
try:
|
|
161
|
+
text = fp.read_text(encoding="utf-8", errors="replace")
|
|
162
|
+
except Exception as e:
|
|
163
|
+
_log.warning("CloudWAFDetector._run failed: %s", e)
|
|
164
|
+
continue
|
|
165
|
+
rel = str(fp.relative_to(root))
|
|
166
|
+
|
|
167
|
+
for res_name, pattern, msg, severity in self.MISSING_WAF_PATTERNS:
|
|
168
|
+
if re.search(pattern, text, re.IGNORECASE):
|
|
169
|
+
findings.append(
|
|
170
|
+
Finding(
|
|
171
|
+
agent=self.name,
|
|
172
|
+
type="missing_waf",
|
|
173
|
+
severity=severity,
|
|
174
|
+
file=rel,
|
|
175
|
+
message=f"{msg} ({res_name})",
|
|
176
|
+
cwe="CWE-693",
|
|
177
|
+
)
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
for name, pattern, severity in self.WAF_WEAKNESSES:
|
|
181
|
+
if not re.search(pattern, text, re.IGNORECASE):
|
|
182
|
+
findings.append(
|
|
183
|
+
Finding(
|
|
184
|
+
agent=self.name,
|
|
185
|
+
type="waf_weakness",
|
|
186
|
+
severity=severity,
|
|
187
|
+
file=rel,
|
|
188
|
+
message=f"WAF weakness: {name}",
|
|
189
|
+
cwe="CWE-693",
|
|
190
|
+
)
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
for name, pattern, msg, severity in self.WAF_MISCONFIG_PATTERNS:
|
|
194
|
+
for m in re.finditer(pattern, text, re.IGNORECASE):
|
|
195
|
+
findings.append(
|
|
196
|
+
Finding(
|
|
197
|
+
agent=self.name,
|
|
198
|
+
type="waf_" + name,
|
|
199
|
+
severity=severity,
|
|
200
|
+
file=rel,
|
|
201
|
+
message=msg,
|
|
202
|
+
snippet=m.group()[:80],
|
|
203
|
+
cwe="CWE-693",
|
|
204
|
+
)
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
# Cloudflare-specific checks
|
|
208
|
+
if fp.suffix == ".yaml":
|
|
209
|
+
m = self.CF_INSECURE_PAGE_RULES.search(text)
|
|
210
|
+
if m:
|
|
211
|
+
findings.append(
|
|
212
|
+
Finding(
|
|
213
|
+
agent=self.name,
|
|
214
|
+
type="cf_insecure_page_rule",
|
|
215
|
+
severity=Severity.HIGH,
|
|
216
|
+
file=rel,
|
|
217
|
+
message="Cloudflare page rule disables security",
|
|
218
|
+
snippet=m.group(),
|
|
219
|
+
cwe="CWE-693",
|
|
220
|
+
)
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
m = self.CF_WIDE_OPEN_CACHE.search(text)
|
|
224
|
+
if m:
|
|
225
|
+
findings.append(
|
|
226
|
+
Finding(
|
|
227
|
+
agent=self.name,
|
|
228
|
+
type="cf_bypass_cache",
|
|
229
|
+
severity=Severity.MEDIUM,
|
|
230
|
+
file=rel,
|
|
231
|
+
message="Cloudflare cache bypassed in page rule",
|
|
232
|
+
snippet=m.group(),
|
|
233
|
+
cwe="CWE-693",
|
|
234
|
+
)
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
# Phase 2: WAF bypass patterns in test/request files
|
|
238
|
+
test_files = list(safe_rglob(root, "*test*")) + list(safe_rglob(root, "*spec*"))
|
|
239
|
+
seen_bypass: set[tuple[str, str]] = set()
|
|
240
|
+
for fp in test_files:
|
|
241
|
+
if fp.suffix not in {".py", ".js", ".ts", ".http", ".txt", ".csv", ".json"}:
|
|
242
|
+
continue
|
|
243
|
+
try:
|
|
244
|
+
text = fp.read_text(encoding="utf-8", errors="replace")
|
|
245
|
+
except Exception as e:
|
|
246
|
+
_log.warning("CloudWAFDetector._run failed: %s", e)
|
|
247
|
+
continue
|
|
248
|
+
rel = str(fp.relative_to(root))
|
|
249
|
+
for name, pattern, severity in self.WAF_BYPASS_PATTERNS:
|
|
250
|
+
for m in re.finditer(pattern, text, re.IGNORECASE):
|
|
251
|
+
key = (rel, name)
|
|
252
|
+
if key in seen_bypass:
|
|
253
|
+
continue
|
|
254
|
+
seen_bypass.add(key)
|
|
255
|
+
findings.append(
|
|
256
|
+
Finding(
|
|
257
|
+
agent=self.name,
|
|
258
|
+
type="waf_bypass",
|
|
259
|
+
severity=severity,
|
|
260
|
+
file=rel,
|
|
261
|
+
message=f"WAF bypass pattern: {name}",
|
|
262
|
+
line=1 + text[: m.start()].count("\n"),
|
|
263
|
+
snippet=m.group()[:80],
|
|
264
|
+
cwe="CWE-693",
|
|
265
|
+
)
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
# Phase 3: CloudFront without origin access identity
|
|
269
|
+
seen_no_oai = False
|
|
270
|
+
for fp in tf_files:
|
|
271
|
+
try:
|
|
272
|
+
text = fp.read_text(encoding="utf-8", errors="replace")
|
|
273
|
+
except Exception as e:
|
|
274
|
+
_log.warning("CloudWAFDetector._run failed: %s", e)
|
|
275
|
+
continue
|
|
276
|
+
rel = str(fp.relative_to(root))
|
|
277
|
+
if re.search(r"resource\s+\"aws_cloudfront_distribution\"", text) and not re.search(
|
|
278
|
+
r"origin_access_identity|oai", text
|
|
279
|
+
):
|
|
280
|
+
if not seen_no_oai:
|
|
281
|
+
seen_no_oai = True
|
|
282
|
+
findings.append(
|
|
283
|
+
Finding(
|
|
284
|
+
agent=self.name,
|
|
285
|
+
type="cloudfront_no_oai",
|
|
286
|
+
severity=Severity.MEDIUM,
|
|
287
|
+
file=rel,
|
|
288
|
+
message="CloudFront distribution without origin access identity",
|
|
289
|
+
cwe="CWE-693",
|
|
290
|
+
)
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
result.findings = findings
|
|
294
|
+
result.status = AgentStatus.DONE
|
|
295
|
+
result.files_scanned = len(ia_files) + len(test_files)
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CodeQLAgent - CodeQL static analysis integration.
|
|
3
|
+
|
|
4
|
+
CodeQL is a semantic code analysis engine from GitHub that can find
|
|
5
|
+
vulnerabilities and code quality issues.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import logging
|
|
11
|
+
import subprocess
|
|
12
|
+
import tempfile
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
from patchi.core.agents.base import (
|
|
17
|
+
AgentDomain,
|
|
18
|
+
AgentGroup,
|
|
19
|
+
AgentInput,
|
|
20
|
+
AgentResult,
|
|
21
|
+
AgentStatus,
|
|
22
|
+
BaseAgent,
|
|
23
|
+
register,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
_log = logging.getLogger("patchi.security.agents.codeql_agent")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@register
|
|
30
|
+
class CodeqlAgent(BaseAgent):
|
|
31
|
+
"""
|
|
32
|
+
CodeQLAgent - CodeQL static analysis integration.
|
|
33
|
+
|
|
34
|
+
CodeQL is a semantic code analysis engine from GitHub that can find
|
|
35
|
+
vulnerabilities and code quality issues using a query-based approach.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
group = AgentGroup.SECURITY
|
|
39
|
+
domain = AgentDomain.SECURITY
|
|
40
|
+
name = "CodeqlAgent"
|
|
41
|
+
description = "CodeQL semantic analysis for vulnerabilities"
|
|
42
|
+
# Database build + QL compilation is genuinely slow (minutes even on tiny
|
|
43
|
+
# projects the first time). The old 180s timeout guaranteed silent zeros.
|
|
44
|
+
timeout = 3600
|
|
45
|
+
|
|
46
|
+
def _run(self, inp: AgentInput, result: AgentResult) -> None:
|
|
47
|
+
"""Run CodeQL analysis on the project."""
|
|
48
|
+
if not self._is_codeql_available():
|
|
49
|
+
result.status = AgentStatus.SKIPPED
|
|
50
|
+
result.data["error"] = "CodeQL CLI not installed"
|
|
51
|
+
return
|
|
52
|
+
|
|
53
|
+
findings = self._run_codeql(inp.root)
|
|
54
|
+
|
|
55
|
+
for finding_data in findings:
|
|
56
|
+
finding = self._create_finding(finding_data)
|
|
57
|
+
result.add_finding(finding)
|
|
58
|
+
|
|
59
|
+
result.status = AgentStatus.DONE
|
|
60
|
+
result.files_scanned = 1
|
|
61
|
+
|
|
62
|
+
def _is_codeql_available(self) -> bool:
|
|
63
|
+
"""Check if CodeQL CLI is installed and available."""
|
|
64
|
+
try:
|
|
65
|
+
result = subprocess.run(["codeql", "version"], capture_output=True, timeout=10)
|
|
66
|
+
return result.returncode == 0
|
|
67
|
+
except FileNotFoundError:
|
|
68
|
+
return False
|
|
69
|
+
|
|
70
|
+
def _resolve_query_suite(self) -> str:
|
|
71
|
+
"""Prefer a locally cached query pack; never hit the network.
|
|
72
|
+
|
|
73
|
+
``--download``/bare pack names trip a CLI pack-manifest bug
|
|
74
|
+
(UnrecognizedPropertyException on Docker digest) and need network.
|
|
75
|
+
The standard install caches packs under ~/.codeql/packages.
|
|
76
|
+
"""
|
|
77
|
+
candidates = sorted(
|
|
78
|
+
(Path.home() / ".codeql" / "packages" / "codeql" / "python-queries").glob(
|
|
79
|
+
"*/codeql-suites/python-security-extended.qls"
|
|
80
|
+
)
|
|
81
|
+
)
|
|
82
|
+
if candidates:
|
|
83
|
+
return str(candidates[-1])
|
|
84
|
+
return "python-security-extended"
|
|
85
|
+
|
|
86
|
+
def _run_codeql(self, root: Path) -> list[dict[str, Any]]:
|
|
87
|
+
"""Create a database, run the security suite, return parsed findings."""
|
|
88
|
+
findings: list[dict[str, Any]] = []
|
|
89
|
+
suite = self._resolve_query_suite()
|
|
90
|
+
|
|
91
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
92
|
+
db_dir = Path(tmpdir) / "codeql_db"
|
|
93
|
+
results_file = Path(tmpdir) / "results.sarif"
|
|
94
|
+
|
|
95
|
+
try:
|
|
96
|
+
# ── Phase 1: database create ────────────────────────────────
|
|
97
|
+
result = subprocess.run(
|
|
98
|
+
[
|
|
99
|
+
"codeql",
|
|
100
|
+
"database",
|
|
101
|
+
"create",
|
|
102
|
+
str(db_dir),
|
|
103
|
+
"--language=python",
|
|
104
|
+
"--source-root",
|
|
105
|
+
str(root),
|
|
106
|
+
],
|
|
107
|
+
capture_output=True,
|
|
108
|
+
text=True,
|
|
109
|
+
encoding="utf-8",
|
|
110
|
+
errors="replace",
|
|
111
|
+
timeout=900,
|
|
112
|
+
cwd=str(root),
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
if result.returncode != 0:
|
|
116
|
+
tail = (result.stderr or result.stdout or "")[-300:]
|
|
117
|
+
_log.warning("CodeQL database creation failed: %s", tail)
|
|
118
|
+
return findings
|
|
119
|
+
|
|
120
|
+
# ── Phase 2: analyze against the security suite ─────────────
|
|
121
|
+
result = subprocess.run(
|
|
122
|
+
[
|
|
123
|
+
"codeql",
|
|
124
|
+
"database",
|
|
125
|
+
"analyze",
|
|
126
|
+
str(db_dir),
|
|
127
|
+
suite,
|
|
128
|
+
"--format=sarif-latest",
|
|
129
|
+
"--output",
|
|
130
|
+
str(results_file),
|
|
131
|
+
"--sarif-add-snippets",
|
|
132
|
+
],
|
|
133
|
+
capture_output=True,
|
|
134
|
+
text=True,
|
|
135
|
+
encoding="utf-8",
|
|
136
|
+
errors="replace",
|
|
137
|
+
timeout=3600,
|
|
138
|
+
cwd=str(root),
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
if result.returncode != 0:
|
|
142
|
+
tail = (result.stderr or result.stdout or "")[-300:]
|
|
143
|
+
_log.warning("CodeQL analyze failed: %s", tail)
|
|
144
|
+
return findings
|
|
145
|
+
|
|
146
|
+
if results_file.exists():
|
|
147
|
+
with open(results_file, encoding="utf-8") as f:
|
|
148
|
+
data = json.load(f)
|
|
149
|
+
return self._parse_sarif_output(data)
|
|
150
|
+
|
|
151
|
+
except subprocess.TimeoutExpired:
|
|
152
|
+
_log.warning("CodeQL timed out")
|
|
153
|
+
except Exception as e: # noqa: BLE001 — tool failures are data
|
|
154
|
+
_log.warning("CodeQL execution failed: %s", e)
|
|
155
|
+
|
|
156
|
+
return findings
|
|
157
|
+
|
|
158
|
+
def _parse_sarif_output(self, data: dict[str, Any]) -> list[dict[str, Any]]:
|
|
159
|
+
"""Parse SARIF into finding dicts with full fidelity.
|
|
160
|
+
|
|
161
|
+
Extracts per-rule metadata (CWE tags, numeric security-severity)
|
|
162
|
+
and per-result code snippets so downstream scoring and AI review
|
|
163
|
+
get real context instead of bare rule ids.
|
|
164
|
+
"""
|
|
165
|
+
findings: list[dict[str, Any]] = []
|
|
166
|
+
|
|
167
|
+
for run in data.get("runs", []):
|
|
168
|
+
# Rule metadata lives in tool.driver.rules / extensions
|
|
169
|
+
rules_by_id: dict[str, dict[str, Any]] = {}
|
|
170
|
+
for tool in run.get("tool", {}).get("driver", {}).get("rules", []) + [
|
|
171
|
+
r for ext in run.get("tool", {}).get("extensions", []) for r in ext.get("rules", [])
|
|
172
|
+
]:
|
|
173
|
+
rid = tool.get("id", "")
|
|
174
|
+
if rid:
|
|
175
|
+
rules_by_id[rid] = tool
|
|
176
|
+
|
|
177
|
+
for result in run.get("results", []):
|
|
178
|
+
rule_id = result.get("ruleId", "codeql_finding")
|
|
179
|
+
rule_meta = rules_by_id.get(rule_id, {})
|
|
180
|
+
props = rule_meta.get("properties", {}) or {}
|
|
181
|
+
|
|
182
|
+
# Severity: prefer numeric security-severity (0-10)
|
|
183
|
+
sec_sev = props.get("security-severity")
|
|
184
|
+
level = result.get("level") or props.get("problem.severity") or "warning"
|
|
185
|
+
|
|
186
|
+
# CWE from tags like "security/cwe/cwe-089"
|
|
187
|
+
cwe = ""
|
|
188
|
+
for tag in props.get("tags", []) or []:
|
|
189
|
+
tag_l = str(tag).lower()
|
|
190
|
+
if "cwe-" in tag_l:
|
|
191
|
+
idx = tag_l.index("cwe-")
|
|
192
|
+
num = "".join(ch for ch in tag_l[idx + 4 :] if ch.isdigit())
|
|
193
|
+
if num:
|
|
194
|
+
cwe = f"CWE-{int(num)}"
|
|
195
|
+
break
|
|
196
|
+
|
|
197
|
+
location = result.get("locations", [{}])[0].get("physicalLocation", {})
|
|
198
|
+
artifact = location.get("artifactLocation", {})
|
|
199
|
+
region = location.get("region", {})
|
|
200
|
+
snippet = (region.get("snippet", {}) or {}).get("text", "")
|
|
201
|
+
|
|
202
|
+
findings.append(
|
|
203
|
+
{
|
|
204
|
+
"type": rule_id,
|
|
205
|
+
"severity": sec_sev if sec_sev is not None else level,
|
|
206
|
+
"file": artifact.get("uri", ""),
|
|
207
|
+
"line": region.get("startLine", 0),
|
|
208
|
+
"message": (result.get("message", {}) or {}).get("text", ""),
|
|
209
|
+
"cwe": cwe,
|
|
210
|
+
"snippet": snippet,
|
|
211
|
+
"rule_id": rule_id,
|
|
212
|
+
}
|
|
213
|
+
)
|
|
214
|
+
return findings
|
|
215
|
+
|
|
216
|
+
def _create_finding(self, finding_data: dict[str, Any]):
|
|
217
|
+
"""Create a Finding object from CodeQL output."""
|
|
218
|
+
from patchi.core.security.tool_adapters import make_tool_finding
|
|
219
|
+
|
|
220
|
+
return make_tool_finding(
|
|
221
|
+
agent="CodeqlAgent",
|
|
222
|
+
ftype=finding_data.get("type", "codeql_finding"),
|
|
223
|
+
raw_severity=finding_data.get("severity", "MEDIUM"),
|
|
224
|
+
file=finding_data.get("file", ""),
|
|
225
|
+
line=finding_data.get("line", 0),
|
|
226
|
+
message=finding_data.get("message", ""),
|
|
227
|
+
cwe=finding_data.get("cwe", ""),
|
|
228
|
+
snippet=finding_data.get("snippet", ""),
|
|
229
|
+
confidence_raw=0.8, # database-query findings: semantic, not pattern
|
|
230
|
+
extra={"rule_id": finding_data.get("rule_id", "")},
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
|