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,913 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Dynamic Security Domain Activator v2 — Intelligent, signal-based domain activation.
|
|
3
|
+
|
|
4
|
+
Replaces static domain loading with adaptive activation based on:
|
|
5
|
+
- Code signals (imports, patterns, frameworks)
|
|
6
|
+
- Configuration signals (env vars, config files, infra)
|
|
7
|
+
- Runtime signals (running services, open ports)
|
|
8
|
+
- Historical signals (past findings, incident history)
|
|
9
|
+
- Project context (business domain, compliance requirements)
|
|
10
|
+
|
|
11
|
+
Only activates domains that are actually relevant, reducing scan time and noise.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import logging
|
|
17
|
+
import re
|
|
18
|
+
from dataclasses import dataclass, field
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
from patchi.core import memory as mem
|
|
22
|
+
from patchi.core.brain.scanner import FileInfo
|
|
23
|
+
from patchi.core.security.domain_loader import Domain, DomainLoader
|
|
24
|
+
|
|
25
|
+
_log = logging.getLogger("patchi.security.domain_activator_v2")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class ActivationSignal:
|
|
30
|
+
"""A signal that contributes to domain activation."""
|
|
31
|
+
|
|
32
|
+
source: str # "code", "config", "runtime", "history", "context"
|
|
33
|
+
domain: str
|
|
34
|
+
weight: float # 0.0 - 1.0
|
|
35
|
+
evidence: str # Human-readable evidence
|
|
36
|
+
metadata: dict = field(default_factory=dict)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass
|
|
40
|
+
class DomainActivationResult:
|
|
41
|
+
"""Result of domain activation analysis."""
|
|
42
|
+
|
|
43
|
+
activated_domains: list[str]
|
|
44
|
+
signals: list[ActivationSignal]
|
|
45
|
+
domain_scores: dict[str, float]
|
|
46
|
+
skipped_domains: list[str]
|
|
47
|
+
activation_threshold: float
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# ── Domain Signal Definitions ────────────────────────────────────────────────
|
|
51
|
+
|
|
52
|
+
# Each domain has a set of signals that activate it
|
|
53
|
+
# Signals are weighted by reliability (higher = more reliable indicator)
|
|
54
|
+
DOMAIN_SIGNALS: dict[str, dict[str, list[tuple[str, float, str]]]] = {
|
|
55
|
+
# Injection domains
|
|
56
|
+
"injection-sql": {
|
|
57
|
+
"code": [
|
|
58
|
+
(r"\.execute\s*\([^)]*%[sf]", 0.9, "String formatting in SQL execute"),
|
|
59
|
+
# Any `.execute(...)` / `.executemany(...)` built with `+` string
|
|
60
|
+
# concatenation — catches conn/cursor/session variants alike.
|
|
61
|
+
(
|
|
62
|
+
r"\.execute(?:many)?\s*\([^)]*['\"]\s*\+",
|
|
63
|
+
0.85,
|
|
64
|
+
"String concatenation in SQL execute",
|
|
65
|
+
),
|
|
66
|
+
(r"cursor\.execute\s*\([^)]*\+", 0.8, "String concatenation in cursor SQL"),
|
|
67
|
+
(r"SELECT.*FROM.*WHERE.*\{", 0.7, "Template-style SQL"),
|
|
68
|
+
(r"f['\"].*SELECT", 0.7, "F-string SQL query"),
|
|
69
|
+
(r"\.raw\s*\([^)]*SELECT", 0.8, "Raw SQL usage"),
|
|
70
|
+
],
|
|
71
|
+
"config": [
|
|
72
|
+
(r"DATABASE_URL", 0.3, "Database connection configured"),
|
|
73
|
+
],
|
|
74
|
+
},
|
|
75
|
+
"injection-nosql": {
|
|
76
|
+
"code": [
|
|
77
|
+
(r"\.find\s*\(\s*\{[^}]*\$where", 0.9, "MongoDB $where injection"),
|
|
78
|
+
(r"\.aggregate\s*\([^)]*\$expr", 0.7, "Aggregation with user input"),
|
|
79
|
+
(r"eval\s*\([^)]*db\.", 0.8, "JavaScript eval with DB"),
|
|
80
|
+
],
|
|
81
|
+
},
|
|
82
|
+
"injection-command": {
|
|
83
|
+
"code": [
|
|
84
|
+
(
|
|
85
|
+
r"subprocess\.(run|call|Popen)\s*\([^)]*shell\s*=\s*True",
|
|
86
|
+
0.9,
|
|
87
|
+
"Shell=True with user input",
|
|
88
|
+
),
|
|
89
|
+
(r"os\.system\s*\(", 0.95, "os.system usage"),
|
|
90
|
+
(r"os\.popen\s*\(", 0.9, "os.popen usage"),
|
|
91
|
+
(r"commands\.getstatusoutput", 0.8, "commands module usage"),
|
|
92
|
+
(r"shlex\.split\s*\([^)]*input", 0.7, "shlex with user input"),
|
|
93
|
+
],
|
|
94
|
+
},
|
|
95
|
+
"injection-ldap": {
|
|
96
|
+
"code": [
|
|
97
|
+
(r"ldap3.*\([^)]*\)", 0.6, "LDAP library usage"),
|
|
98
|
+
(r"python-ldap", 0.5, "LDAP import"),
|
|
99
|
+
],
|
|
100
|
+
},
|
|
101
|
+
"injection-xpath": {
|
|
102
|
+
"code": [
|
|
103
|
+
(r"lxml.*xpath", 0.6, "XPath usage"),
|
|
104
|
+
(r"etree\.XPath", 0.7, "XPath evaluation"),
|
|
105
|
+
],
|
|
106
|
+
},
|
|
107
|
+
"injection-template": {
|
|
108
|
+
"code": [
|
|
109
|
+
(r"jinja2\.Environment.*autoescape\s*=\s*False", 0.9, "Jinja2 autoescape disabled"),
|
|
110
|
+
(r"render_template_string\s*\(", 0.8, "Flask render_template_string"),
|
|
111
|
+
(r"Template\s*\([^)]*request\.", 0.7, "Template with request data"),
|
|
112
|
+
(r"mustache|handlebars|nunjucks", 0.5, "Template engine usage"),
|
|
113
|
+
],
|
|
114
|
+
},
|
|
115
|
+
# XSS
|
|
116
|
+
"xss-reflected": {
|
|
117
|
+
"code": [
|
|
118
|
+
(r"request\.(args|query_params|GET)\[", 0.7, "Direct request param usage"),
|
|
119
|
+
(r"HttpResponse\s*\([^)]*request\.", 0.8, "Direct response with request data"),
|
|
120
|
+
(r"res\.send\s*\([^)]*req\.", 0.7, "Express res.send with req data"),
|
|
121
|
+
],
|
|
122
|
+
},
|
|
123
|
+
"xss-stored": {
|
|
124
|
+
"code": [
|
|
125
|
+
(r"innerHTML\s*=", 0.8, "Direct innerHTML assignment"),
|
|
126
|
+
(r"dangerouslySetInnerHTML", 0.9, "React dangerouslySetInnerHTML"),
|
|
127
|
+
(r"v-html\s*=", 0.8, "Vue v-html directive"),
|
|
128
|
+
(r"\.html\s*\([^)]*user", 0.7, "jQuery .html() with user data"),
|
|
129
|
+
],
|
|
130
|
+
},
|
|
131
|
+
"xss-dom": {
|
|
132
|
+
"code": [
|
|
133
|
+
(r"document\.write\s*\(", 0.9, "document.write usage"),
|
|
134
|
+
(r"location\.hash", 0.6, "Hash-based routing"),
|
|
135
|
+
(r"eval\s*\([^)]*location", 0.8, "Eval with location"),
|
|
136
|
+
],
|
|
137
|
+
},
|
|
138
|
+
# Auth & Session
|
|
139
|
+
"authentication": {
|
|
140
|
+
"code": [
|
|
141
|
+
(r"@login_required|@auth_required", 0.8, "Auth decorator usage"),
|
|
142
|
+
(r"passport\.authenticate", 0.8, "Passport.js usage"),
|
|
143
|
+
(r"flask_login|django\.contrib\.auth", 0.7, "Auth framework"),
|
|
144
|
+
(r"bcrypt|argon2|scrypt|PBKDF2", 0.6, "Password hashing"),
|
|
145
|
+
(r"session\[|session\.get\(", 0.7, "Session usage"),
|
|
146
|
+
],
|
|
147
|
+
"config": [
|
|
148
|
+
(r"SECRET_KEY|JWT_SECRET", 0.5, "Auth secret configured"),
|
|
149
|
+
],
|
|
150
|
+
},
|
|
151
|
+
"authorization": {
|
|
152
|
+
"code": [
|
|
153
|
+
(r"@permission_required|@role_required", 0.8, "Permission decorators"),
|
|
154
|
+
(r"rbac|acl|casbin", 0.6, "Authorization library"),
|
|
155
|
+
(r"policy\.enforce|can\s*\(", 0.7, "Policy enforcement"),
|
|
156
|
+
],
|
|
157
|
+
},
|
|
158
|
+
"session-management": {
|
|
159
|
+
"code": [
|
|
160
|
+
(r"session\.cookie_secure\s*=\s*False", 0.8, "Insecure session cookie"),
|
|
161
|
+
(r"session\.cookie_httponly\s*=\s*False", 0.8, "Non-HttpOnly cookie"),
|
|
162
|
+
(r"same_site\s*=\s*['\"]?none", 0.7, "SameSite=None"),
|
|
163
|
+
],
|
|
164
|
+
},
|
|
165
|
+
"jwt-security": {
|
|
166
|
+
"code": [
|
|
167
|
+
(r"jwt\.encode|jwt\.decode", 0.7, "JWT usage"),
|
|
168
|
+
(r"algorithm\s*=\s*['\"]none['\"]", 0.95, "JWT alg=none"),
|
|
169
|
+
(r"verify\s*=\s*False", 0.9, "JWT verification disabled"),
|
|
170
|
+
],
|
|
171
|
+
},
|
|
172
|
+
# Secrets
|
|
173
|
+
"secrets-management": {
|
|
174
|
+
"code": [
|
|
175
|
+
(
|
|
176
|
+
r"(api[_-]?key|secret|password|token)\s*=\s*['\"][^'\"]{16,}",
|
|
177
|
+
0.9,
|
|
178
|
+
"Hardcoded secret",
|
|
179
|
+
),
|
|
180
|
+
(r"os\.environ\[['\"][A-Z_]*KEY", 0.6, "Env var access"),
|
|
181
|
+
(r"vault|keyring|secretsmanager", 0.5, "Secret management lib"),
|
|
182
|
+
],
|
|
183
|
+
"config": [
|
|
184
|
+
(r"\.env", 0.4, "Env file present"),
|
|
185
|
+
],
|
|
186
|
+
},
|
|
187
|
+
# Crypto
|
|
188
|
+
"cryptography": {
|
|
189
|
+
"code": [
|
|
190
|
+
(r"Crypto\.Cipher|pycryptodome|cryptography\.hazmat", 0.7, "Crypto library"),
|
|
191
|
+
(r"MD5|SHA1|DES|RC4", 0.8, "Weak algorithm"),
|
|
192
|
+
(r"ECB\s*mode", 0.7, "ECB mode"),
|
|
193
|
+
(r"IV\s*=\s*['\"]0{16}", 0.8, "Static IV"),
|
|
194
|
+
],
|
|
195
|
+
},
|
|
196
|
+
"tls-ssl": {
|
|
197
|
+
"code": [
|
|
198
|
+
(r"verify\s*=\s*False", 0.8, "SSL verify disabled"),
|
|
199
|
+
(r"ssl\._create_unverified_context", 0.9, "Unverified SSL context"),
|
|
200
|
+
(r"check_hostname\s*=\s*False", 0.8, "Hostname check disabled"),
|
|
201
|
+
],
|
|
202
|
+
"config": [
|
|
203
|
+
(r"TLS|SSL|HTTPS", 0.4, "TLS config present"),
|
|
204
|
+
],
|
|
205
|
+
},
|
|
206
|
+
# SSRF
|
|
207
|
+
"ssrf": {
|
|
208
|
+
"code": [
|
|
209
|
+
(r"requests\.(get|post|put|delete)\s*\([^)]*request\.", 0.8, "Request with user URL"),
|
|
210
|
+
(r"urllib\.request\.urlopen\s*\([^)]*input", 0.8, "urlopen with input"),
|
|
211
|
+
(r"httpx\.get\s*\([^)]*user", 0.7, "httpx with user input"),
|
|
212
|
+
(r"fetch\s*\([^)]*input", 0.7, "Fetch with user input"),
|
|
213
|
+
],
|
|
214
|
+
},
|
|
215
|
+
# Path Traversal
|
|
216
|
+
"path-traversal": {
|
|
217
|
+
"code": [
|
|
218
|
+
(r"open\s*\([^)]*request\.", 0.8, "File open with request"),
|
|
219
|
+
(r"send_file\s*\([^)]*request\.", 0.8, "Flask send_file with request"),
|
|
220
|
+
(r"path\.join\s*\([^)]*\.\.", 0.7, "Path join with traversal"),
|
|
221
|
+
(r"os\.path\.join.*user", 0.7, "Path join with user input"),
|
|
222
|
+
],
|
|
223
|
+
},
|
|
224
|
+
# Deserialization
|
|
225
|
+
"deserialization": {
|
|
226
|
+
"code": [
|
|
227
|
+
(r"pickle\.loads?\s*\(", 0.95, "Pickle deserialization"),
|
|
228
|
+
(r"marshal\.loads?\s*\(", 0.9, "Marshal deserialization"),
|
|
229
|
+
(r"yaml\.load\s*\([^)]*\)", 0.8, "YAML load without SafeLoader"),
|
|
230
|
+
(r"jsonpickle|dill|cloudpickle", 0.7, "Serialization library"),
|
|
231
|
+
(r"java\.io\.ObjectInputStream", 0.9, "Java deserialization"),
|
|
232
|
+
],
|
|
233
|
+
},
|
|
234
|
+
# XXE
|
|
235
|
+
"xxe": {
|
|
236
|
+
"code": [
|
|
237
|
+
(r"etree\.parse\s*\([^)]*request", 0.8, "XML parse with request"),
|
|
238
|
+
(r"XMLParser\s*\([^)]*resolve_entities\s*=\s*True", 0.9, "Entity resolution enabled"),
|
|
239
|
+
(
|
|
240
|
+
r"SAXParser.*setFeature.*external-general-entities.*true",
|
|
241
|
+
0.8,
|
|
242
|
+
"SAX external entities",
|
|
243
|
+
),
|
|
244
|
+
],
|
|
245
|
+
},
|
|
246
|
+
# CORS
|
|
247
|
+
"cors": {
|
|
248
|
+
"code": [
|
|
249
|
+
(r"CORS\s*\([^)]*origins\s*=\s*['\"]\*['\"]", 0.9, "CORS wildcard origin"),
|
|
250
|
+
(r"Access-Control-Allow-Origin\s*:\s*\*", 0.9, "Wildcard CORS header"),
|
|
251
|
+
(r"cors\.allow_all_origins\s*=\s*True", 0.8, "Allow all origins"),
|
|
252
|
+
],
|
|
253
|
+
},
|
|
254
|
+
# CSRF
|
|
255
|
+
"csrf": {
|
|
256
|
+
"code": [
|
|
257
|
+
(r"@csrf_exempt", 0.9, "CSRF exemption"),
|
|
258
|
+
(r"csrf\.protect\s*=\s*False", 0.8, "CSRF disabled"),
|
|
259
|
+
(r"WTF_CSRF_ENABLED\s*=\s*False", 0.8, "Flask-WTF CSRF disabled"),
|
|
260
|
+
],
|
|
261
|
+
},
|
|
262
|
+
# Security Headers
|
|
263
|
+
"security-headers": {
|
|
264
|
+
"code": [
|
|
265
|
+
(r"helmet|secure-headers", 0.5, "Security headers lib"),
|
|
266
|
+
(r"X-Frame-Options|CSP|HSTS", 0.6, "Security header usage"),
|
|
267
|
+
],
|
|
268
|
+
"config": [
|
|
269
|
+
(r"SECURE_.*_HEADER", 0.5, "Security header config"),
|
|
270
|
+
],
|
|
271
|
+
},
|
|
272
|
+
# Rate Limiting
|
|
273
|
+
"rate-limiting": {
|
|
274
|
+
"code": [
|
|
275
|
+
(r"flask_limiter|django_ratelimit|express-rate-limit", 0.6, "Rate limit lib"),
|
|
276
|
+
(r"@limiter\.limit|@ratelimit", 0.7, "Rate limit decorator"),
|
|
277
|
+
],
|
|
278
|
+
"config": [
|
|
279
|
+
(r"RATE_LIMIT|RATELIMIT", 0.4, "Rate limit config"),
|
|
280
|
+
],
|
|
281
|
+
},
|
|
282
|
+
# Supply Chain
|
|
283
|
+
"supply-chain": {
|
|
284
|
+
"code": [
|
|
285
|
+
(
|
|
286
|
+
r"package\.json|requirements\.txt|pyproject\.toml|Cargo\.toml|go\.mod",
|
|
287
|
+
0.4,
|
|
288
|
+
"Dependency file",
|
|
289
|
+
),
|
|
290
|
+
],
|
|
291
|
+
"config": [
|
|
292
|
+
(r"dependabot|renovate|snyk", 0.5, "Dependency bot config"),
|
|
293
|
+
],
|
|
294
|
+
},
|
|
295
|
+
"dependency-vulnerability": {
|
|
296
|
+
"code": [
|
|
297
|
+
(
|
|
298
|
+
r"package\.json|requirements\.txt|pyproject\.toml|Cargo\.toml|go\.mod",
|
|
299
|
+
0.4,
|
|
300
|
+
"Dependency file",
|
|
301
|
+
),
|
|
302
|
+
],
|
|
303
|
+
},
|
|
304
|
+
# Container/Cloud
|
|
305
|
+
"container-security": {
|
|
306
|
+
"code": [
|
|
307
|
+
(r"Dockerfile|docker-compose\.ya?ml", 0.6, "Docker config"),
|
|
308
|
+
(r"FROM\s+\w+:", 0.5, "Base image"),
|
|
309
|
+
],
|
|
310
|
+
},
|
|
311
|
+
"kubernetes-security": {
|
|
312
|
+
"code": [
|
|
313
|
+
(r"\.ya?ml$", 0.3, "YAML file"),
|
|
314
|
+
(r"apiVersion:|kind: (Deployment|Service|Pod|Ingress)", 0.7, "K8s resource"),
|
|
315
|
+
(r"securityContext|runAsNonRoot|readOnlyRootFilesystem", 0.6, "Security context"),
|
|
316
|
+
],
|
|
317
|
+
},
|
|
318
|
+
"cloud-security": {
|
|
319
|
+
"code": [
|
|
320
|
+
(r"boto3|google-cloud|azure-mgmt", 0.5, "Cloud SDK"),
|
|
321
|
+
(r"terraform|cloudformation|pulumi", 0.5, "IaC"),
|
|
322
|
+
],
|
|
323
|
+
},
|
|
324
|
+
# Runtime
|
|
325
|
+
"runtime-protection": {
|
|
326
|
+
"code": [
|
|
327
|
+
(r"falco|sysdig|tracee", 0.6, "Runtime security tool"),
|
|
328
|
+
],
|
|
329
|
+
"config": [
|
|
330
|
+
(r"falco|runtime", 0.4, "Runtime config"),
|
|
331
|
+
],
|
|
332
|
+
},
|
|
333
|
+
# Privacy
|
|
334
|
+
"privacy-gdpr": {
|
|
335
|
+
"code": [
|
|
336
|
+
(r"gdpr|personal_data|pii|data_subject", 0.6, "GDPR keywords"),
|
|
337
|
+
(r"anonymize|pseudonymize|consent", 0.6, "Privacy functions"),
|
|
338
|
+
],
|
|
339
|
+
"config": [
|
|
340
|
+
(r"GDPR|PRIVACY|DATA_PROTECTION", 0.4, "Privacy config"),
|
|
341
|
+
],
|
|
342
|
+
},
|
|
343
|
+
# Business Logic
|
|
344
|
+
"business-logic": {
|
|
345
|
+
"code": [
|
|
346
|
+
(r"transfer|payment|checkout|order", 0.5, "Financial keywords"),
|
|
347
|
+
(r"workflow|state_machine|saga", 0.5, "Workflow patterns"),
|
|
348
|
+
],
|
|
349
|
+
},
|
|
350
|
+
# API Security
|
|
351
|
+
"api-security": {
|
|
352
|
+
"code": [
|
|
353
|
+
(r"openapi|swagger|graphql", 0.5, "API schema"),
|
|
354
|
+
(r"rate_limit|throttle|quota", 0.5, "API protection"),
|
|
355
|
+
],
|
|
356
|
+
},
|
|
357
|
+
# Logging/Audit
|
|
358
|
+
"audit-logging": {
|
|
359
|
+
"code": [
|
|
360
|
+
(r"audit|auditlog|structlog", 0.5, "Audit library"),
|
|
361
|
+
(r"logging\.audit|logger\.audit", 0.6, "Audit logging"),
|
|
362
|
+
],
|
|
363
|
+
},
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
# ── Signal Extraction ────────────────────────────────────────────────────────
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
class SignalExtractor:
|
|
371
|
+
"""Extracts activation signals from project artifacts."""
|
|
372
|
+
|
|
373
|
+
def __init__(self, root: Path):
|
|
374
|
+
self.root = root
|
|
375
|
+
self._code_cache: dict[str, str] = {}
|
|
376
|
+
|
|
377
|
+
def extract_all_signals(
|
|
378
|
+
self,
|
|
379
|
+
file_infos: list[FileInfo],
|
|
380
|
+
project_context: dict,
|
|
381
|
+
) -> list[ActivationSignal]:
|
|
382
|
+
"""Extract all signals from code, config, and context."""
|
|
383
|
+
signals = []
|
|
384
|
+
|
|
385
|
+
# Code signals
|
|
386
|
+
signals.extend(self._extract_code_signals(file_infos))
|
|
387
|
+
|
|
388
|
+
# Config signals
|
|
389
|
+
signals.extend(self._extract_config_signals())
|
|
390
|
+
|
|
391
|
+
# Context signals (frameworks, domains, etc.)
|
|
392
|
+
signals.extend(self._extract_context_signals(project_context))
|
|
393
|
+
|
|
394
|
+
# History signals (past findings)
|
|
395
|
+
signals.extend(self._extract_history_signals())
|
|
396
|
+
|
|
397
|
+
return signals
|
|
398
|
+
|
|
399
|
+
def _extract_code_signals(self, file_infos: list[FileInfo]) -> list[ActivationSignal]:
|
|
400
|
+
signals = []
|
|
401
|
+
|
|
402
|
+
for fi in file_infos:
|
|
403
|
+
if fi.error:
|
|
404
|
+
continue
|
|
405
|
+
|
|
406
|
+
# Get file content (cached)
|
|
407
|
+
content = self._get_file_content(fi.path)
|
|
408
|
+
if not content:
|
|
409
|
+
continue
|
|
410
|
+
|
|
411
|
+
content.lower()
|
|
412
|
+
file_path = fi.path
|
|
413
|
+
|
|
414
|
+
# Check each domain's code signals
|
|
415
|
+
for domain, signal_defs in DOMAIN_SIGNALS.items():
|
|
416
|
+
code_signals = signal_defs.get("code", [])
|
|
417
|
+
for pattern, weight, description in code_signals:
|
|
418
|
+
if re.search(pattern, content, re.IGNORECASE):
|
|
419
|
+
signals.append(
|
|
420
|
+
ActivationSignal(
|
|
421
|
+
source="code",
|
|
422
|
+
domain=domain,
|
|
423
|
+
weight=weight,
|
|
424
|
+
evidence=f"{description} in {file_path}",
|
|
425
|
+
metadata={"file": file_path, "pattern": pattern},
|
|
426
|
+
)
|
|
427
|
+
)
|
|
428
|
+
|
|
429
|
+
return signals
|
|
430
|
+
|
|
431
|
+
def _extract_config_signals(self) -> list[ActivationSignal]:
|
|
432
|
+
signals = []
|
|
433
|
+
|
|
434
|
+
# Check common config files
|
|
435
|
+
config_files = [
|
|
436
|
+
".env",
|
|
437
|
+
"config.yaml",
|
|
438
|
+
"config.yml",
|
|
439
|
+
"settings.py",
|
|
440
|
+
"application.properties",
|
|
441
|
+
"application.yml",
|
|
442
|
+
"docker-compose.yml",
|
|
443
|
+
"docker-compose.yaml",
|
|
444
|
+
"kubernetes/",
|
|
445
|
+
"k8s/",
|
|
446
|
+
"helm/",
|
|
447
|
+
"terraform/",
|
|
448
|
+
".tf",
|
|
449
|
+
"package.json",
|
|
450
|
+
"requirements.txt",
|
|
451
|
+
"pyproject.toml",
|
|
452
|
+
"Cargo.toml",
|
|
453
|
+
"go.mod",
|
|
454
|
+
"pom.xml",
|
|
455
|
+
]
|
|
456
|
+
|
|
457
|
+
for config_path in config_files:
|
|
458
|
+
full_path = self.root / config_path
|
|
459
|
+
if not full_path.exists():
|
|
460
|
+
# Try glob for directories
|
|
461
|
+
if config_path.endswith("/"):
|
|
462
|
+
matches = list(self.root.rglob(config_path[:-1] + "/*"))
|
|
463
|
+
if not matches:
|
|
464
|
+
continue
|
|
465
|
+
full_path = matches[0]
|
|
466
|
+
else:
|
|
467
|
+
continue
|
|
468
|
+
|
|
469
|
+
try:
|
|
470
|
+
if full_path.is_file():
|
|
471
|
+
content = full_path.read_text(encoding="utf-8", errors="ignore")
|
|
472
|
+
else:
|
|
473
|
+
continue
|
|
474
|
+
except Exception:
|
|
475
|
+
continue
|
|
476
|
+
|
|
477
|
+
content.lower()
|
|
478
|
+
|
|
479
|
+
for domain, signal_defs in DOMAIN_SIGNALS.items():
|
|
480
|
+
config_signals = signal_defs.get("config", [])
|
|
481
|
+
for pattern, weight, description in config_signals:
|
|
482
|
+
if re.search(pattern, content, re.IGNORECASE):
|
|
483
|
+
signals.append(
|
|
484
|
+
ActivationSignal(
|
|
485
|
+
source="config",
|
|
486
|
+
domain=domain,
|
|
487
|
+
weight=weight,
|
|
488
|
+
evidence=f"{description} in {config_path}",
|
|
489
|
+
metadata={"config_file": config_path, "pattern": pattern},
|
|
490
|
+
)
|
|
491
|
+
)
|
|
492
|
+
|
|
493
|
+
return signals
|
|
494
|
+
|
|
495
|
+
def _extract_context_signals(self, project_context: dict) -> list[ActivationSignal]:
|
|
496
|
+
signals = []
|
|
497
|
+
|
|
498
|
+
# Framework-based signals
|
|
499
|
+
frameworks = project_context.get("frameworks", [])
|
|
500
|
+
for fw in frameworks:
|
|
501
|
+
fw_name = fw.get("name", "").lower()
|
|
502
|
+
|
|
503
|
+
# Framework implies certain domains
|
|
504
|
+
if fw_name in ("django", "flask", "fastapi", "express", "spring", "gin"):
|
|
505
|
+
signals.append(
|
|
506
|
+
ActivationSignal(
|
|
507
|
+
source="context",
|
|
508
|
+
domain="api-security",
|
|
509
|
+
weight=0.6,
|
|
510
|
+
evidence=f"Web framework {fw_name} detected",
|
|
511
|
+
)
|
|
512
|
+
)
|
|
513
|
+
signals.append(
|
|
514
|
+
ActivationSignal(
|
|
515
|
+
source="context",
|
|
516
|
+
domain="authentication",
|
|
517
|
+
weight=0.5,
|
|
518
|
+
evidence=f"Web framework {fw_name} typically has auth",
|
|
519
|
+
)
|
|
520
|
+
)
|
|
521
|
+
signals.append(
|
|
522
|
+
ActivationSignal(
|
|
523
|
+
source="context",
|
|
524
|
+
domain="security-headers",
|
|
525
|
+
weight=0.4,
|
|
526
|
+
evidence=f"Web framework {fw_name} needs security headers",
|
|
527
|
+
)
|
|
528
|
+
)
|
|
529
|
+
|
|
530
|
+
if fw_name in ("react", "vue", "svelte", "angular", "next.js", "nuxt"):
|
|
531
|
+
signals.append(
|
|
532
|
+
ActivationSignal(
|
|
533
|
+
source="context",
|
|
534
|
+
domain="xss-dom",
|
|
535
|
+
weight=0.6,
|
|
536
|
+
evidence=f"Frontend framework {fw_name} - DOM XSS relevant",
|
|
537
|
+
)
|
|
538
|
+
)
|
|
539
|
+
signals.append(
|
|
540
|
+
ActivationSignal(
|
|
541
|
+
source="context",
|
|
542
|
+
domain="api-security",
|
|
543
|
+
weight=0.5,
|
|
544
|
+
evidence=f"Frontend framework {fw_name} consumes APIs",
|
|
545
|
+
)
|
|
546
|
+
)
|
|
547
|
+
|
|
548
|
+
# Component type signals
|
|
549
|
+
comp_type = project_context.get("component_type", "")
|
|
550
|
+
if comp_type:
|
|
551
|
+
if "frontend" in comp_type:
|
|
552
|
+
signals.append(
|
|
553
|
+
ActivationSignal(
|
|
554
|
+
source="context",
|
|
555
|
+
domain="xss-dom",
|
|
556
|
+
weight=0.7,
|
|
557
|
+
evidence="Frontend component type",
|
|
558
|
+
)
|
|
559
|
+
)
|
|
560
|
+
if "backend" in comp_type or "api" in comp_type:
|
|
561
|
+
signals.append(
|
|
562
|
+
ActivationSignal(
|
|
563
|
+
source="context",
|
|
564
|
+
domain="api-security",
|
|
565
|
+
weight=0.7,
|
|
566
|
+
evidence="Backend/API component type",
|
|
567
|
+
)
|
|
568
|
+
)
|
|
569
|
+
if "mobile" in comp_type:
|
|
570
|
+
signals.append(
|
|
571
|
+
ActivationSignal(
|
|
572
|
+
source="context",
|
|
573
|
+
domain="mobile-security",
|
|
574
|
+
weight=0.7,
|
|
575
|
+
evidence="Mobile component type",
|
|
576
|
+
)
|
|
577
|
+
)
|
|
578
|
+
if "infra" in comp_type or "devops" in comp_type:
|
|
579
|
+
signals.append(
|
|
580
|
+
ActivationSignal(
|
|
581
|
+
source="context",
|
|
582
|
+
domain="container-security",
|
|
583
|
+
weight=0.6,
|
|
584
|
+
evidence="Infrastructure component type",
|
|
585
|
+
)
|
|
586
|
+
)
|
|
587
|
+
signals.append(
|
|
588
|
+
ActivationSignal(
|
|
589
|
+
source="context",
|
|
590
|
+
domain="kubernetes-security",
|
|
591
|
+
weight=0.5,
|
|
592
|
+
evidence="Infrastructure component type",
|
|
593
|
+
)
|
|
594
|
+
)
|
|
595
|
+
|
|
596
|
+
# Active security domains from brain
|
|
597
|
+
active_domains = project_context.get("active_security_domains", [])
|
|
598
|
+
for domain in active_domains:
|
|
599
|
+
signals.append(
|
|
600
|
+
ActivationSignal(
|
|
601
|
+
source="context",
|
|
602
|
+
domain=domain,
|
|
603
|
+
weight=0.8,
|
|
604
|
+
evidence=f"Previously activated domain: {domain}",
|
|
605
|
+
)
|
|
606
|
+
)
|
|
607
|
+
|
|
608
|
+
# Deployment model
|
|
609
|
+
deploy_model = project_context.get("deployment_model", "")
|
|
610
|
+
if deploy_model in ("kubernetes", "k8s", "container"):
|
|
611
|
+
signals.append(
|
|
612
|
+
ActivationSignal(
|
|
613
|
+
source="context",
|
|
614
|
+
domain="kubernetes-security",
|
|
615
|
+
weight=0.7,
|
|
616
|
+
evidence=f"Deployment model: {deploy_model}",
|
|
617
|
+
)
|
|
618
|
+
)
|
|
619
|
+
signals.append(
|
|
620
|
+
ActivationSignal(
|
|
621
|
+
source="context",
|
|
622
|
+
domain="container-security",
|
|
623
|
+
weight=0.7,
|
|
624
|
+
evidence=f"Deployment model: {deploy_model}",
|
|
625
|
+
)
|
|
626
|
+
)
|
|
627
|
+
elif deploy_model in ("serverless", "lambda", "cloudflare", "vercel"):
|
|
628
|
+
signals.append(
|
|
629
|
+
ActivationSignal(
|
|
630
|
+
source="context",
|
|
631
|
+
domain="cloud-security",
|
|
632
|
+
weight=0.7,
|
|
633
|
+
evidence=f"Deployment model: {deploy_model}",
|
|
634
|
+
)
|
|
635
|
+
)
|
|
636
|
+
|
|
637
|
+
return signals
|
|
638
|
+
|
|
639
|
+
def _extract_history_signals(self) -> list[ActivationSignal]:
|
|
640
|
+
signals = []
|
|
641
|
+
|
|
642
|
+
# Check past scan results for recurring findings
|
|
643
|
+
scan_results = mem.get_scan_results(self.root)
|
|
644
|
+
domain_finding_counts: dict[str, int] = {}
|
|
645
|
+
|
|
646
|
+
for _scanner_name, data in scan_results.items():
|
|
647
|
+
for finding in data.get("findings", []):
|
|
648
|
+
if isinstance(finding, dict):
|
|
649
|
+
finding_type = finding.get("type", "")
|
|
650
|
+
# Map finding types to domains
|
|
651
|
+
domain = self._map_finding_to_domain(finding_type)
|
|
652
|
+
if domain:
|
|
653
|
+
domain_finding_counts[domain] = domain_finding_counts.get(domain, 0) + 1
|
|
654
|
+
|
|
655
|
+
# If a domain had findings before, it's more likely to be relevant
|
|
656
|
+
for domain, count in domain_finding_counts.items():
|
|
657
|
+
if count >= 2: # At least 2 historical findings
|
|
658
|
+
signals.append(
|
|
659
|
+
ActivationSignal(
|
|
660
|
+
source="history",
|
|
661
|
+
domain=domain,
|
|
662
|
+
weight=min(0.3 + count * 0.1, 0.8),
|
|
663
|
+
evidence=f"{count} historical findings in {domain}",
|
|
664
|
+
metadata={"historical_count": count},
|
|
665
|
+
)
|
|
666
|
+
)
|
|
667
|
+
|
|
668
|
+
return signals
|
|
669
|
+
|
|
670
|
+
def _map_finding_to_domain(self, finding_type: str) -> str | None:
|
|
671
|
+
"""Map finding type to security domain."""
|
|
672
|
+
type_lower = finding_type.lower()
|
|
673
|
+
|
|
674
|
+
mapping = {
|
|
675
|
+
"sql_injection": "injection-sql",
|
|
676
|
+
"nosql_injection": "injection-nosql",
|
|
677
|
+
"command_injection": "injection-command",
|
|
678
|
+
"ldap_injection": "injection-ldap",
|
|
679
|
+
"xpath_injection": "injection-xpath",
|
|
680
|
+
"template_injection": "injection-template",
|
|
681
|
+
"xss": "xss-reflected",
|
|
682
|
+
"xss_reflected": "xss-reflected",
|
|
683
|
+
"xss_stored": "xss-stored",
|
|
684
|
+
"xss_dom": "xss-dom",
|
|
685
|
+
"csrf": "csrf",
|
|
686
|
+
"cors": "cors",
|
|
687
|
+
"ssrf": "ssrf",
|
|
688
|
+
"path_traversal": "path-traversal",
|
|
689
|
+
"deserialization": "deserialization",
|
|
690
|
+
"xxe": "xxe",
|
|
691
|
+
"jwt": "jwt-security",
|
|
692
|
+
"auth": "authentication",
|
|
693
|
+
"authz": "authorization",
|
|
694
|
+
"session": "session-management",
|
|
695
|
+
"secret": "secrets-management",
|
|
696
|
+
"crypto": "cryptography",
|
|
697
|
+
"tls": "tls-ssl",
|
|
698
|
+
"supply_chain": "supply-chain",
|
|
699
|
+
"dependency": "dependency-vulnerability",
|
|
700
|
+
"container": "container-security",
|
|
701
|
+
"kubernetes": "kubernetes-security",
|
|
702
|
+
"cloud": "cloud-security",
|
|
703
|
+
"runtime": "runtime-protection",
|
|
704
|
+
"privacy": "privacy-gdpr",
|
|
705
|
+
"business_logic": "business-logic",
|
|
706
|
+
"api": "api-security",
|
|
707
|
+
"audit": "audit-logging",
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
for key, domain in mapping.items():
|
|
711
|
+
if key in type_lower:
|
|
712
|
+
return domain
|
|
713
|
+
return None
|
|
714
|
+
|
|
715
|
+
def _get_file_content(self, file_path: str) -> str | None:
|
|
716
|
+
"""Get file content with caching."""
|
|
717
|
+
if file_path in self._code_cache:
|
|
718
|
+
return self._code_cache[file_path]
|
|
719
|
+
|
|
720
|
+
full_path = self.root / file_path
|
|
721
|
+
if not full_path.exists():
|
|
722
|
+
return None
|
|
723
|
+
|
|
724
|
+
try:
|
|
725
|
+
content = full_path.read_text(encoding="utf-8", errors="ignore")
|
|
726
|
+
# Cache only smaller files to avoid memory issues
|
|
727
|
+
if len(content) < 100_000:
|
|
728
|
+
self._code_cache[file_path] = content
|
|
729
|
+
return content
|
|
730
|
+
except Exception:
|
|
731
|
+
return None
|
|
732
|
+
|
|
733
|
+
|
|
734
|
+
# ── Domain Scoring & Activation ──────────────────────────────────────────────
|
|
735
|
+
|
|
736
|
+
|
|
737
|
+
class DomainActivatorV2:
|
|
738
|
+
"""Main domain activation engine."""
|
|
739
|
+
|
|
740
|
+
def __init__(
|
|
741
|
+
self,
|
|
742
|
+
root: Path,
|
|
743
|
+
activation_threshold: float = 0.5,
|
|
744
|
+
max_domains: int = 20,
|
|
745
|
+
domain_loader=None,
|
|
746
|
+
):
|
|
747
|
+
self.root = root
|
|
748
|
+
self.activation_threshold = activation_threshold
|
|
749
|
+
self.max_domains = max_domains
|
|
750
|
+
self.extractor = SignalExtractor(root)
|
|
751
|
+
# Callers that preloaded the taxonomy in the background (CLI/web scan)
|
|
752
|
+
# inject it here so activation never blocks on a cold DomainLoader.
|
|
753
|
+
self.domain_loader = domain_loader or DomainLoader(root)
|
|
754
|
+
|
|
755
|
+
def activate_domains(
|
|
756
|
+
self,
|
|
757
|
+
file_infos: list[FileInfo],
|
|
758
|
+
project_context: dict,
|
|
759
|
+
forced_domains: list[str] = None,
|
|
760
|
+
) -> DomainActivationResult:
|
|
761
|
+
"""
|
|
762
|
+
Activate security domains based on extracted signals.
|
|
763
|
+
|
|
764
|
+
Args:
|
|
765
|
+
file_infos: Scanned file information
|
|
766
|
+
project_context: Project context from brain
|
|
767
|
+
forced_domains: Domains to force-activate (e.g., from user config)
|
|
768
|
+
|
|
769
|
+
Returns:
|
|
770
|
+
DomainActivationResult with activated domains and scoring details
|
|
771
|
+
"""
|
|
772
|
+
forced_domains = forced_domains or []
|
|
773
|
+
|
|
774
|
+
# Extract all signals
|
|
775
|
+
signals = self.extractor.extract_all_signals(file_infos, project_context)
|
|
776
|
+
|
|
777
|
+
# Score each domain
|
|
778
|
+
domain_scores: dict[str, float] = {}
|
|
779
|
+
domain_evidence: dict[str, list[str]] = {}
|
|
780
|
+
|
|
781
|
+
for signal in signals:
|
|
782
|
+
domain = signal.domain
|
|
783
|
+
domain_scores[domain] = domain_scores.get(domain, 0) + signal.weight
|
|
784
|
+
if domain not in domain_evidence:
|
|
785
|
+
domain_evidence[domain] = []
|
|
786
|
+
domain_evidence[domain].append(signal.evidence)
|
|
787
|
+
|
|
788
|
+
# Add forced domains with high score
|
|
789
|
+
for domain in forced_domains:
|
|
790
|
+
domain_scores[domain] = max(domain_scores.get(domain, 0), 1.0)
|
|
791
|
+
if domain not in domain_evidence:
|
|
792
|
+
domain_evidence[domain] = []
|
|
793
|
+
domain_evidence[domain].append("Forced activation (user/config)")
|
|
794
|
+
|
|
795
|
+
# Filter by threshold
|
|
796
|
+
activated = [
|
|
797
|
+
domain for domain, score in domain_scores.items() if score >= self.activation_threshold
|
|
798
|
+
]
|
|
799
|
+
|
|
800
|
+
# Sort by score descending
|
|
801
|
+
activated.sort(key=lambda d: domain_scores[d], reverse=True)
|
|
802
|
+
|
|
803
|
+
# Limit to max_domains
|
|
804
|
+
if len(activated) > self.max_domains:
|
|
805
|
+
activated[self.max_domains :]
|
|
806
|
+
activated = activated[: self.max_domains]
|
|
807
|
+
else:
|
|
808
|
+
pass
|
|
809
|
+
|
|
810
|
+
# Get all known domains for reporting skipped
|
|
811
|
+
all_known = set(domain_scores.keys())
|
|
812
|
+
all_known.update(self.domain_loader.list_domains())
|
|
813
|
+
skipped_domains = sorted(all_known - set(activated))
|
|
814
|
+
|
|
815
|
+
_log.info(f"Activated {len(activated)} domains (threshold={self.activation_threshold})")
|
|
816
|
+
for d in activated:
|
|
817
|
+
_log.debug(f" {d}: score={domain_scores[d]:.2f}, evidence={domain_evidence[d][:2]}")
|
|
818
|
+
|
|
819
|
+
return DomainActivationResult(
|
|
820
|
+
activated_domains=activated,
|
|
821
|
+
signals=signals,
|
|
822
|
+
domain_scores=domain_scores,
|
|
823
|
+
skipped_domains=skipped_domains,
|
|
824
|
+
activation_threshold=self.activation_threshold,
|
|
825
|
+
)
|
|
826
|
+
|
|
827
|
+
def get_activated_domain_objects(self, activated_domains: list[str]) -> dict[str, Domain]:
|
|
828
|
+
"""Load full Domain objects for activated domains."""
|
|
829
|
+
domains = {}
|
|
830
|
+
for domain_id in activated_domains:
|
|
831
|
+
domain = self.domain_loader.get_domain(domain_id)
|
|
832
|
+
if domain:
|
|
833
|
+
domains[domain_id] = domain
|
|
834
|
+
else:
|
|
835
|
+
_log.warning(f"Activated domain not found in loader: {domain_id}")
|
|
836
|
+
return domains
|
|
837
|
+
|
|
838
|
+
def get_relevant_agents(self, activated_domains: list[str]) -> list[str]:
|
|
839
|
+
"""Map activated domains to relevant security agents."""
|
|
840
|
+
# Domain -> agent mapping
|
|
841
|
+
domain_agent_map = {
|
|
842
|
+
"injection-sql": ["InjectionAgent", "BanditAgent", "SemgrepAgent"],
|
|
843
|
+
"injection-nosql": ["InjectionAgent", "SemgrepAgent"],
|
|
844
|
+
"injection-command": ["InjectionAgent", "BanditAgent"],
|
|
845
|
+
"injection-ldap": ["InjectionAgent"],
|
|
846
|
+
"injection-xpath": ["InjectionAgent"],
|
|
847
|
+
"injection-template": ["InjectionAgent", "SemgrepAgent"],
|
|
848
|
+
"xss-reflected": ["InjectionAgent", "SemgrepAgent", "BrowserTesterAgent"],
|
|
849
|
+
"xss-stored": ["InjectionAgent", "BrowserTesterAgent"],
|
|
850
|
+
"xss-dom": ["BrowserTesterAgent", "SemgrepAgent"],
|
|
851
|
+
"authentication": [
|
|
852
|
+
"AuthenticationAuditAgent",
|
|
853
|
+
"JWTSecurityAgent",
|
|
854
|
+
"SessionManagementAgent",
|
|
855
|
+
],
|
|
856
|
+
"authorization": ["AuthZAgent", "BusinessLogicAgent"],
|
|
857
|
+
"session-management": ["SessionManagementAgent", "JWTSecurityAgent"],
|
|
858
|
+
"jwt-security": ["JWTSecurityAgent", "AuthenticationAuditAgent"],
|
|
859
|
+
"secrets-management": ["SecretScanner", "SecretsGuard", "SecretsRuntimeAgent"],
|
|
860
|
+
"cryptography": ["CryptoAgent", "BanditAgent"],
|
|
861
|
+
"tls-ssl": ["CryptoAgent", "HeaderAuditAgent"],
|
|
862
|
+
"ssrf": ["SSRFProtectionAgent", "NetworkAgent"],
|
|
863
|
+
"path-traversal": ["InjectionAgent", "SemgrepAgent"],
|
|
864
|
+
"deserialization": ["InjectionAgent", "BanditAgent", "SemgrepAgent"],
|
|
865
|
+
"xxe": ["InjectionAgent", "SemgrepAgent"],
|
|
866
|
+
"cors": ["CORSAuditor", "SecurityProber"],
|
|
867
|
+
"csrf": ["SecurityProber", "SemgrepAgent"],
|
|
868
|
+
"security-headers": ["HeaderAuditAgent", "SecurityConfigAgent"],
|
|
869
|
+
"rate-limiting": ["RateLimitAuditor", "SecurityConfigAgent"],
|
|
870
|
+
"supply-chain": ["SupplyChainAgent", "DependencyVulnerabilityAgent", "CVEMonitorAgent"],
|
|
871
|
+
"dependency-vulnerability": [
|
|
872
|
+
"DependencyVulnerabilityAgent",
|
|
873
|
+
"CVEMonitorAgent",
|
|
874
|
+
"OSVScannerAgent",
|
|
875
|
+
],
|
|
876
|
+
"container-security": ["ContainerScannerAgent", "IaCScannerAgent"],
|
|
877
|
+
"kubernetes-security": ["KubernetesAgent", "IaCScannerAgent"],
|
|
878
|
+
"cloud-security": ["IaCScannerAgent", "ConfigAuditAgent"],
|
|
879
|
+
"runtime-protection": ["FalcoRuntimeAgent", "RuntimeValidatorAgent"],
|
|
880
|
+
"privacy-gdpr": ["PrivacyAgent", "SensitiveDataAgent"],
|
|
881
|
+
"business-logic": ["BusinessLogicAgent"],
|
|
882
|
+
"api-security": ["SecurityProber", "APIContractAgent"],
|
|
883
|
+
"audit-logging": ["HistoryAgent", "GovernanceAgent"],
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
agents = set()
|
|
887
|
+
for domain in activated_domains:
|
|
888
|
+
agents.update(domain_agent_map.get(domain, []))
|
|
889
|
+
|
|
890
|
+
# Always include core agents
|
|
891
|
+
core_agents = ["RedTeamAgent", "PreCheckAgent", "PlanAuditorAgent"]
|
|
892
|
+
agents.update(core_agents)
|
|
893
|
+
|
|
894
|
+
return sorted(agents)
|
|
895
|
+
|
|
896
|
+
|
|
897
|
+
# ── Convenience Function ─────────────────────────────────────────────────────
|
|
898
|
+
|
|
899
|
+
|
|
900
|
+
def activate_security_domains(
|
|
901
|
+
root: Path,
|
|
902
|
+
file_infos: list[FileInfo],
|
|
903
|
+
project_context: dict,
|
|
904
|
+
config: dict,
|
|
905
|
+
) -> DomainActivationResult:
|
|
906
|
+
"""Convenience function for domain activation."""
|
|
907
|
+
# Get activation threshold from config
|
|
908
|
+
threshold = config.get("security", {}).get("domain_activation_threshold", 0.5)
|
|
909
|
+
max_domains = config.get("security", {}).get("max_active_domains", 20)
|
|
910
|
+
forced = config.get("security", {}).get("forced_domains", [])
|
|
911
|
+
|
|
912
|
+
activator = DomainActivatorV2(root, threshold, max_domains)
|
|
913
|
+
return activator.activate_domains(file_infos, project_context, forced)
|