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,275 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Dispatcher — routes detection events to the correct specialist agent.
|
|
3
|
+
|
|
4
|
+
Consumes Event objects (raw or Sigma-enriched) and decides:
|
|
5
|
+
1. Which agent to dispatch to (based on technique_id, suggested_agent, confidence)
|
|
6
|
+
2. Whether the action should be synchronous (block) or async (investigate)
|
|
7
|
+
3. Whether to rate-limit, deduplicate, or skip
|
|
8
|
+
|
|
9
|
+
The dispatcher is the orchestration layer between detector output and agent execution.
|
|
10
|
+
It does NOT execute agents itself — it produces DispatchInstruction objects that
|
|
11
|
+
the caller (Coordinator or standalone runner) executes.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import logging
|
|
17
|
+
import time
|
|
18
|
+
from dataclasses import dataclass, field
|
|
19
|
+
from typing import Any
|
|
20
|
+
|
|
21
|
+
from patchi.core.agents.base import list_agents
|
|
22
|
+
from patchi.core.detector.event import Event, EventSeverity, TechniqueID
|
|
23
|
+
|
|
24
|
+
logger = logging.getLogger("patchi.detector.dispatcher")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class DispatchUrgency(str):
|
|
28
|
+
SYNC_BLOCK = "sync_block" # Block the request / stop processing immediately
|
|
29
|
+
SYNC_INVESTIGATE = "sync_inv" # Investigate synchronously (hold response)
|
|
30
|
+
ASYNC_PRIORITY = "async_pri" # High-priority async (process within seconds)
|
|
31
|
+
ASYNC_NORMAL = "async_normal" # Normal async queue
|
|
32
|
+
ASYNC_DEFERRED = "async_defer" # Low-priority, batch later
|
|
33
|
+
DISCARD = "discard" # Drop (below threshold)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class DispatchInstruction:
|
|
38
|
+
"""What the dispatcher wants done — one instruction per event."""
|
|
39
|
+
|
|
40
|
+
event_id: str
|
|
41
|
+
technique_id: str
|
|
42
|
+
agent_name: str
|
|
43
|
+
urgency: str
|
|
44
|
+
confidence: float
|
|
45
|
+
reason: str
|
|
46
|
+
payload: dict[str, Any] = field(default_factory=dict)
|
|
47
|
+
created_at: float = field(default_factory=time.monotonic)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class Dispatcher:
|
|
51
|
+
"""
|
|
52
|
+
Routes events to agents based on technique_id, confidence, and agent availability.
|
|
53
|
+
|
|
54
|
+
Resolution order:
|
|
55
|
+
1. If event has `suggested_agent` AND confidence >= threshold → dispatch to that agent
|
|
56
|
+
2. Map technique_id → agent from TechniqueID.for_agent_type() or agent registration
|
|
57
|
+
3. If no mapping found and severity >= HIGH → dispatch to TriageAgent
|
|
58
|
+
4. Otherwise → DISCARD (below threshold)
|
|
59
|
+
|
|
60
|
+
Urgency is determined by severity:
|
|
61
|
+
- CRITICAL → SYNC_BLOCK
|
|
62
|
+
- HIGH → SYNC_INVESTIGATE
|
|
63
|
+
- MEDIUM → ASYNC_PRIORITY
|
|
64
|
+
- LOW/INFO → ASYNC_NORMAL
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
def __init__(
|
|
68
|
+
self,
|
|
69
|
+
min_confidence: float = 0.3,
|
|
70
|
+
agent_whitelist: set[str] | None = None,
|
|
71
|
+
):
|
|
72
|
+
self._min_confidence = min_confidence
|
|
73
|
+
self._agent_whitelist = agent_whitelist
|
|
74
|
+
self._agent_registry: dict[str, str] = {} # technique_id -> agent_name
|
|
75
|
+
self._dedup_window: dict[str, float] = {} # event_id -> timestamp (for dedup)
|
|
76
|
+
self._dedup_ttl_s = 300.0 # 5 minute dedup window
|
|
77
|
+
|
|
78
|
+
# Build agent registry from TechniqueID mapping + known agents
|
|
79
|
+
self._build_registry()
|
|
80
|
+
|
|
81
|
+
def _build_registry(self) -> None:
|
|
82
|
+
"""Build technique_id → agent_name mapping from all registered agents."""
|
|
83
|
+
for agent_cls in list_agents():
|
|
84
|
+
name = getattr(agent_cls, "name", "") or agent_cls.__name__
|
|
85
|
+
for agent_type in (
|
|
86
|
+
"injection",
|
|
87
|
+
"auth",
|
|
88
|
+
"authz",
|
|
89
|
+
"dependency",
|
|
90
|
+
"secret",
|
|
91
|
+
"network",
|
|
92
|
+
"runtime",
|
|
93
|
+
"crypto",
|
|
94
|
+
"compliance",
|
|
95
|
+
"triage",
|
|
96
|
+
):
|
|
97
|
+
if agent_type.lower() in name.lower():
|
|
98
|
+
tid = TechniqueID.for_agent_type(agent_type)
|
|
99
|
+
self._agent_registry[tid.value] = name
|
|
100
|
+
|
|
101
|
+
# Hard-coded fallbacks for agents that match by technique rather than name
|
|
102
|
+
fallbacks = {
|
|
103
|
+
TechniqueID.INJECTION.value: "InjectionAgent",
|
|
104
|
+
TechniqueID.BRUTE_FORCE.value: "AuthenticationAuditAgent",
|
|
105
|
+
TechniqueID.SECRETS_FROM_STORE.value: "SecretScanner",
|
|
106
|
+
TechniqueID.RESOURCE_DEVELOPMENT.value: "DependencyVulnerabilityAgent",
|
|
107
|
+
TechniqueID.TRAFFIC_SIGNALING.value: "NetworkAgent",
|
|
108
|
+
TechniqueID.EXECUTION.value: "RuntimeValidatorAgent",
|
|
109
|
+
TechniqueID.DEFENSE_EVASION.value: "CryptoAgent",
|
|
110
|
+
TechniqueID.UNKNOWN.value: "TriageAgent",
|
|
111
|
+
}
|
|
112
|
+
self._agent_registry.update(fallbacks)
|
|
113
|
+
|
|
114
|
+
# ── Core dispatch logic ──────────────────────────────────────────────────
|
|
115
|
+
|
|
116
|
+
def dispatch(self, event: Event, sigma_matches: list | None = None) -> DispatchInstruction:
|
|
117
|
+
"""
|
|
118
|
+
Create a dispatch instruction for a single event.
|
|
119
|
+
|
|
120
|
+
Uses both the raw event and any Sigma rule matches to determine routing.
|
|
121
|
+
"""
|
|
122
|
+
event_id = event.event_id
|
|
123
|
+
|
|
124
|
+
# Dedup check
|
|
125
|
+
if event_id in self._dedup_window:
|
|
126
|
+
elapsed = time.monotonic() - self._dedup_window[event_id]
|
|
127
|
+
if elapsed < self._dedup_ttl_s:
|
|
128
|
+
return DispatchInstruction(
|
|
129
|
+
event_id=event_id,
|
|
130
|
+
technique_id="",
|
|
131
|
+
agent_name="",
|
|
132
|
+
urgency=DispatchUrgency.DISCARD,
|
|
133
|
+
confidence=0.0,
|
|
134
|
+
reason="Deduplicated (same event_id within window)",
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
self._dedup_window[event_id] = time.monotonic()
|
|
138
|
+
|
|
139
|
+
# Step 1: Extract the best technique_id and agent hint
|
|
140
|
+
technique_id, confidence, suggested_agent = self._resolve_routing(event, sigma_matches)
|
|
141
|
+
|
|
142
|
+
if technique_id == TechniqueID.NONE.value:
|
|
143
|
+
return DispatchInstruction(
|
|
144
|
+
event_id=event_id,
|
|
145
|
+
technique_id=technique_id,
|
|
146
|
+
agent_name="",
|
|
147
|
+
urgency=DispatchUrgency.DISCARD,
|
|
148
|
+
confidence=0.0,
|
|
149
|
+
reason="No technique mapping (info/heartbeat)",
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
# Step 2: Find the agent to dispatch to
|
|
153
|
+
agent_name = self._resolve_agent(technique_id, suggested_agent)
|
|
154
|
+
|
|
155
|
+
if not agent_name:
|
|
156
|
+
if event.severity.sort_key() <= EventSeverity.LOW.sort_key():
|
|
157
|
+
return DispatchInstruction(
|
|
158
|
+
event_id=event_id,
|
|
159
|
+
technique_id=technique_id,
|
|
160
|
+
agent_name="",
|
|
161
|
+
urgency=DispatchUrgency.DISCARD,
|
|
162
|
+
confidence=confidence,
|
|
163
|
+
reason=f"No agent found for technique {technique_id} and severity too low",
|
|
164
|
+
)
|
|
165
|
+
agent_name = "TriageAgent"
|
|
166
|
+
|
|
167
|
+
# Step 3: Apply whitelist filter
|
|
168
|
+
if self._agent_whitelist and agent_name not in self._agent_whitelist:
|
|
169
|
+
return DispatchInstruction(
|
|
170
|
+
event_id=event_id,
|
|
171
|
+
technique_id=technique_id,
|
|
172
|
+
agent_name="",
|
|
173
|
+
urgency=DispatchUrgency.DISCARD,
|
|
174
|
+
confidence=confidence,
|
|
175
|
+
reason=f"Agent {agent_name} not in whitelist",
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
# Step 4: Determine urgency
|
|
179
|
+
urgency = self._resolve_urgency(event.severity, confidence)
|
|
180
|
+
|
|
181
|
+
# Build reason string
|
|
182
|
+
reason_parts = []
|
|
183
|
+
if suggested_agent:
|
|
184
|
+
reason_parts.append(f"suggested={suggested_agent}")
|
|
185
|
+
reason_parts.append(f"confidence={confidence:.2f}")
|
|
186
|
+
reason_parts.append(f"severity={event.severity.value}")
|
|
187
|
+
if sigma_matches:
|
|
188
|
+
reason_parts.append(f"sigma_rules={len(sigma_matches)}")
|
|
189
|
+
|
|
190
|
+
return DispatchInstruction(
|
|
191
|
+
event_id=event_id,
|
|
192
|
+
technique_id=technique_id,
|
|
193
|
+
agent_name=agent_name,
|
|
194
|
+
urgency=urgency,
|
|
195
|
+
confidence=confidence,
|
|
196
|
+
reason="; ".join(reason_parts),
|
|
197
|
+
payload=event.payload,
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
def dispatch_batch(self, events: list[Event]) -> list[DispatchInstruction]:
|
|
201
|
+
"""Dispatch a batch of events. Deduplicates by event_id."""
|
|
202
|
+
seen: set[str] = set()
|
|
203
|
+
results: list[DispatchInstruction] = []
|
|
204
|
+
for event in events:
|
|
205
|
+
if event.event_id in seen:
|
|
206
|
+
continue
|
|
207
|
+
seen.add(event.event_id)
|
|
208
|
+
instruction = self.dispatch(event)
|
|
209
|
+
if instruction.urgency != DispatchUrgency.DISCARD:
|
|
210
|
+
results.append(instruction)
|
|
211
|
+
return results
|
|
212
|
+
|
|
213
|
+
# ── Helpers ───────────────────────────────────────────────────────────────
|
|
214
|
+
|
|
215
|
+
def _resolve_routing(
|
|
216
|
+
self,
|
|
217
|
+
event: Event,
|
|
218
|
+
sigma_matches: list | None,
|
|
219
|
+
) -> tuple[str, float, str]:
|
|
220
|
+
"""Determine the best technique_id, confidence, and suggested_agent."""
|
|
221
|
+
tid = (
|
|
222
|
+
event.technique_id.value
|
|
223
|
+
if isinstance(event.technique_id, TechniqueID)
|
|
224
|
+
else event.technique_id
|
|
225
|
+
)
|
|
226
|
+
confidence = event.confidence
|
|
227
|
+
suggested_agent = event.suggested_agent or ""
|
|
228
|
+
|
|
229
|
+
# If Sigma matched, boost confidence and use Sigma's technique_id
|
|
230
|
+
if sigma_matches:
|
|
231
|
+
best = sigma_matches[0]
|
|
232
|
+
tid = (
|
|
233
|
+
best.technique_id.value
|
|
234
|
+
if isinstance(best.technique_id, TechniqueID)
|
|
235
|
+
else str(best.technique_id)
|
|
236
|
+
)
|
|
237
|
+
confidence = max(confidence, best.confidence)
|
|
238
|
+
if best.target_agent:
|
|
239
|
+
suggested_agent = best.target_agent
|
|
240
|
+
|
|
241
|
+
return tid, confidence, suggested_agent
|
|
242
|
+
|
|
243
|
+
def _resolve_agent(self, technique_id: str, suggested_agent: str | None) -> str:
|
|
244
|
+
"""Find the agent to handle this technique, with fallback logic."""
|
|
245
|
+
if suggested_agent:
|
|
246
|
+
return suggested_agent
|
|
247
|
+
return self._agent_registry.get(technique_id, "")
|
|
248
|
+
|
|
249
|
+
def _resolve_urgency(self, severity: EventSeverity, confidence: float) -> str:
|
|
250
|
+
"""Map severity + confidence to urgency level."""
|
|
251
|
+
if severity == EventSeverity.CRITICAL:
|
|
252
|
+
if confidence >= 0.7:
|
|
253
|
+
return DispatchUrgency.SYNC_BLOCK
|
|
254
|
+
return DispatchUrgency.SYNC_INVESTIGATE
|
|
255
|
+
if severity == EventSeverity.HIGH:
|
|
256
|
+
if confidence >= 0.5:
|
|
257
|
+
return DispatchUrgency.SYNC_INVESTIGATE
|
|
258
|
+
return DispatchUrgency.ASYNC_PRIORITY
|
|
259
|
+
if severity == EventSeverity.MEDIUM:
|
|
260
|
+
return (
|
|
261
|
+
DispatchUrgency.ASYNC_PRIORITY
|
|
262
|
+
if confidence >= 0.5
|
|
263
|
+
else DispatchUrgency.ASYNC_NORMAL
|
|
264
|
+
)
|
|
265
|
+
return DispatchUrgency.ASYNC_NORMAL
|
|
266
|
+
|
|
267
|
+
def stats(self) -> dict:
|
|
268
|
+
return {
|
|
269
|
+
"agent_registry_size": len(self._agent_registry),
|
|
270
|
+
"dedup_window_size": len(self._dedup_window),
|
|
271
|
+
"known_techniques": list(self._agent_registry.keys()),
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
def clear_dedup(self) -> None:
|
|
275
|
+
self._dedup_window.clear()
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Event schema — the universal envelope for everything Patchi detects, routes, or acts on.
|
|
3
|
+
|
|
4
|
+
Every signal source (HTTP traffic, logs, syscalls, git diffs, agent findings) normalizes
|
|
5
|
+
into this schema before reaching the dispatcher. Every action, override, and audit log
|
|
6
|
+
entry also uses it. This is the single contract between all pipeline stages.
|
|
7
|
+
|
|
8
|
+
MITRE ATT&CK v14 technique IDs are used as the primary classification axis because:
|
|
9
|
+
- They're stable, versioned, and community-maintained (not invented here)
|
|
10
|
+
- They map 1:1 to the specialist agent roster
|
|
11
|
+
- Atomic Red Team tests emit known technique_ids, making end-to-end validation tractable
|
|
12
|
+
- OWASP/CWE mappings are secondary labels (we maintain the bidirectional map)
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import uuid
|
|
18
|
+
from dataclasses import dataclass, field
|
|
19
|
+
from datetime import UTC, datetime
|
|
20
|
+
from enum import StrEnum
|
|
21
|
+
from typing import Any
|
|
22
|
+
|
|
23
|
+
# ── Event severity (extends the Finding severity model) ──────────────────────
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class EventSeverity(StrEnum):
|
|
27
|
+
DEBUG = "debug" # No alerting, diagnostics only
|
|
28
|
+
INFO = "info" # Informational, never alerts
|
|
29
|
+
LOW = "low" # Digest-only, no immediate action
|
|
30
|
+
MEDIUM = "medium" # Worthy of investigation
|
|
31
|
+
HIGH = "high" # Requires prompt attention
|
|
32
|
+
CRITICAL = "critical" # Immediate action required
|
|
33
|
+
|
|
34
|
+
def sort_key(self) -> int:
|
|
35
|
+
return {
|
|
36
|
+
"debug": 5,
|
|
37
|
+
"info": 4,
|
|
38
|
+
"low": 3,
|
|
39
|
+
"medium": 2,
|
|
40
|
+
"high": 1,
|
|
41
|
+
"critical": 0,
|
|
42
|
+
}[self.value]
|
|
43
|
+
|
|
44
|
+
def to_enum(self) -> str:
|
|
45
|
+
return self.value
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
# ── Event source types ──────────────────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class EventSource(StrEnum):
|
|
52
|
+
"""Where the event originated. Each maps to a signal source type."""
|
|
53
|
+
|
|
54
|
+
AGENT = "agent" # A Patchi agent produced a finding
|
|
55
|
+
HTTP_TRAFFIC = "http_traffic" # Ingress/egress HTTP request
|
|
56
|
+
LOG_LINE = "log_line" # Application or access log
|
|
57
|
+
SYSCALL = "syscall" # Host/container syscall (Falco)
|
|
58
|
+
NETWORK_FLOW = "network_flow" # Network protocol event (Suricata/Zeek)
|
|
59
|
+
DEPENDENCY_SCAN = "dependency" # Dependency manifest / CVE scan
|
|
60
|
+
SECRET_SCAN = "secret" # Secret scanning hit
|
|
61
|
+
GIT_EVENT = "git_event" # Commit, PR, diff, push
|
|
62
|
+
CONFIG_CHANGE = "config" # Config file or env var change
|
|
63
|
+
HEARTBEAT = "heartbeat" # System health / uptime check
|
|
64
|
+
USER_ACTION = "user_action" # Explicit user command or override
|
|
65
|
+
ANOMALY = "anomaly" # Detected by the anomaly layer
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
# ── MITRE ATT&CK technique / tactic IDs ──────────────────────────────────────
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
# Selected ATT&CK technique IDs relevant to Patchi's agent roster.
|
|
72
|
+
# v14+ stable IDs; see https://attack.mitre.org/techniques/enterprise/
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class TechniqueID(StrEnum):
|
|
76
|
+
INITIAL_ACCESS = "T1190" # Exploit Public-Facing Application
|
|
77
|
+
EXECUTION = "T1203" # Exploitation for Client Execution
|
|
78
|
+
PERSISTENCE = "T1505" # Server Software Component
|
|
79
|
+
DEFENSE_EVASION = "T1562" # Impair Defenses
|
|
80
|
+
CREDENTIAL_ACCESS = "T1552" # Unsecured Credentials
|
|
81
|
+
DISCOVERY = "T1046" # Network Service Discovery
|
|
82
|
+
COLLECTION = "T1567" # Exfiltration Over Web Service
|
|
83
|
+
COMMAND_AND_CONTROL = "T1071" # Application Layer Protocol
|
|
84
|
+
EXFILTRATION = "T1048" # Exfiltration Over Alternative Protocol
|
|
85
|
+
IMPACT = "T1499" # Endpoint Denial of Service
|
|
86
|
+
RESOURCE_DEVELOPMENT = "T1583" # Acquire Infrastructure
|
|
87
|
+
INJECTION = "T1055" # Process Injection
|
|
88
|
+
VALID_ACCOUNTS = "T1078" # Valid Accounts
|
|
89
|
+
BRUTE_FORCE = "T1110" # Brute Force
|
|
90
|
+
INPUT_CAPTURE = "T1056" # Input Capture
|
|
91
|
+
MODIFY_AUTH_PROCESS = "T1556" # Modify Authentication Process
|
|
92
|
+
STEAL_APPLICATION_ACCESS = "T1528" # Steal Application Access Token
|
|
93
|
+
SECRETS_FROM_STORE = "T1552.001" # Credentials in Files / Keychains
|
|
94
|
+
EXPLOIT_PUBLIC_APP = "T1190" # Exploit Public-Facing Application
|
|
95
|
+
MAN_IN_MIDDLE = "T1557" # Adversary-in-the-Middle
|
|
96
|
+
TRAFFIC_SIGNALING = "T1573" # Encrypted Channel
|
|
97
|
+
PROXY = "T1090" # Proxy
|
|
98
|
+
UNKNOWN = "T9999" # Unclassified / needs triage
|
|
99
|
+
NONE = "T0000" # No technique mapping (info/heartbeat)
|
|
100
|
+
|
|
101
|
+
@classmethod
|
|
102
|
+
def for_agent_type(cls, agent_type: str) -> TechniqueID:
|
|
103
|
+
mapping = {
|
|
104
|
+
"injection": cls.INJECTION,
|
|
105
|
+
"auth": cls.BRUTE_FORCE,
|
|
106
|
+
"authz": cls.VALID_ACCOUNTS,
|
|
107
|
+
"dependency": cls.RESOURCE_DEVELOPMENT,
|
|
108
|
+
"secret": cls.SECRETS_FROM_STORE,
|
|
109
|
+
"network": cls.TRAFFIC_SIGNALING,
|
|
110
|
+
"runtime": cls.EXECUTION,
|
|
111
|
+
"triage": cls.UNKNOWN,
|
|
112
|
+
"crypto": cls.DEFENSE_EVASION,
|
|
113
|
+
"compliance": cls.IMPACT,
|
|
114
|
+
}
|
|
115
|
+
return mapping.get(agent_type, cls.UNKNOWN)
|
|
116
|
+
|
|
117
|
+
@classmethod
|
|
118
|
+
def display_name(cls, tid: str) -> str:
|
|
119
|
+
names = {
|
|
120
|
+
"T1190": "Exploit Public-Facing Application",
|
|
121
|
+
"T1203": "Exploitation for Client Execution",
|
|
122
|
+
"T1505": "Persistent Server Software Component",
|
|
123
|
+
"T1562": "Impair Defenses",
|
|
124
|
+
"T1552": "Unsecured Credentials",
|
|
125
|
+
"T1046": "Network Service Discovery",
|
|
126
|
+
"T1567": "Exfiltration Over Web Service",
|
|
127
|
+
"T1071": "Application Layer Protocol C2",
|
|
128
|
+
"T1048": "Exfiltration Over Alternative Protocol",
|
|
129
|
+
"T1499": "Endpoint Denial of Service",
|
|
130
|
+
"T1583": "Acquire Infrastructure",
|
|
131
|
+
"T1055": "Process Injection",
|
|
132
|
+
"T1078": "Valid Accounts",
|
|
133
|
+
"T1110": "Brute Force",
|
|
134
|
+
"T1056": "Input Capture",
|
|
135
|
+
"T1556": "Modify Authentication Process",
|
|
136
|
+
"T1528": "Steal Application Access Token",
|
|
137
|
+
"T1552.001": "Credentials from Files",
|
|
138
|
+
"T1557": "Adversary-in-the-Middle",
|
|
139
|
+
"T1573": "Encrypted Channel",
|
|
140
|
+
"T1090": "Proxy",
|
|
141
|
+
"T9999": "Unclassified",
|
|
142
|
+
"T0000": "None",
|
|
143
|
+
}
|
|
144
|
+
return names.get(tid, "Unknown Technique")
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
# ── The event envelope ───────────────────────────────────────────────────────
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
@dataclass
|
|
151
|
+
class Event:
|
|
152
|
+
"""
|
|
153
|
+
Universal event envelope.
|
|
154
|
+
|
|
155
|
+
Every signal in the system normalizes to this shape before entering the pipeline.
|
|
156
|
+
Immutable after creation (consumers should not mutate).
|
|
157
|
+
|
|
158
|
+
The `id` is a deterministic hash of the event's key fields so the same external
|
|
159
|
+
signal always produces the same event ID (useful for deduplication). Override with
|
|
160
|
+
`override_id` for synthetic events.
|
|
161
|
+
"""
|
|
162
|
+
|
|
163
|
+
source: EventSource
|
|
164
|
+
technique_id: TechniqueID | str
|
|
165
|
+
|
|
166
|
+
# Core payload
|
|
167
|
+
summary: str # One-line human-readable description
|
|
168
|
+
severity: EventSeverity = EventSeverity.INFO
|
|
169
|
+
payload: dict[str, Any] = field(default_factory=dict) # Signal-specific data
|
|
170
|
+
|
|
171
|
+
# Routing hints — populated by the detector, consumed by the dispatcher
|
|
172
|
+
suggested_agent: str | None = None # e.g. "InjectionAgent"
|
|
173
|
+
confidence: float = 0.5 # 0.0–1.0, how sure the detector is
|
|
174
|
+
|
|
175
|
+
# Enrichment
|
|
176
|
+
agent_name: str | None = None # Which agent produced this (if from an agent)
|
|
177
|
+
finding_id: str | None = None # Link back to a specific Finding if applicable
|
|
178
|
+
cwe_ids: list[str] = field(default_factory=list)
|
|
179
|
+
owasp_category: str = ""
|
|
180
|
+
|
|
181
|
+
# Metadata
|
|
182
|
+
timestamp: str = field(default_factory=lambda: datetime.now(UTC).isoformat())
|
|
183
|
+
event_id: str = field(default_factory=lambda: uuid.uuid4().hex[:16])
|
|
184
|
+
source_details: dict[str, str] = field(default_factory=dict) # host, container, file, repo
|
|
185
|
+
|
|
186
|
+
# Audit chain
|
|
187
|
+
parent_event_id: str | None = None # Which event triggered this one
|
|
188
|
+
|
|
189
|
+
def to_dict(self) -> dict:
|
|
190
|
+
return {
|
|
191
|
+
"event_id": self.event_id,
|
|
192
|
+
"source": self.source.value,
|
|
193
|
+
"technique_id": self.technique_id.value
|
|
194
|
+
if isinstance(self.technique_id, TechniqueID)
|
|
195
|
+
else self.technique_id,
|
|
196
|
+
"summary": self.summary,
|
|
197
|
+
"severity": self.severity.value,
|
|
198
|
+
"payload": self.payload,
|
|
199
|
+
"suggested_agent": self.suggested_agent,
|
|
200
|
+
"confidence": self.confidence,
|
|
201
|
+
"agent_name": self.agent_name,
|
|
202
|
+
"finding_id": self.finding_id,
|
|
203
|
+
"cwe_ids": self.cwe_ids,
|
|
204
|
+
"owasp_category": self.owasp_category,
|
|
205
|
+
"timestamp": self.timestamp,
|
|
206
|
+
"source_details": self.source_details,
|
|
207
|
+
"parent_event_id": self.parent_event_id,
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
@property
|
|
211
|
+
def technique_display(self) -> str:
|
|
212
|
+
tid = (
|
|
213
|
+
self.technique_id.value
|
|
214
|
+
if isinstance(self.technique_id, TechniqueID)
|
|
215
|
+
else self.technique_id
|
|
216
|
+
)
|
|
217
|
+
return f"{tid} — {TechniqueID.display_name(tid)}"
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
# ── EventBatch (for bulk processing) ─────────────────────────────────────────
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
@dataclass
|
|
224
|
+
class EventBatch:
|
|
225
|
+
"""A time-windowed batch of events for bulk dispatch/audit."""
|
|
226
|
+
|
|
227
|
+
events: list[Event] = field(default_factory=list)
|
|
228
|
+
batch_id: str = field(default_factory=lambda: uuid.uuid4().hex[:16])
|
|
229
|
+
created: str = field(default_factory=lambda: datetime.now(UTC).isoformat())
|
|
230
|
+
|
|
231
|
+
def by_severity(self) -> dict[str, list[Event]]:
|
|
232
|
+
groups: dict[str, list[Event]] = {}
|
|
233
|
+
for ev in self.events:
|
|
234
|
+
groups.setdefault(ev.severity.value, []).append(ev)
|
|
235
|
+
return groups
|
|
236
|
+
|
|
237
|
+
def by_technique(self) -> dict[str, list[Event]]:
|
|
238
|
+
groups: dict[str, list[Event]] = {}
|
|
239
|
+
for ev in self.events:
|
|
240
|
+
tid = (
|
|
241
|
+
ev.technique_id.value
|
|
242
|
+
if isinstance(ev.technique_id, TechniqueID)
|
|
243
|
+
else ev.technique_id
|
|
244
|
+
)
|
|
245
|
+
groups.setdefault(tid, []).append(ev)
|
|
246
|
+
return groups
|
|
247
|
+
|
|
248
|
+
def to_dict(self) -> dict:
|
|
249
|
+
return {
|
|
250
|
+
"batch_id": self.batch_id,
|
|
251
|
+
"created": self.created,
|
|
252
|
+
"count": len(self.events),
|
|
253
|
+
"events": [e.to_dict() for e in self.events],
|
|
254
|
+
}
|