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,50 @@
|
|
|
1
|
+
"""Nuclei adapter — CVE/templates scan."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import logging
|
|
7
|
+
import shutil
|
|
8
|
+
import subprocess
|
|
9
|
+
import time
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
from patchi.core.security.pentest.base import PentestResult, PentestTool
|
|
13
|
+
|
|
14
|
+
_log = logging.getLogger("patchi.pentest.nuclei")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class NucleiTool(PentestTool):
|
|
18
|
+
name = "nuclei"
|
|
19
|
+
binary = "nuclei"
|
|
20
|
+
docker_image = "projectdiscovery/nuclei:latest"
|
|
21
|
+
|
|
22
|
+
def run(self, target: str, safe_mode: bool = True, workspace: Path | None = None, extra: dict | None = None) -> PentestResult:
|
|
23
|
+
extra = extra or {}
|
|
24
|
+
templates = extra.get("templates", "cves") # cves|exposures|misconfiguration
|
|
25
|
+
t0 = time.monotonic()
|
|
26
|
+
if shutil.which("nuclei"):
|
|
27
|
+
cmd = ["nuclei", "-u", target, "-t", templates, "-jsonl", "-silent", "-nc"]
|
|
28
|
+
if safe_mode:
|
|
29
|
+
cmd += ["-rate-limit", "10"]
|
|
30
|
+
def runner(c): return subprocess.run(c, capture_output=True, text=True, timeout=300)
|
|
31
|
+
elif shutil.which("docker"):
|
|
32
|
+
cmd = ["docker", "run", "--rm", "projectdiscovery/nuclei:latest", "-u", target, "-t", templates, "-jsonl", "-silent", "-nc"]
|
|
33
|
+
def runner(c): return subprocess.run(c, capture_output=True, text=True, timeout=300)
|
|
34
|
+
else:
|
|
35
|
+
return PentestResult(tool=self.name, success=False, error="nuclei not available (install nuclei or docker)")
|
|
36
|
+
try:
|
|
37
|
+
proc = runner(cmd)
|
|
38
|
+
dur = int((time.monotonic() - t0) * 1000)
|
|
39
|
+
findings: list[dict] = []
|
|
40
|
+
for line in proc.stdout.splitlines()[:200]:
|
|
41
|
+
try:
|
|
42
|
+
j = json.loads(line)
|
|
43
|
+
findings.append({"template": j.get("templateID",""), "name": j.get("info",{}).get("name",""), "severity": j.get("info",{}).get("severity",""), "host": j.get("host",""), "matcher": j.get("matcher-name","")})
|
|
44
|
+
except Exception:
|
|
45
|
+
continue
|
|
46
|
+
return PentestResult(tool=self.name, success=True, evidence=f"nuclei {len(findings)} findings templates={templates}", findings=findings, raw_output=proc.stdout[:8000], duration_ms=dur)
|
|
47
|
+
except subprocess.TimeoutExpired:
|
|
48
|
+
return PentestResult(tool=self.name, success=False, error="nuclei timeout 5m", duration_ms=int((time.monotonic()-t0)*1000))
|
|
49
|
+
except Exception as exc: # noqa: BLE001
|
|
50
|
+
return PentestResult(tool=self.name, success=False, error=str(exc), duration_ms=int((time.monotonic()-t0)*1000))
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""Pentest registry — which real tools are available and AI choice."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from patchi.core.security.pentest.base import PentestResult
|
|
10
|
+
from patchi.core.security.pentest.dalfox_adapter import DalfoxTool
|
|
11
|
+
from patchi.core.security.pentest.ffuf_adapter import FfufTool
|
|
12
|
+
from patchi.core.security.pentest.nuclei_adapter import NucleiTool
|
|
13
|
+
from patchi.core.security.pentest.shannon_adapter import ShannonTool
|
|
14
|
+
from patchi.core.security.pentest.sqlmap_adapter import SqlmapTool
|
|
15
|
+
from patchi.core.security.pentest.zap_adapter import ZapTool
|
|
16
|
+
|
|
17
|
+
_log = logging.getLogger("patchi.pentest.registry")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class PentestRegistry:
|
|
21
|
+
def __init__(self) -> None:
|
|
22
|
+
self._tools = {
|
|
23
|
+
"nuclei": NucleiTool(),
|
|
24
|
+
"sqlmap": SqlmapTool(),
|
|
25
|
+
"dalfox": DalfoxTool(),
|
|
26
|
+
"ffuf": FfufTool(),
|
|
27
|
+
"zap": ZapTool(),
|
|
28
|
+
"shannon": ShannonTool(),
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
def list(self) -> list[str]:
|
|
32
|
+
return list(self._tools.keys())
|
|
33
|
+
|
|
34
|
+
def available(self) -> dict[str, bool]:
|
|
35
|
+
return {k: v.is_available() for k, v in self._tools.items()}
|
|
36
|
+
|
|
37
|
+
def get(self, name: str):
|
|
38
|
+
return self._tools.get(name)
|
|
39
|
+
|
|
40
|
+
def run(self, name: str, target: str, safe_mode: bool = True, workspace: Path | None = None, extra: dict | None = None) -> PentestResult:
|
|
41
|
+
tool = self.get(name)
|
|
42
|
+
if not tool:
|
|
43
|
+
return PentestResult(tool=name, success=False, error=f"unknown tool {name}")
|
|
44
|
+
if not tool.is_available():
|
|
45
|
+
return PentestResult(tool=name, success=False, error=f"{name} not available (install {tool.binary} or docker)")
|
|
46
|
+
return tool.run(target, safe_mode=safe_mode, workspace=workspace, extra=extra or {})
|
|
47
|
+
|
|
48
|
+
def ai_pick(self, context: dict[str, Any], available_only: bool = True) -> list[dict[str, Any]]:
|
|
49
|
+
"""One LLM call to pick 1-3 tools. Fallback heuristic if offline."""
|
|
50
|
+
import os
|
|
51
|
+
|
|
52
|
+
if os.environ.get("PATCHI_OFFLINE"):
|
|
53
|
+
# heuristic: sqli→sqlmap, xss→dalfox, cve→nuclei
|
|
54
|
+
domains = " ".join(context.get("active_domains", [])).lower()
|
|
55
|
+
picks: list[dict] = []
|
|
56
|
+
if "sql" in domains or "injection" in domains:
|
|
57
|
+
picks.append({"tool": "sqlmap", "reason": "injection domain active"})
|
|
58
|
+
if "xss" in domains:
|
|
59
|
+
picks.append({"tool": "dalfox", "reason": "xss domain"})
|
|
60
|
+
if not picks or "cve" in domains:
|
|
61
|
+
picks.append({"tool": "nuclei", "reason": "cve surface"})
|
|
62
|
+
if context.get("target_url") and context.get("use_shannon"):
|
|
63
|
+
picks = [{"tool": "shannon", "reason": "full chain requested"}] + picks
|
|
64
|
+
return picks[:2]
|
|
65
|
+
try:
|
|
66
|
+
from patchi.core.ai.client import call_ai_structured
|
|
67
|
+
|
|
68
|
+
cfg = context.get("config", {})
|
|
69
|
+
avail = self.available()
|
|
70
|
+
prompt = f"CONTEXT: routes={context.get('routes', [])[:5]} domains={context.get('active_domains', [])} target={context.get('target_url','-')} available={avail}\nPick 1-3 tools from {list(self._tools.keys())} with reason. Need live target for dalfox/nuclei/sqlmap/zap/shannon. No target→only code_scan. Return JSON {{\"picks\":[{{\"tool\":\"nuclei\",\"reason\":\"\"}}]}}"
|
|
71
|
+
system = "You are Patchi Red Team Lead. Pick minimal real tools to prove exploitable findings. Prefer nuclei for CVE, sqlmap for SQLi, dalfox for XSS, shannon for full authenticated chain. No exploit, no report."
|
|
72
|
+
data = call_ai_structured(cfg, system, prompt, max_tokens=400, temperature=0.2)
|
|
73
|
+
if isinstance(data, dict) and isinstance(data.get("picks"), list):
|
|
74
|
+
return [p for p in data["picks"] if isinstance(p, dict) and p.get("tool") in self._tools][:3]
|
|
75
|
+
except Exception as exc: # noqa: BLE001
|
|
76
|
+
_log.debug("ai_pick fallback: %s", exc)
|
|
77
|
+
# LLM failed — honor shannon intent if target+flag present
|
|
78
|
+
if context.get("use_shannon") and context.get("target_url"):
|
|
79
|
+
return [{"tool": "shannon", "reason": "fallback shannon requested"}]
|
|
80
|
+
return [{"tool": "nuclei", "reason": "fallback"}]
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""Shannon adapter — Option 2: external npx, not vendored (AGPL-clean)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import logging
|
|
7
|
+
import shutil
|
|
8
|
+
import subprocess
|
|
9
|
+
import time
|
|
10
|
+
import uuid
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
from patchi.core.security.pentest.base import PentestResult, PentestTool
|
|
14
|
+
|
|
15
|
+
_log = logging.getLogger("patchi.pentest.shannon")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ShannonTool(PentestTool):
|
|
19
|
+
name = "shannon"
|
|
20
|
+
binary = "npx"
|
|
21
|
+
docker_image = "ghcr.io/keygraphhq/shannon-worker:latest"
|
|
22
|
+
requires_target = True
|
|
23
|
+
|
|
24
|
+
def is_available(self) -> bool:
|
|
25
|
+
if not shutil.which("npx"):
|
|
26
|
+
return False
|
|
27
|
+
if not shutil.which("docker"):
|
|
28
|
+
_log.debug("shannon: docker not found — still available via npx")
|
|
29
|
+
return True
|
|
30
|
+
|
|
31
|
+
def run(
|
|
32
|
+
self,
|
|
33
|
+
target: str,
|
|
34
|
+
safe_mode: bool = True,
|
|
35
|
+
workspace: Path | None = None,
|
|
36
|
+
extra: dict | None = None,
|
|
37
|
+
) -> PentestResult:
|
|
38
|
+
extra = extra or {}
|
|
39
|
+
repo_root = extra.get("repo_root")
|
|
40
|
+
if not repo_root:
|
|
41
|
+
return PentestResult(tool=self.name, success=False, error="repo_root required for shannon")
|
|
42
|
+
repo_root = Path(repo_root)
|
|
43
|
+
ws = workspace or (repo_root / ".patchi" / "shannon" / uuid.uuid4().hex[:8])
|
|
44
|
+
ws.mkdir(parents=True, exist_ok=True)
|
|
45
|
+
t0 = time.monotonic()
|
|
46
|
+
# SAFETY: shannon mutates target — never run without explicit target + non-prod
|
|
47
|
+
if not target or target.strip() in ("", "http://", "https://"):
|
|
48
|
+
return PentestResult(tool=self.name, success=False, error="target URL required")
|
|
49
|
+
cmd = [
|
|
50
|
+
"npx",
|
|
51
|
+
"@keygraph/shannon@latest",
|
|
52
|
+
"start",
|
|
53
|
+
"-u",
|
|
54
|
+
target,
|
|
55
|
+
"-r",
|
|
56
|
+
str(repo_root),
|
|
57
|
+
"--workspace",
|
|
58
|
+
str(ws),
|
|
59
|
+
"--format",
|
|
60
|
+
"sarif",
|
|
61
|
+
]
|
|
62
|
+
if safe_mode:
|
|
63
|
+
cmd += ["--safe-mode"]
|
|
64
|
+
_log.info("shannon: %s", " ".join(cmd))
|
|
65
|
+
try:
|
|
66
|
+
proc = subprocess.run(
|
|
67
|
+
cmd,
|
|
68
|
+
capture_output=True,
|
|
69
|
+
text=True,
|
|
70
|
+
timeout=5400, # 90m max (shannon 1-1.5h docs)
|
|
71
|
+
)
|
|
72
|
+
dur = int((time.monotonic() - t0) * 1000)
|
|
73
|
+
raw = (proc.stdout + proc.stderr)[:20000]
|
|
74
|
+
# Parse SARIF
|
|
75
|
+
findings: list[dict] = []
|
|
76
|
+
sarif_files = list(ws.rglob("*.sarif")) + list(ws.rglob("*.json"))
|
|
77
|
+
for sf in sarif_files:
|
|
78
|
+
try:
|
|
79
|
+
data = json.loads(sf.read_text(encoding="utf-8"))
|
|
80
|
+
# SARIF 2.1.0: runs[].results[]
|
|
81
|
+
for run in data.get("runs", []):
|
|
82
|
+
for res in run.get("results", []):
|
|
83
|
+
findings.append(
|
|
84
|
+
{
|
|
85
|
+
"ruleId": res.get("ruleId", ""),
|
|
86
|
+
"message": res.get("message", {}).get("text", "")[:400],
|
|
87
|
+
"level": res.get("level", "warning"),
|
|
88
|
+
"locations": res.get("locations", [])[:2],
|
|
89
|
+
}
|
|
90
|
+
)
|
|
91
|
+
except Exception as exc: # noqa: BLE001
|
|
92
|
+
_log.debug("shannon sarif parse %s failed: %s", sf, exc)
|
|
93
|
+
ok = proc.returncode == 0
|
|
94
|
+
ev = f"shannon {'ok' if ok else 'fail'} rc={proc.returncode} findings={len(findings)} ws={ws}"
|
|
95
|
+
return PentestResult(
|
|
96
|
+
tool=self.name,
|
|
97
|
+
success=ok,
|
|
98
|
+
evidence=ev,
|
|
99
|
+
findings=findings[:50],
|
|
100
|
+
raw_output=raw[:12000],
|
|
101
|
+
duration_ms=dur,
|
|
102
|
+
error="" if ok else raw[:500],
|
|
103
|
+
)
|
|
104
|
+
except subprocess.TimeoutExpired:
|
|
105
|
+
return PentestResult(tool=self.name, success=False, error="shannon timeout 90m", duration_ms=int((time.monotonic() - t0) * 1000))
|
|
106
|
+
except Exception as exc: # noqa: BLE001
|
|
107
|
+
return PentestResult(tool=self.name, success=False, error=str(exc), duration_ms=int((time.monotonic() - t0) * 1000))
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Sqlmap adapter — SQLi confirmation."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import shutil
|
|
7
|
+
import subprocess
|
|
8
|
+
import time
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from patchi.core.security.pentest.base import PentestResult, PentestTool
|
|
12
|
+
|
|
13
|
+
_log = logging.getLogger("patchi.pentest.sqlmap")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class SqlmapTool(PentestTool):
|
|
17
|
+
name = "sqlmap"
|
|
18
|
+
binary = "sqlmap"
|
|
19
|
+
docker_image = "paoloo/sqlmap:latest"
|
|
20
|
+
|
|
21
|
+
def run(self, target: str, safe_mode: bool = True, workspace: Path | None = None, extra: dict | None = None) -> PentestResult:
|
|
22
|
+
extra = extra or {}
|
|
23
|
+
data = extra.get("data", "") # POST body if needed
|
|
24
|
+
t0 = time.monotonic()
|
|
25
|
+
# Build base command
|
|
26
|
+
base = ["sqlmap", "-u", target, "--batch", "--risk", "1", "--level", "1", "--parse-errors"]
|
|
27
|
+
if safe_mode:
|
|
28
|
+
base += ["--technique", "BEUS"] # no destructive DROP etc, handled by sqlmap risk 1
|
|
29
|
+
if data:
|
|
30
|
+
base += ["--data", data]
|
|
31
|
+
if shutil.which("sqlmap"):
|
|
32
|
+
cmd = base
|
|
33
|
+
elif shutil.which("docker"):
|
|
34
|
+
cmd = ["docker", "run", "--rm", "paoloo/sqlmap:latest"] + base[1:]
|
|
35
|
+
else:
|
|
36
|
+
return PentestResult(tool=self.name, success=False, error="sqlmap not available")
|
|
37
|
+
try:
|
|
38
|
+
proc = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
|
|
39
|
+
dur = int((time.monotonic()-t0)*1000)
|
|
40
|
+
out = proc.stdout + proc.stderr
|
|
41
|
+
findings=[]
|
|
42
|
+
# Simple signal: sqlmap reports "is vulnerable" or lists DBMS
|
|
43
|
+
low = out.lower()
|
|
44
|
+
if "is vulnerable" in low or "parameter" in low and "vulnerable" in low:
|
|
45
|
+
findings.append({"type": "sqli", "target": target, "evidence": "sqlmap reports vulnerable"})
|
|
46
|
+
if "back-end dbms" in low:
|
|
47
|
+
# extract line
|
|
48
|
+
for line in out.splitlines():
|
|
49
|
+
if "back-end dbms" in line.lower():
|
|
50
|
+
findings.append({"type": "dbms-fingerprint", "detail": line.strip()[:200]})
|
|
51
|
+
break
|
|
52
|
+
return PentestResult(tool=self.name, success=True, evidence=f"sqlmap {len(findings)} signals", findings=findings, raw_output=out[:8000], duration_ms=dur)
|
|
53
|
+
except subprocess.TimeoutExpired:
|
|
54
|
+
return PentestResult(tool=self.name, success=False, error="sqlmap timeout 5m", duration_ms=int((time.monotonic()-t0)*1000))
|
|
55
|
+
except Exception as exc: # noqa: BLE001
|
|
56
|
+
return PentestResult(tool=self.name, success=False, error=str(exc), duration_ms=int((time.monotonic()-t0)*1000))
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""ZAP adapter — active scan via zap-api (optional, needs daemon)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import time
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from patchi.core.security.pentest.base import PentestResult, PentestTool
|
|
10
|
+
|
|
11
|
+
_log = logging.getLogger("patchi.pentest.zap")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ZapTool(PentestTool):
|
|
15
|
+
name = "zap"
|
|
16
|
+
binary = "zap.sh"
|
|
17
|
+
docker_image = "ghcr.io/zaproxy/zaproxy:stable"
|
|
18
|
+
|
|
19
|
+
def is_available(self) -> bool:
|
|
20
|
+
# ZAP needs daemon at http://127.0.0.1:8090 or docker
|
|
21
|
+
try:
|
|
22
|
+
import urllib.request
|
|
23
|
+
urllib.request.urlopen("http://127.0.0.1:8090", timeout=2)
|
|
24
|
+
return True
|
|
25
|
+
except Exception as _exc:
|
|
26
|
+
_log.debug("zap daemon not reachable: %s", _exc)
|
|
27
|
+
return super().is_available()
|
|
28
|
+
|
|
29
|
+
def run(self, target: str, safe_mode: bool = True, workspace: Path | None = None, extra: dict | None = None) -> PentestResult:
|
|
30
|
+
t0 = time.monotonic()
|
|
31
|
+
try:
|
|
32
|
+
import json as _json
|
|
33
|
+
import urllib.request
|
|
34
|
+
# Quick spider + active scan via ZAP API if daemon
|
|
35
|
+
base = "http://127.0.0.1:8090/JSON"
|
|
36
|
+
# open URL
|
|
37
|
+
urllib.request.urlopen(f"{base}/core/action/accessUrl/?url={target}", timeout=10).read()
|
|
38
|
+
time.sleep(2)
|
|
39
|
+
# active scan
|
|
40
|
+
scan = _json.loads(urllib.request.urlopen(f"{base}/ascan/action/scan/?url={target}&recurse=true", timeout=30).read())
|
|
41
|
+
scan_id = scan.get("scan")
|
|
42
|
+
# poll
|
|
43
|
+
for _ in range(30):
|
|
44
|
+
time.sleep(5)
|
|
45
|
+
st = _json.loads(urllib.request.urlopen(f"{base}/ascan/view/status/?scanId={scan_id}", timeout=10).read())
|
|
46
|
+
if int(st.get("status", 0)) >= 100:
|
|
47
|
+
break
|
|
48
|
+
alerts = _json.loads(urllib.request.urlopen(f"{base}/core/view/alerts/?baseurl={target}", timeout=10).read())
|
|
49
|
+
findings = [{"alert": a.get("alert",""), "risk": a.get("risk",""), "url": a.get("url","")} for a in alerts.get("alerts", [])[:20]]
|
|
50
|
+
return PentestResult(tool=self.name, success=True, evidence=f"zap {len(findings)} alerts", findings=findings, duration_ms=int((time.monotonic()-t0)*1000))
|
|
51
|
+
except Exception as exc: # noqa: BLE001
|
|
52
|
+
return PentestResult(tool=self.name, success=False, error=f"zap not available: {exc}", duration_ms=int((time.monotonic()-t0)*1000))
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Plan Auditor Agent — audits a Patchi checkout against build plan specs.
|
|
3
|
+
|
|
4
|
+
Only runs when the scanned root actually contains the Patchi package (a
|
|
5
|
+
`patchi/core/security` tree); against any third-party project the checks are
|
|
6
|
+
meaningless — every Patchi-internal path it references is absent by
|
|
7
|
+
construction, so emitting them would fabricate CRITICAL/HIGH findings.
|
|
8
|
+
|
|
9
|
+
Checks that:
|
|
10
|
+
- All agents registered in BUILD_PLAN.md are actually registered
|
|
11
|
+
- All CLI commands exist and are wired up
|
|
12
|
+
- All security scan types work
|
|
13
|
+
- All test files exist for new modules
|
|
14
|
+
- Health score components are wired correctly
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import re
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
|
|
22
|
+
from patchi.core.agents.base import (
|
|
23
|
+
AgentGroup,
|
|
24
|
+
AgentInput,
|
|
25
|
+
AgentResult,
|
|
26
|
+
BaseAgent,
|
|
27
|
+
Finding,
|
|
28
|
+
Severity,
|
|
29
|
+
list_agents,
|
|
30
|
+
register,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@register
|
|
35
|
+
class PlanAuditorAgent(BaseAgent):
|
|
36
|
+
"""Audits codebase against BUILD_PLAN.md specifications."""
|
|
37
|
+
|
|
38
|
+
name = "PlanAuditorAgent"
|
|
39
|
+
group = AgentGroup.GUARD
|
|
40
|
+
timeout = 60
|
|
41
|
+
|
|
42
|
+
def _run(self, inp: AgentInput, result: AgentResult) -> None:
|
|
43
|
+
root = inp.root
|
|
44
|
+
|
|
45
|
+
# This agent audits PATCHI ITSELF against its build plan (registered
|
|
46
|
+
# agents, CLI wiring, Patchi-internal modules). Every check below is
|
|
47
|
+
# meaningless when the scanned project is not a Patchi checkout — the
|
|
48
|
+
# paths it references simply cannot exist in a third-party codebase,
|
|
49
|
+
# so emitting them would fabricate CRITICAL/HIGH findings about a
|
|
50
|
+
# project it isn't auditing. Skip unless a Patchi package is present.
|
|
51
|
+
if not (root / "patchi" / "core" / "security").is_dir():
|
|
52
|
+
return
|
|
53
|
+
|
|
54
|
+
# Check 1: All security agents registered
|
|
55
|
+
self._check_security_agents(root, result)
|
|
56
|
+
|
|
57
|
+
# Check 2: All CLI commands wired
|
|
58
|
+
self._check_cli_commands(root, result)
|
|
59
|
+
|
|
60
|
+
# Check 3: New modules exist
|
|
61
|
+
self._check_new_modules(root, result)
|
|
62
|
+
|
|
63
|
+
# Check 4: Test coverage for new modules
|
|
64
|
+
self._check_test_coverage(root, result)
|
|
65
|
+
|
|
66
|
+
# Check 5: Health score wiring
|
|
67
|
+
self._check_health_wiring(root, result)
|
|
68
|
+
|
|
69
|
+
def _check_security_agents(self, root: Path, result: AgentResult) -> None:
|
|
70
|
+
"""Verify all registered security agents are imported in security_agents.py."""
|
|
71
|
+
expected_agents = [
|
|
72
|
+
a.name
|
|
73
|
+
for a in list_agents()
|
|
74
|
+
if a.group == AgentGroup.SECURITY or a.name == "PlanAuditorAgent"
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
sec_file = root / "patchi" / "core" / "security" / "security_agents.py"
|
|
78
|
+
if not sec_file.exists():
|
|
79
|
+
result.add_finding(
|
|
80
|
+
Finding(
|
|
81
|
+
agent=self.name,
|
|
82
|
+
type="plan_audit",
|
|
83
|
+
severity=Severity.CRITICAL,
|
|
84
|
+
file="patchi/core/security/security_agents.py",
|
|
85
|
+
message="security_agents.py not found",
|
|
86
|
+
)
|
|
87
|
+
)
|
|
88
|
+
return
|
|
89
|
+
|
|
90
|
+
try:
|
|
91
|
+
content = sec_file.read_text(encoding="utf-8")
|
|
92
|
+
except OSError:
|
|
93
|
+
return
|
|
94
|
+
for agent_name in expected_agents:
|
|
95
|
+
if agent_name not in content:
|
|
96
|
+
result.add_finding(
|
|
97
|
+
Finding(
|
|
98
|
+
agent=self.name,
|
|
99
|
+
type="plan_audit",
|
|
100
|
+
severity=Severity.HIGH,
|
|
101
|
+
file="patchi/core/security/security_agents.py",
|
|
102
|
+
message=f"Agent '{agent_name}' not imported in security_agents.py",
|
|
103
|
+
)
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
# Check __all__ includes all agents
|
|
107
|
+
all_match = re.search(r"__all__\s*=\s*\[(.*?)\]", content, re.DOTALL)
|
|
108
|
+
if all_match:
|
|
109
|
+
all_text = all_match.group(1)
|
|
110
|
+
for agent_name in expected_agents:
|
|
111
|
+
if f"'{agent_name}'" not in all_text and f'"{agent_name}"' not in all_text:
|
|
112
|
+
result.add_finding(
|
|
113
|
+
Finding(
|
|
114
|
+
agent=self.name,
|
|
115
|
+
type="plan_audit",
|
|
116
|
+
severity=Severity.MEDIUM,
|
|
117
|
+
file="patchi/core/security/security_agents.py",
|
|
118
|
+
message=f"Agent '{agent_name}' not in __all__",
|
|
119
|
+
)
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
def _check_cli_commands(self, root: Path, result: AgentResult) -> None:
|
|
123
|
+
"""Verify all CLI commands exist."""
|
|
124
|
+
expected_commands = [
|
|
125
|
+
"init",
|
|
126
|
+
"scan",
|
|
127
|
+
"status",
|
|
128
|
+
"fix",
|
|
129
|
+
"review",
|
|
130
|
+
"patch",
|
|
131
|
+
"undo",
|
|
132
|
+
"queue",
|
|
133
|
+
"memory",
|
|
134
|
+
"key",
|
|
135
|
+
"agents",
|
|
136
|
+
"watch",
|
|
137
|
+
"mode",
|
|
138
|
+
"restrict",
|
|
139
|
+
"notify",
|
|
140
|
+
"web",
|
|
141
|
+
"doctor",
|
|
142
|
+
"model",
|
|
143
|
+
"report",
|
|
144
|
+
"hosted",
|
|
145
|
+
"test",
|
|
146
|
+
"security",
|
|
147
|
+
"explain",
|
|
148
|
+
"blast",
|
|
149
|
+
"trend",
|
|
150
|
+
"audit",
|
|
151
|
+
"learn",
|
|
152
|
+
"chat",
|
|
153
|
+
"settings",
|
|
154
|
+
"access",
|
|
155
|
+
"ai",
|
|
156
|
+
"help",
|
|
157
|
+
]
|
|
158
|
+
|
|
159
|
+
cmd_dir = root / "patchi" / "cli" / "commands"
|
|
160
|
+
if not cmd_dir.exists():
|
|
161
|
+
result.add_finding(
|
|
162
|
+
Finding(
|
|
163
|
+
agent=self.name,
|
|
164
|
+
type="plan_audit",
|
|
165
|
+
severity=Severity.CRITICAL,
|
|
166
|
+
file="patchi/cli/commands/",
|
|
167
|
+
message="Commands directory not found",
|
|
168
|
+
)
|
|
169
|
+
)
|
|
170
|
+
return
|
|
171
|
+
|
|
172
|
+
existing = {f.stem for f in cmd_dir.glob("*.py")}
|
|
173
|
+
for cmd in expected_commands:
|
|
174
|
+
expected_file = f"{cmd}_cmd" if cmd != "init" else "init"
|
|
175
|
+
if expected_file not in existing:
|
|
176
|
+
result.add_finding(
|
|
177
|
+
Finding(
|
|
178
|
+
agent=self.name,
|
|
179
|
+
type="plan_audit",
|
|
180
|
+
severity=Severity.HIGH,
|
|
181
|
+
file=f"patchi/cli/commands/{expected_file}.py",
|
|
182
|
+
message=f"CLI command '{cmd}' not found",
|
|
183
|
+
)
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
def _check_new_modules(self, root: Path, result: AgentResult) -> None:
|
|
187
|
+
"""Verify new security modules exist."""
|
|
188
|
+
expected_modules = [
|
|
189
|
+
"orchestrator.py",
|
|
190
|
+
"secrets_guard.py",
|
|
191
|
+
"supply_chain.py",
|
|
192
|
+
"iac_scanner.py",
|
|
193
|
+
"policy_engine.py",
|
|
194
|
+
"cve_monitor.py",
|
|
195
|
+
"red_team_agent.py",
|
|
196
|
+
]
|
|
197
|
+
|
|
198
|
+
sec_dir = root / "patchi" / "core" / "security"
|
|
199
|
+
for mod in expected_modules:
|
|
200
|
+
if not (sec_dir / mod).exists():
|
|
201
|
+
result.add_finding(
|
|
202
|
+
Finding(
|
|
203
|
+
agent=self.name,
|
|
204
|
+
type="plan_audit",
|
|
205
|
+
severity=Severity.HIGH,
|
|
206
|
+
file=f"patchi/core/security/{mod}",
|
|
207
|
+
message=f"New module '{mod}' not found",
|
|
208
|
+
)
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
# Check security_test_agent
|
|
212
|
+
test_dir = root / "patchi" / "core" / "testing"
|
|
213
|
+
if not (test_dir / "security_test_agent.py").exists():
|
|
214
|
+
result.add_finding(
|
|
215
|
+
Finding(
|
|
216
|
+
agent=self.name,
|
|
217
|
+
type="plan_audit",
|
|
218
|
+
severity=Severity.HIGH,
|
|
219
|
+
file="patchi/core/testing/security_test_agent.py",
|
|
220
|
+
message="SecurityTestAgent module not found",
|
|
221
|
+
)
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
def _check_test_coverage(self, root: Path, result: AgentResult) -> None:
|
|
225
|
+
"""Verify tests exist for new modules."""
|
|
226
|
+
test_file = root / "tests" / "test_new_security_agents.py"
|
|
227
|
+
if not test_file.exists():
|
|
228
|
+
result.add_finding(
|
|
229
|
+
Finding(
|
|
230
|
+
agent=self.name,
|
|
231
|
+
type="plan_audit",
|
|
232
|
+
severity=Severity.HIGH,
|
|
233
|
+
file="tests/test_new_security_agents.py",
|
|
234
|
+
message="New security agent tests not found",
|
|
235
|
+
)
|
|
236
|
+
)
|
|
237
|
+
else:
|
|
238
|
+
try:
|
|
239
|
+
content = test_file.read_text(encoding="utf-8")
|
|
240
|
+
except OSError:
|
|
241
|
+
return
|
|
242
|
+
test_count = content.count("def test_")
|
|
243
|
+
if test_count < 20:
|
|
244
|
+
result.add_finding(
|
|
245
|
+
Finding(
|
|
246
|
+
agent=self.name,
|
|
247
|
+
type="plan_audit",
|
|
248
|
+
severity=Severity.MEDIUM,
|
|
249
|
+
file="tests/test_new_security_agents.py",
|
|
250
|
+
message=f"Only {test_count} tests in new security agent test file (expected 20+)",
|
|
251
|
+
)
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
def _check_health_wiring(self, root: Path, result: AgentResult) -> None:
|
|
255
|
+
"""Verify health score references new agents."""
|
|
256
|
+
health_file = root / "patchi" / "core" / "health.py"
|
|
257
|
+
if not health_file.exists():
|
|
258
|
+
return
|
|
259
|
+
|
|
260
|
+
try:
|
|
261
|
+
content = health_file.read_text(encoding="utf-8")
|
|
262
|
+
except OSError:
|
|
263
|
+
return
|
|
264
|
+
# Check that security agents list includes new agents
|
|
265
|
+
new_agents = ["InjectionAgent", "AuthZAgent", "CryptoAgent", "NetworkAgent", "PrivacyAgent"]
|
|
266
|
+
for agent in new_agents:
|
|
267
|
+
if agent not in content:
|
|
268
|
+
result.add_finding(
|
|
269
|
+
Finding(
|
|
270
|
+
agent=self.name,
|
|
271
|
+
type="plan_audit",
|
|
272
|
+
severity=Severity.MEDIUM,
|
|
273
|
+
file="patchi/core/health.py",
|
|
274
|
+
message=f"Health score doesn't reference '{agent}'",
|
|
275
|
+
)
|
|
276
|
+
)
|