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,395 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CatchBlockAuditor — Detects empty or bare catch/except blocks.
|
|
3
|
+
|
|
4
|
+
Finds error handlers that swallow exceptions:
|
|
5
|
+
- Empty catch/except blocks: `catch (e) {}` or `except: pass`
|
|
6
|
+
- Bare logging without re-raise: `catch (e) { console.log(e); }`
|
|
7
|
+
- Swallowed exceptions with no action: `catch {}`
|
|
8
|
+
- Silent failure: bare `except:` without specifying exception type
|
|
9
|
+
|
|
10
|
+
Covers all languages with tree-sitter via AST walkers where a try/catch
|
|
11
|
+
construct exists; Go (ignored `err`) and Rust (`unwrap`) are handled with
|
|
12
|
+
targeted checks since they have no exception-handling blocks.
|
|
13
|
+
|
|
14
|
+
Uses AST structure (accurate block boundaries) + lightweight text analysis
|
|
15
|
+
of the handler body to decide whether it does something useful.
|
|
16
|
+
Does NOT call AI.
|
|
17
|
+
Does NOT write to disk.
|
|
18
|
+
Does NOT touch the queue.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import logging
|
|
24
|
+
import re
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
|
|
27
|
+
from ..agents.base import (
|
|
28
|
+
AgentGroup,
|
|
29
|
+
AgentInput,
|
|
30
|
+
AgentResult,
|
|
31
|
+
AgentStatus,
|
|
32
|
+
BaseAgent,
|
|
33
|
+
Finding,
|
|
34
|
+
Severity,
|
|
35
|
+
make_finding,
|
|
36
|
+
register,
|
|
37
|
+
safe_rglob,
|
|
38
|
+
)
|
|
39
|
+
from ..brain.ast_utils import (
|
|
40
|
+
child_by_field,
|
|
41
|
+
find_assignments,
|
|
42
|
+
find_calls,
|
|
43
|
+
named_children,
|
|
44
|
+
node_text,
|
|
45
|
+
parse_source,
|
|
46
|
+
)
|
|
47
|
+
from ..brain.languages import EXTENSION_MAP, Lang
|
|
48
|
+
from ..brain.trace_log import trace_agent
|
|
49
|
+
|
|
50
|
+
# Tree-sitter node types that represent an exception-handling block, per language.
|
|
51
|
+
CATCH_NODE_TYPES: dict[Lang, set[str]] = {
|
|
52
|
+
Lang.PYTHON: {"except_clause"},
|
|
53
|
+
Lang.JAVASCRIPT: {"catch_clause"},
|
|
54
|
+
Lang.TYPESCRIPT: {"catch_clause"},
|
|
55
|
+
Lang.JAVA: {"catch_clause"},
|
|
56
|
+
Lang.C_SHARP: {"catch_clause"},
|
|
57
|
+
Lang.CPP: {"catch_clause"},
|
|
58
|
+
Lang.KOTLIN: {"catch_block"},
|
|
59
|
+
Lang.SWIFT: {"catch_block"},
|
|
60
|
+
Lang.RUBY: {"rescue"},
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
# Body node types that hold the handler statements (fallback when no field name).
|
|
64
|
+
_BODY_TYPES = {"block", "compound_statement", "statement_block", "then", "statements"}
|
|
65
|
+
|
|
66
|
+
# Line-level heuristics for deciding whether a handler body does something useful.
|
|
67
|
+
_SINGLE_IDENT_RE = re.compile(r"^[a-zA-Z_]\w*$")
|
|
68
|
+
_CONSOLE_LOG_RE = re.compile(r"console\.(log|warn)\([^)]*\)")
|
|
69
|
+
_PRINT_RE = re.compile(r"print\([^)]*\)")
|
|
70
|
+
_P_RE = re.compile(r"p\s+[^)]+")
|
|
71
|
+
_TRACEBACK_RE = re.compile(r"(traceback\.print_exc|print_exc|e\.printStackTrace)")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
_log = logging.getLogger("patchi.security.catch_block_auditor")
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@register
|
|
78
|
+
class CatchBlockAuditor(BaseAgent):
|
|
79
|
+
"""Agent for detecting empty or insufficient catch/except blocks."""
|
|
80
|
+
|
|
81
|
+
group = AgentGroup.SECURITY
|
|
82
|
+
name = "CatchBlockAuditor"
|
|
83
|
+
description = "Empty or bare catch/except blocks that swallow exceptions"
|
|
84
|
+
|
|
85
|
+
def _run(self, inp: AgentInput, result: AgentResult) -> None:
|
|
86
|
+
findings = []
|
|
87
|
+
|
|
88
|
+
source_patterns = [
|
|
89
|
+
"*.py",
|
|
90
|
+
"*.js",
|
|
91
|
+
"*.jsx",
|
|
92
|
+
"*.ts",
|
|
93
|
+
"*.tsx",
|
|
94
|
+
"*.java",
|
|
95
|
+
"*.go",
|
|
96
|
+
"*.rs",
|
|
97
|
+
"*.c",
|
|
98
|
+
"*.h",
|
|
99
|
+
"*.cpp",
|
|
100
|
+
"*.cxx",
|
|
101
|
+
"*.cc",
|
|
102
|
+
"*.hpp",
|
|
103
|
+
"*.rb",
|
|
104
|
+
"*.swift",
|
|
105
|
+
"*.kt",
|
|
106
|
+
"*.kts",
|
|
107
|
+
"*.cs",
|
|
108
|
+
]
|
|
109
|
+
|
|
110
|
+
with trace_agent(self.name, inp.root) as trace:
|
|
111
|
+
for pattern in source_patterns:
|
|
112
|
+
for file_path in safe_rglob(inp.root, pattern):
|
|
113
|
+
if file_path.is_file():
|
|
114
|
+
rel_path = file_path.relative_to(inp.root).as_posix()
|
|
115
|
+
if not self._should_skip_file(rel_path, inp):
|
|
116
|
+
ext = file_path.suffix.lower()
|
|
117
|
+
content = self._safe_read(file_path)
|
|
118
|
+
if content is None:
|
|
119
|
+
continue
|
|
120
|
+
findings.extend(self._scan_catch_blocks(content, rel_path, ext))
|
|
121
|
+
trace.findings = len(findings)
|
|
122
|
+
|
|
123
|
+
result.status = AgentStatus.SUCCEEDED
|
|
124
|
+
result.findings = findings
|
|
125
|
+
result.data.update({"catch_block_findings": len(findings), "needs_ai": False})
|
|
126
|
+
return
|
|
127
|
+
|
|
128
|
+
def _should_skip_file(self, file_path: str, inp: AgentInput) -> bool:
|
|
129
|
+
from pathlib import PurePosixPath
|
|
130
|
+
|
|
131
|
+
restrictions = inp.config.get("restrictions", [])
|
|
132
|
+
for r in restrictions:
|
|
133
|
+
if r.get("enabled", True):
|
|
134
|
+
path = r["path"]
|
|
135
|
+
if file_path.startswith(path) or PurePosixPath(file_path).match(path):
|
|
136
|
+
if r["type"] == "NO_TOUCH":
|
|
137
|
+
return True
|
|
138
|
+
if r["type"] == "SCAN_ONLY" and self.__class__.__name__ == "FixAgent":
|
|
139
|
+
return True
|
|
140
|
+
return False
|
|
141
|
+
|
|
142
|
+
def _safe_read(self, path: Path) -> str | None:
|
|
143
|
+
try:
|
|
144
|
+
return path.read_text(encoding="utf-8")
|
|
145
|
+
except Exception as e:
|
|
146
|
+
_log.warning("CatchBlockAuditor._safe_read failed: %s", e)
|
|
147
|
+
return None
|
|
148
|
+
|
|
149
|
+
# ── Dispatch ─────────────────────────────────────────────────────────────
|
|
150
|
+
|
|
151
|
+
def _scan_catch_blocks(self, content: str, rel_path: str, ext: str) -> list[Finding]:
|
|
152
|
+
lang = EXTENSION_MAP.get(ext)
|
|
153
|
+
if lang is None:
|
|
154
|
+
return []
|
|
155
|
+
|
|
156
|
+
# Go and Rust have no exception-handling blocks — handle idiomatically.
|
|
157
|
+
if lang == Lang.GO:
|
|
158
|
+
return self._scan_go(content, rel_path)
|
|
159
|
+
if lang == Lang.RUST:
|
|
160
|
+
return self._scan_rust(content, rel_path)
|
|
161
|
+
|
|
162
|
+
node_types = CATCH_NODE_TYPES.get(lang)
|
|
163
|
+
if not node_types:
|
|
164
|
+
return []
|
|
165
|
+
|
|
166
|
+
tree = parse_source(content, lang)
|
|
167
|
+
if tree is None:
|
|
168
|
+
return []
|
|
169
|
+
|
|
170
|
+
findings = self._walk_catch_tree(tree.root_node, node_types, lang, rel_path)
|
|
171
|
+
if lang == Lang.JAVA:
|
|
172
|
+
findings.extend(self._scan_java_printstacktrace(content, rel_path))
|
|
173
|
+
return findings
|
|
174
|
+
|
|
175
|
+
# ── AST walking ──────────────────────────────────────────────────────────
|
|
176
|
+
|
|
177
|
+
def _walk_catch_tree(
|
|
178
|
+
self, node, node_types: set[str], lang: Lang, rel_path: str
|
|
179
|
+
) -> list[Finding]:
|
|
180
|
+
findings: list[Finding] = []
|
|
181
|
+
if node.type in node_types:
|
|
182
|
+
spec, is_bare, body_text = self._classify_catch(node, lang)
|
|
183
|
+
line = node.start_point[0] + 1
|
|
184
|
+
if is_bare:
|
|
185
|
+
findings.append(
|
|
186
|
+
make_finding(
|
|
187
|
+
severity=Severity.MEDIUM,
|
|
188
|
+
file=rel_path,
|
|
189
|
+
line_start=line,
|
|
190
|
+
title="Bare except clause" if lang == Lang.PYTHON else "Bare catch clause",
|
|
191
|
+
description=(
|
|
192
|
+
"A catch/except handler without an exception type catches everything "
|
|
193
|
+
"(including control-flow exceptions). Specify the expected exception type(s) "
|
|
194
|
+
"to avoid swallowing critical errors."
|
|
195
|
+
),
|
|
196
|
+
evidence=node_text(node).strip(),
|
|
197
|
+
cwe="CWE-755",
|
|
198
|
+
)
|
|
199
|
+
)
|
|
200
|
+
elif not body_text.strip():
|
|
201
|
+
findings.append(
|
|
202
|
+
make_finding(
|
|
203
|
+
severity=Severity.MEDIUM,
|
|
204
|
+
file=rel_path,
|
|
205
|
+
line_start=line,
|
|
206
|
+
title="Empty catch block",
|
|
207
|
+
description=(
|
|
208
|
+
"Catch block is empty. Exceptions are silently swallowed. "
|
|
209
|
+
"Add error handling, logging, or re-throw."
|
|
210
|
+
),
|
|
211
|
+
evidence=node_text(node).strip(),
|
|
212
|
+
cwe="CWE-755",
|
|
213
|
+
)
|
|
214
|
+
)
|
|
215
|
+
elif self._is_bare_catch(body_text):
|
|
216
|
+
findings.append(
|
|
217
|
+
make_finding(
|
|
218
|
+
severity=Severity.LOW,
|
|
219
|
+
file=rel_path,
|
|
220
|
+
line_start=line,
|
|
221
|
+
title="Insufficient catch block",
|
|
222
|
+
description=(
|
|
223
|
+
f"Exception handler for '{spec}' only logs or prints without recovery. "
|
|
224
|
+
"Consider adding proper error handling, fallback, or re-raise."
|
|
225
|
+
),
|
|
226
|
+
evidence=node_text(node).strip(),
|
|
227
|
+
cwe="CWE-755",
|
|
228
|
+
)
|
|
229
|
+
)
|
|
230
|
+
for child in node.children:
|
|
231
|
+
findings.extend(self._walk_catch_tree(child, node_types, lang, rel_path))
|
|
232
|
+
return findings
|
|
233
|
+
|
|
234
|
+
def _classify_catch(self, node, lang: Lang) -> tuple[str, bool, str]:
|
|
235
|
+
"""Return (exception_spec_text, is_bare, body_text)."""
|
|
236
|
+
body_node = None
|
|
237
|
+
for field in ("body", "statements"):
|
|
238
|
+
b = child_by_field(node, field)
|
|
239
|
+
if b is not None:
|
|
240
|
+
body_node = b
|
|
241
|
+
break
|
|
242
|
+
if body_node is None:
|
|
243
|
+
for c in named_children(node):
|
|
244
|
+
if c.type in _BODY_TYPES:
|
|
245
|
+
body_node = c
|
|
246
|
+
break
|
|
247
|
+
body_text = node_text(body_node) if body_node is not None else ""
|
|
248
|
+
# Strip the surrounding braces so the inner statements are analysed.
|
|
249
|
+
stripped = body_text.strip()
|
|
250
|
+
if stripped.startswith("{") and stripped.endswith("}"):
|
|
251
|
+
inner = stripped[1:-1]
|
|
252
|
+
body_text = inner
|
|
253
|
+
|
|
254
|
+
if lang == Lang.PYTHON:
|
|
255
|
+
val = child_by_field(node, "value")
|
|
256
|
+
is_bare = val is None
|
|
257
|
+
spec = node_text(val) if val is not None else "bare except"
|
|
258
|
+
elif lang in (Lang.JAVASCRIPT, Lang.TYPESCRIPT):
|
|
259
|
+
param = child_by_field(node, "parameter")
|
|
260
|
+
is_bare = param is None
|
|
261
|
+
spec = node_text(param) if param is not None else "bare catch"
|
|
262
|
+
elif lang in (Lang.JAVA, Lang.C_SHARP):
|
|
263
|
+
cfp = None
|
|
264
|
+
for c in named_children(node):
|
|
265
|
+
if c.type in ("catch_formal_parameter", "catch_declaration"):
|
|
266
|
+
cfp = c
|
|
267
|
+
break
|
|
268
|
+
spec = node_text(cfp) if cfp is not None else "catch"
|
|
269
|
+
is_bare = False
|
|
270
|
+
elif lang == Lang.CPP:
|
|
271
|
+
params = child_by_field(node, "parameters")
|
|
272
|
+
ptext = node_text(params) if params is not None else ""
|
|
273
|
+
is_bare = ptext.strip() == "..."
|
|
274
|
+
spec = ptext or "catch"
|
|
275
|
+
elif lang == Lang.KOTLIN:
|
|
276
|
+
spec = node_text(node)
|
|
277
|
+
is_bare = False
|
|
278
|
+
elif lang == Lang.SWIFT:
|
|
279
|
+
err = child_by_field(node, "error")
|
|
280
|
+
is_bare = err is None
|
|
281
|
+
spec = node_text(err) if err is not None else "bare catch"
|
|
282
|
+
elif lang == Lang.RUBY:
|
|
283
|
+
ex = child_by_field(node, "exceptions")
|
|
284
|
+
is_bare = ex is None
|
|
285
|
+
spec = node_text(ex) if ex is not None else "bare rescue"
|
|
286
|
+
else:
|
|
287
|
+
spec = node_text(node)
|
|
288
|
+
is_bare = False
|
|
289
|
+
return spec, is_bare, body_text
|
|
290
|
+
|
|
291
|
+
def _scan_java_printstacktrace(self, content: str, rel_path: str) -> list[Finding]:
|
|
292
|
+
from ..brain.ast_utils import find_calls
|
|
293
|
+
|
|
294
|
+
findings: list[Finding] = []
|
|
295
|
+
for m in find_calls(content, Lang.JAVA, {"printStackTrace"}):
|
|
296
|
+
findings.append(
|
|
297
|
+
make_finding(
|
|
298
|
+
severity=Severity.LOW,
|
|
299
|
+
file=rel_path,
|
|
300
|
+
line_start=m.get("line", 0),
|
|
301
|
+
title="Bare printStackTrace()",
|
|
302
|
+
description=(
|
|
303
|
+
"e.printStackTrace() writes to stderr but is often lost in production. "
|
|
304
|
+
"Use a logging framework (SLF4J, Log4j) instead."
|
|
305
|
+
),
|
|
306
|
+
evidence=m.get("full_text", "printStackTrace()").strip(),
|
|
307
|
+
cwe="CWE-755",
|
|
308
|
+
)
|
|
309
|
+
)
|
|
310
|
+
return findings
|
|
311
|
+
|
|
312
|
+
# ── Body usefulness analysis (text-based) ────────────────────────────────
|
|
313
|
+
|
|
314
|
+
def _is_bare_catch(self, block_body: str) -> bool:
|
|
315
|
+
"""Check if a catch block body contains only trivial/no-op statements."""
|
|
316
|
+
stripped = block_body.strip().rstrip(";")
|
|
317
|
+
if not stripped:
|
|
318
|
+
return True
|
|
319
|
+
noop_lines = [
|
|
320
|
+
ln.strip().rstrip(";")
|
|
321
|
+
for ln in stripped.split("\n")
|
|
322
|
+
if ln.strip() and not ln.strip().startswith(("#", "//", "/*", "*"))
|
|
323
|
+
]
|
|
324
|
+
if not noop_lines:
|
|
325
|
+
return True
|
|
326
|
+
for line in noop_lines:
|
|
327
|
+
if not self._is_noop_line(line):
|
|
328
|
+
return False
|
|
329
|
+
return True
|
|
330
|
+
|
|
331
|
+
def _is_noop_line(self, line: str) -> bool:
|
|
332
|
+
"""Check if a line is a no-op in exception handling."""
|
|
333
|
+
line = line.strip().rstrip(";")
|
|
334
|
+
if not line:
|
|
335
|
+
return True
|
|
336
|
+
# Single identifiers (bare variable names like `error`)
|
|
337
|
+
if _SINGLE_IDENT_RE.match(line):
|
|
338
|
+
return True
|
|
339
|
+
# Pass, next, nil, None, null, undefined
|
|
340
|
+
if line in ("pass", "next", "nil", "None", "null", "undefined"):
|
|
341
|
+
return True
|
|
342
|
+
# Bare console.log/print of the error
|
|
343
|
+
if _CONSOLE_LOG_RE.match(line):
|
|
344
|
+
return True
|
|
345
|
+
if _PRINT_RE.match(line):
|
|
346
|
+
return True
|
|
347
|
+
if _P_RE.match(line):
|
|
348
|
+
return True
|
|
349
|
+
# Bare traceback/stack print
|
|
350
|
+
if _TRACEBACK_RE.match(line):
|
|
351
|
+
return True
|
|
352
|
+
return False
|
|
353
|
+
|
|
354
|
+
# ── Go / Rust (no exception-handling blocks) ─────────────────────────────
|
|
355
|
+
|
|
356
|
+
def _scan_go(self, content: str, rel_path: str) -> list[Finding]:
|
|
357
|
+
findings: list[Finding] = []
|
|
358
|
+
for assign in find_assignments(content, Lang.GO):
|
|
359
|
+
if assign["target"].strip() == "_":
|
|
360
|
+
value = assign["value"]
|
|
361
|
+
if value and not value.startswith('"') and not value.startswith("'"):
|
|
362
|
+
findings.append(
|
|
363
|
+
make_finding(
|
|
364
|
+
severity=Severity.MEDIUM,
|
|
365
|
+
file=rel_path,
|
|
366
|
+
line_start=assign["line"],
|
|
367
|
+
title="Error explicitly discarded with _ =",
|
|
368
|
+
description=(
|
|
369
|
+
"Error return value is explicitly discarded. "
|
|
370
|
+
"Check the error or use a linter to enforce error checking."
|
|
371
|
+
),
|
|
372
|
+
evidence=assign["full_text"][:120],
|
|
373
|
+
cwe="CWE-755",
|
|
374
|
+
)
|
|
375
|
+
)
|
|
376
|
+
return findings
|
|
377
|
+
|
|
378
|
+
def _scan_rust(self, content: str, rel_path: str) -> list[Finding]:
|
|
379
|
+
findings: list[Finding] = []
|
|
380
|
+
for call in find_calls(content, Lang.RUST, {"unwrap"}):
|
|
381
|
+
findings.append(
|
|
382
|
+
make_finding(
|
|
383
|
+
severity=Severity.MEDIUM,
|
|
384
|
+
file=rel_path,
|
|
385
|
+
line_start=call["line"],
|
|
386
|
+
title="Unsafe .unwrap() call",
|
|
387
|
+
description=(
|
|
388
|
+
".unwrap() panics on Err/None. Prefer pattern matching, "
|
|
389
|
+
"?, or .expect() with a descriptive message."
|
|
390
|
+
),
|
|
391
|
+
evidence=call["full_text"][:120],
|
|
392
|
+
cwe="CWE-754",
|
|
393
|
+
)
|
|
394
|
+
)
|
|
395
|
+
return findings
|