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,2183 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Patchi Domain Activator
|
|
3
|
+
=======================
|
|
4
|
+
|
|
5
|
+
Determines which security domains should be activated for a given codebase
|
|
6
|
+
based on detected imports, dependencies, file extensions, infra files, and
|
|
7
|
+
other signals. Each domain has a dedicated ``_check_<domain_id>`` function
|
|
8
|
+
that returns ``True`` if the domain should be activated.
|
|
9
|
+
|
|
10
|
+
The functions are registered in ``_DOMAIN_CHECKERS`` and invoked by the
|
|
11
|
+
scanner with an 11-element tuple (see ``_DomainContext`` below).
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
# ---------------------------------------------------------------------------
|
|
17
|
+
# Context passed to every checker
|
|
18
|
+
# ---------------------------------------------------------------------------
|
|
19
|
+
import logging
|
|
20
|
+
import re
|
|
21
|
+
from collections.abc import Callable, Iterable, Sequence
|
|
22
|
+
from dataclasses import dataclass
|
|
23
|
+
from typing import Any
|
|
24
|
+
|
|
25
|
+
_log = logging.getLogger("patchi.brain.domain_activator")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass(frozen=True)
|
|
29
|
+
class _DomainContext:
|
|
30
|
+
"""Structured view of the 11-element args tuple passed by the scanner.
|
|
31
|
+
|
|
32
|
+
Fields (in order):
|
|
33
|
+
language: detected primary language (e.g. "python", "go")
|
|
34
|
+
framework: detected framework name (e.g. "django", "rails")
|
|
35
|
+
imports: list of import strings observed in source files
|
|
36
|
+
deps: list of declared dependencies (gemfile, package.json, etc.)
|
|
37
|
+
routes: list of route strings detected (URL paths, handlers)
|
|
38
|
+
config_keys: list of config keys observed in YAML/ENV/toml files
|
|
39
|
+
infra_files: list of infra-relative file paths (Cargo.toml, routes.rb, etc.)
|
|
40
|
+
has_web: True if a web framework is detected
|
|
41
|
+
has_cli: True if a CLI entrypoint is detected
|
|
42
|
+
has_mobile: True if mobile (iOS/Android) markers are present
|
|
43
|
+
exts: list of file extensions observed (lowercase, with leading dot)
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
language: str
|
|
47
|
+
framework: str
|
|
48
|
+
imports: Sequence[str]
|
|
49
|
+
deps: Sequence[str]
|
|
50
|
+
routes: Sequence[str]
|
|
51
|
+
config_keys: Sequence[str]
|
|
52
|
+
infra_files: Sequence[str]
|
|
53
|
+
has_web: bool
|
|
54
|
+
has_cli: bool
|
|
55
|
+
has_mobile: bool
|
|
56
|
+
exts: Sequence[str]
|
|
57
|
+
|
|
58
|
+
@classmethod
|
|
59
|
+
def from_args(cls, args: Sequence) -> _DomainContext:
|
|
60
|
+
if len(args) != 11:
|
|
61
|
+
raise ValueError(f"Expected 11-element args tuple, got {len(args)}: {args!r}")
|
|
62
|
+
(
|
|
63
|
+
language,
|
|
64
|
+
framework,
|
|
65
|
+
imports,
|
|
66
|
+
deps,
|
|
67
|
+
routes,
|
|
68
|
+
config_keys,
|
|
69
|
+
infra_files,
|
|
70
|
+
has_web,
|
|
71
|
+
has_cli,
|
|
72
|
+
has_mobile,
|
|
73
|
+
exts,
|
|
74
|
+
) = args
|
|
75
|
+
return cls(
|
|
76
|
+
language=language or "",
|
|
77
|
+
framework=framework or "",
|
|
78
|
+
imports=tuple(imports or ()),
|
|
79
|
+
deps=tuple(deps or ()),
|
|
80
|
+
routes=tuple(routes or ()),
|
|
81
|
+
config_keys=tuple(config_keys or ()),
|
|
82
|
+
infra_files=tuple(infra_files or ()),
|
|
83
|
+
has_web=bool(has_web),
|
|
84
|
+
has_cli=bool(has_cli),
|
|
85
|
+
has_mobile=bool(has_mobile),
|
|
86
|
+
exts=tuple(exts or ()),
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
# ---------------------------------------------------------------------------
|
|
91
|
+
# Matching helpers
|
|
92
|
+
# ---------------------------------------------------------------------------
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _compile_pattern(pat: str) -> re.Pattern:
|
|
96
|
+
"""Compile a substring pattern into a regex with word-boundary awareness.
|
|
97
|
+
|
|
98
|
+
For patterns that begin or end with a word character (letter, digit, or
|
|
99
|
+
underscore), we add a negative lookbehind / lookahead so that the pattern
|
|
100
|
+
must NOT be preceded/followed by another word character. This prevents
|
|
101
|
+
false positives like the pattern ``"sync"`` matching the Python import
|
|
102
|
+
``"asyncio"`` (which contains "sync" as a substring) while still matching
|
|
103
|
+
legitimate occurrences like ``"sync.Mutex"``, ``"use sync"``, etc.
|
|
104
|
+
|
|
105
|
+
For patterns that begin or end with a non-word character (e.g. ``"libc::"``,
|
|
106
|
+
``"<stdlib.h>"``, ``"@sveltejs/kit"``), no boundary assertion is added on
|
|
107
|
+
that side because the non-word character itself acts as a sufficient
|
|
108
|
+
delimiter.
|
|
109
|
+
"""
|
|
110
|
+
escaped = re.escape(pat)
|
|
111
|
+
if pat and (pat[0].isalnum() or pat[0] == "_"):
|
|
112
|
+
escaped = r"(?<![A-Za-z0-9_])" + escaped
|
|
113
|
+
if pat and (pat[-1].isalnum() or pat[-1] == "_"):
|
|
114
|
+
escaped = escaped + r"(?![A-Za-z0-9_])"
|
|
115
|
+
return re.compile(escaped, re.IGNORECASE)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _has_import(imports: Iterable[str], patterns: Iterable[str]) -> bool:
|
|
119
|
+
"""Return True if any ``patterns`` substring is found in any ``imports`` entry.
|
|
120
|
+
|
|
121
|
+
Uses word-boundary-aware matching (see :func:`_compile_pattern`) so that
|
|
122
|
+
short identifiers like ``"sync"`` or ``"context"`` do not match inside
|
|
123
|
+
longer identifiers like ``"asyncio"`` or ``"user_context_manager"``.
|
|
124
|
+
"""
|
|
125
|
+
compiled = [_compile_pattern(p) for p in patterns]
|
|
126
|
+
for imp in imports:
|
|
127
|
+
for r in compiled:
|
|
128
|
+
if r.search(imp):
|
|
129
|
+
return True
|
|
130
|
+
return False
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _has_dependency(deps: Iterable[str], patterns: Iterable[str]) -> bool:
|
|
134
|
+
"""Return True if any ``patterns`` substring is found in any ``deps`` entry.
|
|
135
|
+
|
|
136
|
+
Same word-boundary semantics as :func:`_has_import`. This prevents e.g.
|
|
137
|
+
a pattern ``"rails"`` from matching a dep named ``"grails"``.
|
|
138
|
+
"""
|
|
139
|
+
compiled = [_compile_pattern(p) for p in patterns]
|
|
140
|
+
for dep in deps:
|
|
141
|
+
for r in compiled:
|
|
142
|
+
if r.search(dep):
|
|
143
|
+
return True
|
|
144
|
+
return False
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _has_ext(exts: Iterable[str], patterns: Iterable[str]) -> bool:
|
|
148
|
+
"""Return True if any ``patterns`` extension is observed.
|
|
149
|
+
|
|
150
|
+
Patterns are matched as case-insensitive suffix so that ``".go"`` matches
|
|
151
|
+
both ``".go"`` and ``".GO"`` (rare, but legacy Windows tooling produces it).
|
|
152
|
+
"""
|
|
153
|
+
exts_lower = [e.lower() for e in exts]
|
|
154
|
+
return any(ext.lower().endswith(pat.lower()) for pat in patterns for ext in exts_lower)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _has_infra_file(infra_files: Iterable[str], patterns: Iterable[str]) -> bool:
|
|
158
|
+
"""Return True if any infra file path matches any pattern (case-insensitive)."""
|
|
159
|
+
infra_lower = [f.lower() for f in infra_files]
|
|
160
|
+
return any(pat.lower() in f for pat in patterns for f in infra_lower)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _has_config_key(config_keys: Iterable[str], patterns: Iterable[str]) -> bool:
|
|
164
|
+
"""Return True if any config key matches any pattern (case-insensitive)."""
|
|
165
|
+
keys_lower = [k.lower() for k in config_keys]
|
|
166
|
+
return any(pat.lower() in k for pat in patterns for k in keys_lower)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
# ---------------------------------------------------------------------------
|
|
170
|
+
# Domain checkers
|
|
171
|
+
# ---------------------------------------------------------------------------
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _check_native_code_safety(*args) -> bool:
|
|
175
|
+
"""Activate for C/C++/Rust codebases with native-unsafe patterns."""
|
|
176
|
+
ctx = _DomainContext.from_args(args)
|
|
177
|
+
|
|
178
|
+
ncs_exts = [".c", ".cpp", ".cc", ".cxx", ".h", ".hpp", ".hh", ".hxx", ".rs"]
|
|
179
|
+
ncs_imports = [
|
|
180
|
+
"unsafe", # Rust unsafe blocks (lexically detectable)
|
|
181
|
+
"std::ptr", # Rust raw pointer module
|
|
182
|
+
"std::mem::transmute", # Rust transmute
|
|
183
|
+
"libc::", # Rust libc FFI
|
|
184
|
+
'extern "C"', # Rust FFI
|
|
185
|
+
"Box::into_raw", # Rust manual lifetime
|
|
186
|
+
"Box::from_raw",
|
|
187
|
+
"<stdlib.h>", # C/C++ malloc family
|
|
188
|
+
"<string.h>", # C/C++ strcpy family
|
|
189
|
+
"<stdio.h>", # C/C++ printf family
|
|
190
|
+
"strcpy",
|
|
191
|
+
"strcat",
|
|
192
|
+
"sprintf",
|
|
193
|
+
"gets", # CWE-119 classics
|
|
194
|
+
]
|
|
195
|
+
ncs_deps: list[str] = [] # C/C++/Rust do not use dep manifests we scan here
|
|
196
|
+
|
|
197
|
+
return (
|
|
198
|
+
_has_ext(ctx.exts, ncs_exts)
|
|
199
|
+
or _has_import(ctx.imports, ncs_imports)
|
|
200
|
+
or _has_dependency(ctx.deps, ncs_deps)
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _check_go_concurrency(*args) -> bool:
|
|
205
|
+
"""Activate for Go codebases with goroutine / sync usage."""
|
|
206
|
+
ctx = _DomainContext.from_args(args)
|
|
207
|
+
|
|
208
|
+
gco_exts = [".go"]
|
|
209
|
+
gco_imports = [
|
|
210
|
+
"go func", # goroutine spawn (lexical)
|
|
211
|
+
"sync", # sync.Mutex / sync.RWMutex / sync.WaitGroup
|
|
212
|
+
"sync/atomic", # atomic primitives
|
|
213
|
+
"context", # context.Context for cancellation
|
|
214
|
+
"sync.Map", # concurrent map
|
|
215
|
+
"errgroup", # golang.org/x/sync/errgroup
|
|
216
|
+
"runtime.Goexit", # goroutine exit
|
|
217
|
+
]
|
|
218
|
+
gco_deps = [
|
|
219
|
+
"golang.org/x/sync", # errgroup, semaphore
|
|
220
|
+
"github.com/sourcegraph/conc", # modern concurrency helpers
|
|
221
|
+
]
|
|
222
|
+
|
|
223
|
+
return (
|
|
224
|
+
_has_ext(ctx.exts, gco_exts)
|
|
225
|
+
or _has_import(ctx.imports, gco_imports)
|
|
226
|
+
or _has_dependency(ctx.deps, gco_deps)
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def _check_jvm_hardening(*args) -> bool:
|
|
231
|
+
"""Activate for JVM (Java/Kotlin/Scala) codebases, esp. Spring Boot."""
|
|
232
|
+
ctx = _DomainContext.from_args(args)
|
|
233
|
+
|
|
234
|
+
jvm_exts = [".java", ".kt", ".kts", ".scala"]
|
|
235
|
+
jvm_imports = [
|
|
236
|
+
"org.springframework", # Spring Framework
|
|
237
|
+
"org.springframework.boot", # Spring Boot
|
|
238
|
+
"javax.servlet", # Servlet API
|
|
239
|
+
"jakarta.servlet", # Jakarta EE (Spring Boot 3+)
|
|
240
|
+
"java.io.ObjectInputStream", # CWE-502 deserialization
|
|
241
|
+
"java.lang.Runtime", # CWE-78 command exec
|
|
242
|
+
"java.lang.reflect", # CWE-470 reflection
|
|
243
|
+
"com.fasterxml.jackson", # Jackson (default typing risk)
|
|
244
|
+
"com.alibaba.fastjson", # Fastjson (autoType risk)
|
|
245
|
+
"org.apache.commons.collections", # ysoserial gadget
|
|
246
|
+
]
|
|
247
|
+
jvm_deps = [
|
|
248
|
+
"spring-boot-starter",
|
|
249
|
+
"spring-boot-actuator", # CWE-526 actuator exposure
|
|
250
|
+
"spring-boot-devtools", # CWE-489 debug code
|
|
251
|
+
"spring-web",
|
|
252
|
+
"spring-webmvc",
|
|
253
|
+
"spring-webflux",
|
|
254
|
+
"javax.servlet:javax.servlet-api",
|
|
255
|
+
"jakarta.servlet:jakarta.servlet-api",
|
|
256
|
+
"org.apache.commons:commons-collections",
|
|
257
|
+
]
|
|
258
|
+
|
|
259
|
+
jvm_infra = ["pom.xml", "build.gradle", "build.gradle.kts", "settings.gradle"]
|
|
260
|
+
|
|
261
|
+
return (
|
|
262
|
+
_has_ext(ctx.exts, jvm_exts)
|
|
263
|
+
or _has_import(ctx.imports, jvm_imports)
|
|
264
|
+
or _has_dependency(ctx.deps, jvm_deps)
|
|
265
|
+
or _has_infra_file(ctx.infra_files, jvm_infra)
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def _check_mobile_native(*args) -> bool:
|
|
270
|
+
"""Activate for iOS (Swift) / Android (Kotlin/Java) mobile codebases."""
|
|
271
|
+
ctx = _DomainContext.from_args(args)
|
|
272
|
+
|
|
273
|
+
mob_exts = [".swift", ".kt", ".kts", ".m", ".mm"]
|
|
274
|
+
mob_imports = [
|
|
275
|
+
"UIKit", # iOS UI framework
|
|
276
|
+
"SwiftUI", # iOS modern UI
|
|
277
|
+
"Foundation", # iOS core (often co-located)
|
|
278
|
+
"Security", # iOS Keychain
|
|
279
|
+
"androidx.", # AndroidX
|
|
280
|
+
"android.app", # Android app framework
|
|
281
|
+
"android.content", # Android Intent / Context
|
|
282
|
+
"android.security.keystore", # Android Keystore
|
|
283
|
+
"com.android", # Android tooling
|
|
284
|
+
"java.security.KeyStore", # Java/Android Keystore
|
|
285
|
+
]
|
|
286
|
+
mob_deps = [
|
|
287
|
+
"com.android.tools.build:gradle", # Android Gradle plugin
|
|
288
|
+
"io.realm:realm", # Realm mobile DB
|
|
289
|
+
"androidx.compose", # Jetpack Compose
|
|
290
|
+
"io.coil-kt:coil", # Coil (Android image lib)
|
|
291
|
+
"com.google.firebase", # Firebase mobile SDK
|
|
292
|
+
"platform-ui", # iOS package alias
|
|
293
|
+
]
|
|
294
|
+
mob_infra = [
|
|
295
|
+
"info.plist",
|
|
296
|
+
"androidmanifest.xml",
|
|
297
|
+
"project.pbxproj",
|
|
298
|
+
"package.swift",
|
|
299
|
+
"podfile",
|
|
300
|
+
"podfile.lock",
|
|
301
|
+
"build.gradle",
|
|
302
|
+
"build.gradle.kts",
|
|
303
|
+
]
|
|
304
|
+
|
|
305
|
+
return (
|
|
306
|
+
_has_ext(ctx.exts, mob_exts)
|
|
307
|
+
or _has_import(ctx.imports, mob_imports)
|
|
308
|
+
or _has_dependency(ctx.deps, mob_deps)
|
|
309
|
+
or _has_infra_file(ctx.infra_files, mob_infra)
|
|
310
|
+
or ctx.has_mobile
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def _check_ruby_rails(*args) -> bool:
|
|
315
|
+
"""Activate for Ruby on Rails codebases."""
|
|
316
|
+
ctx = _DomainContext.from_args(args)
|
|
317
|
+
|
|
318
|
+
rrs_exts = [".rb", ".erb", ".rhtml", ".rjs", ".rake", ".gemspec"]
|
|
319
|
+
rrs_imports = [
|
|
320
|
+
"ActiveRecord", # ORM
|
|
321
|
+
"ActionController", # controllers
|
|
322
|
+
"ActionView", # views
|
|
323
|
+
"ActionDispatch", # routing
|
|
324
|
+
"Rails", # Rails constant
|
|
325
|
+
"ApplicationController",
|
|
326
|
+
"ApplicationRecord",
|
|
327
|
+
"ActiveModel",
|
|
328
|
+
"ActiveJob",
|
|
329
|
+
"ActiveSupport",
|
|
330
|
+
"protect_from_forgery", # CSRF config
|
|
331
|
+
"params.permit", # strong params
|
|
332
|
+
"attr_accessible", # legacy mass-assignment
|
|
333
|
+
]
|
|
334
|
+
rrs_deps = [
|
|
335
|
+
"rails", # the framework
|
|
336
|
+
"activerecord",
|
|
337
|
+
"actionpack",
|
|
338
|
+
"activesupport",
|
|
339
|
+
"actionview",
|
|
340
|
+
"railties",
|
|
341
|
+
"pg", # Postgres adapter (often Rails)
|
|
342
|
+
"mysql2",
|
|
343
|
+
"puma", # Rails app server
|
|
344
|
+
"devise", # Rails auth
|
|
345
|
+
]
|
|
346
|
+
rrs_infra = [
|
|
347
|
+
"config/routes.rb",
|
|
348
|
+
"config/application.rb",
|
|
349
|
+
"config/database.yml",
|
|
350
|
+
"gemfile",
|
|
351
|
+
"gemfile.lock",
|
|
352
|
+
"config.ru",
|
|
353
|
+
"bin/rails",
|
|
354
|
+
"rakefile",
|
|
355
|
+
]
|
|
356
|
+
|
|
357
|
+
return (
|
|
358
|
+
_has_ext(ctx.exts, rrs_exts)
|
|
359
|
+
or _has_import(ctx.imports, rrs_imports)
|
|
360
|
+
or _has_dependency(ctx.deps, rrs_deps)
|
|
361
|
+
or _has_infra_file(ctx.infra_files, rrs_infra)
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
def _check_svelte_ssr(*args) -> bool:
|
|
366
|
+
"""Activate for SvelteKit SSR codebases."""
|
|
367
|
+
ctx = _DomainContext.from_args(args)
|
|
368
|
+
|
|
369
|
+
ssr_exts = [".svelte", ".svelte.js", ".svelte.ts"]
|
|
370
|
+
ssr_imports = [
|
|
371
|
+
"@sveltejs/kit", # SvelteKit core
|
|
372
|
+
"$app/store", # SvelteKit $app module
|
|
373
|
+
"$app/environment",
|
|
374
|
+
"$app/navigation",
|
|
375
|
+
"$env/static/private", # private env (server-only)
|
|
376
|
+
"$env/dynamic/private",
|
|
377
|
+
"$env/static/public",
|
|
378
|
+
"$env/dynamic/public",
|
|
379
|
+
"$lib/server", # server-only lib
|
|
380
|
+
"import { redirect }", # SvelteKit redirect helper
|
|
381
|
+
"import { error }", # SvelteKit error helper
|
|
382
|
+
"cookies.set", # SvelteKit cookies API (called inside actions/load)
|
|
383
|
+
"cookies.get", # SvelteKit cookies API (read)
|
|
384
|
+
]
|
|
385
|
+
ssr_deps = [
|
|
386
|
+
"@sveltejs/kit",
|
|
387
|
+
"@sveltejs/adapter-node",
|
|
388
|
+
"@sveltejs/adapter-auto",
|
|
389
|
+
"@sveltejs/vite-plugin-svelte",
|
|
390
|
+
"svelte",
|
|
391
|
+
]
|
|
392
|
+
ssr_infra = [
|
|
393
|
+
"svelte.config.js",
|
|
394
|
+
"svelte.config.mjs",
|
|
395
|
+
"src/routes/+page.server.js",
|
|
396
|
+
"src/routes/+page.server.ts",
|
|
397
|
+
"src/routes/+layout.server.js",
|
|
398
|
+
"src/routes/+layout.server.ts",
|
|
399
|
+
"+page.server.js",
|
|
400
|
+
"+page.server.ts",
|
|
401
|
+
"+layout.server.js",
|
|
402
|
+
"+layout.server.ts",
|
|
403
|
+
"src/app.html",
|
|
404
|
+
]
|
|
405
|
+
|
|
406
|
+
return (
|
|
407
|
+
_has_ext(ctx.exts, ssr_exts)
|
|
408
|
+
or _has_import(ctx.imports, ssr_imports)
|
|
409
|
+
or _has_dependency(ctx.deps, ssr_deps)
|
|
410
|
+
or _has_infra_file(ctx.infra_files, ssr_infra)
|
|
411
|
+
)
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
def _check_cargo_supply_chain(*args) -> bool:
|
|
415
|
+
"""Activate for Rust codebases that declare any Cargo dependencies."""
|
|
416
|
+
ctx = _DomainContext.from_args(args)
|
|
417
|
+
|
|
418
|
+
csc_infra = [
|
|
419
|
+
"cargo.toml", # case-insensitive
|
|
420
|
+
"cargo.lock",
|
|
421
|
+
]
|
|
422
|
+
|
|
423
|
+
# Activation requires Cargo.toml present AND a non-empty [dependencies] section.
|
|
424
|
+
# The scanner surfaces Cargo.toml as an infra_file; we trust its presence.
|
|
425
|
+
if not (
|
|
426
|
+
_has_infra_file(ctx.infra_files, csc_infra)
|
|
427
|
+
or _has_ext(ctx.exts, [".rs"])
|
|
428
|
+
or _has_dependency(ctx.deps, ["cargo"]) # rare: cargo as a Cargo plugin
|
|
429
|
+
):
|
|
430
|
+
return False
|
|
431
|
+
|
|
432
|
+
# At least one Rust source file OR Cargo.toml presence is required.
|
|
433
|
+
# The deps list may include crates pulled from Cargo.toml; if non-empty,
|
|
434
|
+
# the project has dependencies to audit.
|
|
435
|
+
if ctx.deps:
|
|
436
|
+
return True
|
|
437
|
+
|
|
438
|
+
# Fallback: just Cargo.toml presence (we cannot verify dep count from here,
|
|
439
|
+
# but the scanner's domain runner will re-check the file's [dependencies] section).
|
|
440
|
+
return _has_infra_file(ctx.infra_files, csc_infra)
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
def _check_nodejs_runtime(*args) -> bool:
|
|
444
|
+
"""Activate for Node.js codebases (JS/TS) using Node runtime APIs."""
|
|
445
|
+
ctx = _DomainContext.from_args(args)
|
|
446
|
+
|
|
447
|
+
njs_exts = [".js", ".mjs", ".cjs", ".ts", ".mts", ".cts"]
|
|
448
|
+
njs_imports = [
|
|
449
|
+
"require(", # CommonJS require
|
|
450
|
+
"child_process", # CWE-78 command exec
|
|
451
|
+
"node:child_process",
|
|
452
|
+
"eval(", # CWE-95 code injection
|
|
453
|
+
"new Function(", # CWE-95 dynamic function
|
|
454
|
+
"vm.runInNewContext", # CWE-95 vm sandbox (not a security boundary)
|
|
455
|
+
"vm.runInThisContext",
|
|
456
|
+
"node:vm",
|
|
457
|
+
"fs.readFile", # CWE-22 path traversal (fs family)
|
|
458
|
+
"fs.writeFile",
|
|
459
|
+
"fs.createReadStream",
|
|
460
|
+
"fs.createWriteStream",
|
|
461
|
+
"path.join", # often misused for path traversal
|
|
462
|
+
"path.resolve",
|
|
463
|
+
"process.env", # env var access (CWE-200 if leaked to client)
|
|
464
|
+
"__proto__", # CWE-1321 prototype pollution
|
|
465
|
+
"Object.assign", # CWE-1321 if source is user input
|
|
466
|
+
"RegExp(", # CWE-1333 ReDoS if user-controlled pattern
|
|
467
|
+
]
|
|
468
|
+
njs_deps = [
|
|
469
|
+
"express", # Express framework (overlaps with express-web)
|
|
470
|
+
"fastify",
|
|
471
|
+
"koa",
|
|
472
|
+
"lodash", # CWE-1321 prototype pollution in old versions
|
|
473
|
+
"jquery", # CWE-1321 $.extend
|
|
474
|
+
"ejs", # CWE-1336 SSTI
|
|
475
|
+
"pug",
|
|
476
|
+
"nunjucks",
|
|
477
|
+
"handlebars",
|
|
478
|
+
"vm2", # deprecated, vulnerable sandbox
|
|
479
|
+
"isolated-vm", # safer sandbox (positive signal)
|
|
480
|
+
]
|
|
481
|
+
njs_infra = [
|
|
482
|
+
"package.json",
|
|
483
|
+
"package-lock.json",
|
|
484
|
+
"yarn.lock",
|
|
485
|
+
"pnpm-lock.yaml",
|
|
486
|
+
"tsconfig.json",
|
|
487
|
+
]
|
|
488
|
+
|
|
489
|
+
return (
|
|
490
|
+
_has_ext(ctx.exts, njs_exts)
|
|
491
|
+
or _has_import(ctx.imports, njs_imports)
|
|
492
|
+
or _has_dependency(ctx.deps, njs_deps)
|
|
493
|
+
or _has_infra_file(ctx.infra_files, njs_infra)
|
|
494
|
+
)
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
def _check_express_web(*args) -> bool:
|
|
498
|
+
"""Activate for Express.js web applications."""
|
|
499
|
+
ctx = _DomainContext.from_args(args)
|
|
500
|
+
|
|
501
|
+
exp_imports = [
|
|
502
|
+
"express", # the framework
|
|
503
|
+
"app.use(", # Express middleware mounting
|
|
504
|
+
"app.get(",
|
|
505
|
+
"app.post(",
|
|
506
|
+
"app.put(",
|
|
507
|
+
"app.patch(",
|
|
508
|
+
"app.delete(",
|
|
509
|
+
"router.get(",
|
|
510
|
+
"router.post(",
|
|
511
|
+
"express.json",
|
|
512
|
+
"express.urlencoded",
|
|
513
|
+
"express.static",
|
|
514
|
+
"res.render", # template rendering (SSTI risk)
|
|
515
|
+
"res.cookie", # cookie setting (insecure flag risk)
|
|
516
|
+
"req.body", # body access (validation risk)
|
|
517
|
+
]
|
|
518
|
+
exp_deps = [
|
|
519
|
+
"express",
|
|
520
|
+
"body-parser",
|
|
521
|
+
"cookie-parser",
|
|
522
|
+
"express-session",
|
|
523
|
+
"cookie-session",
|
|
524
|
+
"csurf", # deprecated but still used
|
|
525
|
+
"csrf-csrf", # modern replacement
|
|
526
|
+
"helmet", # security headers (positive signal)
|
|
527
|
+
"cors", # CORS config (misconfig risk)
|
|
528
|
+
"ejs", # template engines
|
|
529
|
+
"pug",
|
|
530
|
+
"nunjucks",
|
|
531
|
+
"handlebars",
|
|
532
|
+
"multer", # file upload
|
|
533
|
+
]
|
|
534
|
+
exp_infra = [
|
|
535
|
+
"app.js",
|
|
536
|
+
"server.js",
|
|
537
|
+
"index.js",
|
|
538
|
+
"src/app.js",
|
|
539
|
+
"src/server.js",
|
|
540
|
+
]
|
|
541
|
+
|
|
542
|
+
return (
|
|
543
|
+
_has_import(ctx.imports, exp_imports)
|
|
544
|
+
or _has_dependency(ctx.deps, exp_deps)
|
|
545
|
+
or _has_infra_file(ctx.infra_files, exp_infra)
|
|
546
|
+
)
|
|
547
|
+
|
|
548
|
+
|
|
549
|
+
def _check_nextjs_app(*args) -> bool:
|
|
550
|
+
"""Activate for Next.js applications (App Router or Pages Router)."""
|
|
551
|
+
ctx = _DomainContext.from_args(args)
|
|
552
|
+
|
|
553
|
+
nxt_imports = [
|
|
554
|
+
"next/server", # Next.js server utilities
|
|
555
|
+
"next/navigation",
|
|
556
|
+
"next/headers",
|
|
557
|
+
"next/image", # Image optimization (SSRF risk)
|
|
558
|
+
"next/link",
|
|
559
|
+
"next/router",
|
|
560
|
+
"next/document",
|
|
561
|
+
"next/script",
|
|
562
|
+
"NextResponse", # Next.js response class
|
|
563
|
+
"NextRequest",
|
|
564
|
+
"getServerSideProps", # Pages Router SSR (data leak risk)
|
|
565
|
+
"getStaticProps",
|
|
566
|
+
"getInitialProps",
|
|
567
|
+
"use server", # Server Actions (Next.js 14+)
|
|
568
|
+
"searchParams", # App Router searchParams
|
|
569
|
+
]
|
|
570
|
+
nxt_deps = [
|
|
571
|
+
"next",
|
|
572
|
+
"next-auth",
|
|
573
|
+
"@auth/core",
|
|
574
|
+
]
|
|
575
|
+
nxt_infra = [
|
|
576
|
+
"next.config.js",
|
|
577
|
+
"next.config.mjs",
|
|
578
|
+
"next.config.ts",
|
|
579
|
+
"middleware.ts",
|
|
580
|
+
"middleware.js",
|
|
581
|
+
"src/middleware.ts",
|
|
582
|
+
"src/middleware.js",
|
|
583
|
+
"src/app/layout.tsx",
|
|
584
|
+
"src/app/layout.js",
|
|
585
|
+
"src/app/page.tsx",
|
|
586
|
+
"src/app/page.js",
|
|
587
|
+
"src/app/route.ts",
|
|
588
|
+
"src/app/route.js",
|
|
589
|
+
"pages/_app.js",
|
|
590
|
+
"pages/_app.tsx",
|
|
591
|
+
"pages/api/",
|
|
592
|
+
]
|
|
593
|
+
|
|
594
|
+
return (
|
|
595
|
+
_has_import(ctx.imports, nxt_imports)
|
|
596
|
+
or _has_dependency(ctx.deps, nxt_deps)
|
|
597
|
+
or _has_infra_file(ctx.infra_files, nxt_infra)
|
|
598
|
+
)
|
|
599
|
+
|
|
600
|
+
|
|
601
|
+
def _check_python_runtime(*args) -> bool:
|
|
602
|
+
"""Activate for Python codebases using runtime-unsafe APIs."""
|
|
603
|
+
ctx = _DomainContext.from_args(args)
|
|
604
|
+
|
|
605
|
+
pyr_exts = [".py", ".pyw", ".pyi"]
|
|
606
|
+
pyr_imports = [
|
|
607
|
+
"import os", # os.system (CWE-78)
|
|
608
|
+
"import subprocess", # subprocess shell=True (CWE-78)
|
|
609
|
+
"from subprocess",
|
|
610
|
+
"import pickle", # pickle.loads (CWE-502)
|
|
611
|
+
"import cPickle", # Python 2 pickle (CWE-502)
|
|
612
|
+
"import yaml", # yaml.load (CWE-502)
|
|
613
|
+
"import marshal", # marshal.loads (CWE-502)
|
|
614
|
+
"import shelve", # shelve uses pickle (CWE-502)
|
|
615
|
+
"import ctypes", # ctypes abuse (CWE-78)
|
|
616
|
+
"import requests", # SSRF risk
|
|
617
|
+
"import urllib", # SSRF risk
|
|
618
|
+
"from urllib",
|
|
619
|
+
"import httpx", # SSRF risk
|
|
620
|
+
"import aiohttp", # SSRF risk
|
|
621
|
+
"eval(", # CWE-95 code injection
|
|
622
|
+
"exec(", # CWE-95
|
|
623
|
+
"compile(", # CWE-95
|
|
624
|
+
"import re", # ReDoS risk
|
|
625
|
+
"tempfile.mktemp", # CWE-377 race condition
|
|
626
|
+
]
|
|
627
|
+
pyr_deps = [
|
|
628
|
+
"requests",
|
|
629
|
+
"urllib3",
|
|
630
|
+
"httpx",
|
|
631
|
+
"aiohttp",
|
|
632
|
+
"pyyaml", # yaml.load risk
|
|
633
|
+
"pickle", # stdlib but listed for clarity
|
|
634
|
+
"google-re2", # positive signal (ReDoS mitigation)
|
|
635
|
+
]
|
|
636
|
+
pyr_infra = [
|
|
637
|
+
"setup.py",
|
|
638
|
+
"setup.cfg",
|
|
639
|
+
"pyproject.toml",
|
|
640
|
+
"requirements.txt",
|
|
641
|
+
"pipfile",
|
|
642
|
+
"pipfile.lock",
|
|
643
|
+
"poetry.lock",
|
|
644
|
+
"tox.ini",
|
|
645
|
+
]
|
|
646
|
+
|
|
647
|
+
return (
|
|
648
|
+
_has_ext(ctx.exts, pyr_exts)
|
|
649
|
+
or _has_import(ctx.imports, pyr_imports)
|
|
650
|
+
or _has_dependency(ctx.deps, pyr_deps)
|
|
651
|
+
or _has_infra_file(ctx.infra_files, pyr_infra)
|
|
652
|
+
)
|
|
653
|
+
|
|
654
|
+
|
|
655
|
+
def _check_django_hardening(*args) -> bool:
|
|
656
|
+
"""Activate for Django applications."""
|
|
657
|
+
ctx = _DomainContext.from_args(args)
|
|
658
|
+
|
|
659
|
+
djg_imports = [
|
|
660
|
+
"django", # the framework
|
|
661
|
+
"from django",
|
|
662
|
+
"import django",
|
|
663
|
+
"django.http",
|
|
664
|
+
"django.conf",
|
|
665
|
+
"django.shortcuts",
|
|
666
|
+
"django.views",
|
|
667
|
+
"django.middleware",
|
|
668
|
+
"django.contrib",
|
|
669
|
+
"django.db.models",
|
|
670
|
+
"django.template",
|
|
671
|
+
"django.urls",
|
|
672
|
+
"Model.objects.raw", # SQL injection risk
|
|
673
|
+
"cursor.execute",
|
|
674
|
+
"csrf_exempt", # CSRF bypass risk
|
|
675
|
+
"autoescape off", # XSS risk in templates
|
|
676
|
+
]
|
|
677
|
+
djg_deps = [
|
|
678
|
+
"django",
|
|
679
|
+
"Django",
|
|
680
|
+
"django-rest-framework",
|
|
681
|
+
"djangorestframework",
|
|
682
|
+
"django-cors-headers",
|
|
683
|
+
"django-debug-toolbar", # debug code risk
|
|
684
|
+
"django-extensions",
|
|
685
|
+
"celery", # often paired with Django
|
|
686
|
+
"gunicorn", # Django app server
|
|
687
|
+
"whitenoise", # Django static files
|
|
688
|
+
]
|
|
689
|
+
djg_infra = [
|
|
690
|
+
"manage.py",
|
|
691
|
+
"wsgi.py",
|
|
692
|
+
"asgi.py",
|
|
693
|
+
"settings.py",
|
|
694
|
+
"settings/__init__.py",
|
|
695
|
+
"settings/base.py",
|
|
696
|
+
"settings/production.py",
|
|
697
|
+
"urls.py",
|
|
698
|
+
]
|
|
699
|
+
|
|
700
|
+
return (
|
|
701
|
+
_has_import(ctx.imports, djg_imports)
|
|
702
|
+
or _has_dependency(ctx.deps, djg_deps)
|
|
703
|
+
or _has_infra_file(ctx.infra_files, djg_infra)
|
|
704
|
+
)
|
|
705
|
+
|
|
706
|
+
|
|
707
|
+
def _check_flask_hardening(*args) -> bool:
|
|
708
|
+
"""Activate for Flask applications."""
|
|
709
|
+
ctx = _DomainContext.from_args(args)
|
|
710
|
+
|
|
711
|
+
flk_imports = [
|
|
712
|
+
"from flask", # the framework
|
|
713
|
+
"import flask",
|
|
714
|
+
"flask.Flask",
|
|
715
|
+
"flask.request",
|
|
716
|
+
"flask.session",
|
|
717
|
+
"flask.render_template",
|
|
718
|
+
"flask.render_template_string", # SSTI risk
|
|
719
|
+
"flask.redirect",
|
|
720
|
+
"flask.url_for",
|
|
721
|
+
"flask.abort",
|
|
722
|
+
"flask.send_file", # path traversal risk
|
|
723
|
+
"flask.send_from_directory",
|
|
724
|
+
"app.run(", # debug mode risk
|
|
725
|
+
"app.debug",
|
|
726
|
+
"app.config[", # config access (SECRET_KEY risk)
|
|
727
|
+
"cursor.execute", # SQL injection risk (raw SQL)
|
|
728
|
+
"db.session.execute", # SQLAlchemy raw SQL
|
|
729
|
+
]
|
|
730
|
+
flk_deps = [
|
|
731
|
+
"flask",
|
|
732
|
+
"Flask",
|
|
733
|
+
"flask-wtf", # CSRF protection (positive signal)
|
|
734
|
+
"flask-sqlalchemy", # ORM
|
|
735
|
+
"flask-login", # auth
|
|
736
|
+
"flask-session", # server-side sessions
|
|
737
|
+
"flask-jwt-extended",
|
|
738
|
+
"flask-cors",
|
|
739
|
+
"flask-limiter",
|
|
740
|
+
"werkzeug", # Flask's WSGI lib (debugger RCE risk)
|
|
741
|
+
]
|
|
742
|
+
flk_infra = [
|
|
743
|
+
"app.py",
|
|
744
|
+
"wsgi.py",
|
|
745
|
+
"asgi.py",
|
|
746
|
+
"flaskr/__init__.py",
|
|
747
|
+
"requirements.txt",
|
|
748
|
+
]
|
|
749
|
+
|
|
750
|
+
return (
|
|
751
|
+
_has_import(ctx.imports, flk_imports)
|
|
752
|
+
or _has_dependency(ctx.deps, flk_deps)
|
|
753
|
+
or _has_infra_file(ctx.infra_files, flk_infra)
|
|
754
|
+
)
|
|
755
|
+
|
|
756
|
+
|
|
757
|
+
# ---------------------------------------------------------------------------
|
|
758
|
+
# Registry
|
|
759
|
+
# ---------------------------------------------------------------------------
|
|
760
|
+
# New domain checkers (2026-07-12)
|
|
761
|
+
# ---------------------------------------------------------------------------
|
|
762
|
+
|
|
763
|
+
|
|
764
|
+
def _check_cdn_cache_security(*args) -> bool:
|
|
765
|
+
"""Activate for projects using a CDN/edge cache layer."""
|
|
766
|
+
ctx = _DomainContext.from_args(args)
|
|
767
|
+
|
|
768
|
+
cdn_infra = [
|
|
769
|
+
"cloudfront",
|
|
770
|
+
"cloudflare",
|
|
771
|
+
"fastly",
|
|
772
|
+
"akamai",
|
|
773
|
+
"cdn",
|
|
774
|
+
"edge",
|
|
775
|
+
"varnish",
|
|
776
|
+
"cloudflare",
|
|
777
|
+
]
|
|
778
|
+
cdn_deps = ["cloudfront", "fastly", "cloudflare", "boto3"]
|
|
779
|
+
cdn_imports = ["cloudfront", "fastly", "cloudflare", "cdn"]
|
|
780
|
+
|
|
781
|
+
return (
|
|
782
|
+
_has_infra_file(ctx.infra_files, cdn_infra)
|
|
783
|
+
or _has_dependency(ctx.deps, cdn_deps)
|
|
784
|
+
or _has_import(ctx.imports, cdn_imports)
|
|
785
|
+
)
|
|
786
|
+
|
|
787
|
+
|
|
788
|
+
def _check_dns_security(*args) -> bool:
|
|
789
|
+
"""Activate for projects managing DNS zones."""
|
|
790
|
+
ctx = _DomainContext.from_args(args)
|
|
791
|
+
|
|
792
|
+
dns_infra = ["route53", "dns", "zone-file", "named.conf", "cloudflare"]
|
|
793
|
+
dns_deps = ["dnspython", "dns", "ns1", "dyn", "route53", "boto3"]
|
|
794
|
+
dns_imports = ["dns", "route53", "ns1", "dnspython"]
|
|
795
|
+
|
|
796
|
+
return (
|
|
797
|
+
_has_infra_file(ctx.infra_files, dns_infra)
|
|
798
|
+
or _has_dependency(ctx.deps, dns_deps)
|
|
799
|
+
or _has_import(ctx.imports, dns_imports)
|
|
800
|
+
)
|
|
801
|
+
|
|
802
|
+
|
|
803
|
+
def _check_email_authentication(*args) -> bool:
|
|
804
|
+
"""Activate for projects that send email."""
|
|
805
|
+
ctx = _DomainContext.from_args(args)
|
|
806
|
+
|
|
807
|
+
email_deps = [
|
|
808
|
+
"sendgrid",
|
|
809
|
+
"mailgun",
|
|
810
|
+
"ses",
|
|
811
|
+
"sparkpost",
|
|
812
|
+
"postmark",
|
|
813
|
+
"mailchimp",
|
|
814
|
+
"aiosmtplib",
|
|
815
|
+
"django.core.mail",
|
|
816
|
+
]
|
|
817
|
+
email_imports = ["smtplib", "sendgrid", "ses", "mailgun", "email.mime", "aiosmtplib"]
|
|
818
|
+
|
|
819
|
+
return _has_dependency(ctx.deps, email_deps) or _has_import(ctx.imports, email_imports)
|
|
820
|
+
|
|
821
|
+
|
|
822
|
+
def _check_push_notification_security(*args) -> bool:
|
|
823
|
+
"""Activate for projects using push notifications (FCM/APNs)."""
|
|
824
|
+
ctx = _DomainContext.from_args(args)
|
|
825
|
+
|
|
826
|
+
push_deps = ["firebase", "fcm", "apns", "pyfcm", "python-push-notify", "firebase-admin"]
|
|
827
|
+
push_imports = ["firebase_admin", "apns", "fcm", "firebase"]
|
|
828
|
+
|
|
829
|
+
return _has_dependency(ctx.deps, push_deps) or _has_import(ctx.imports, push_imports)
|
|
830
|
+
|
|
831
|
+
|
|
832
|
+
def _check_saml_sso_security(*args) -> bool:
|
|
833
|
+
"""Activate for projects using SAML SSO."""
|
|
834
|
+
ctx = _DomainContext.from_args(args)
|
|
835
|
+
|
|
836
|
+
saml_deps = [
|
|
837
|
+
"pysaml2",
|
|
838
|
+
"onelogin",
|
|
839
|
+
"python3-saml",
|
|
840
|
+
"spring-security-saml2",
|
|
841
|
+
"saml2",
|
|
842
|
+
"leptoplast",
|
|
843
|
+
]
|
|
844
|
+
saml_imports = ["saml", "SAML", "OneLogin", "saml2", "onelogin"]
|
|
845
|
+
|
|
846
|
+
return _has_dependency(ctx.deps, saml_deps) or _has_import(ctx.imports, saml_imports)
|
|
847
|
+
|
|
848
|
+
|
|
849
|
+
def _check_secrets_runtime_management(*args) -> bool:
|
|
850
|
+
"""Activate for projects using runtime secrets backends."""
|
|
851
|
+
ctx = _DomainContext.from_args(args)
|
|
852
|
+
|
|
853
|
+
secrets_deps = [
|
|
854
|
+
"hvac",
|
|
855
|
+
"boto3",
|
|
856
|
+
"google-cloud-secret-manager",
|
|
857
|
+
"azure-keyvault",
|
|
858
|
+
"vault",
|
|
859
|
+
"aws-secretsmanager",
|
|
860
|
+
]
|
|
861
|
+
secrets_imports = ["vault", "secretsmanager", "keyvault", "hvac", "google.cloud.secretmanager"]
|
|
862
|
+
|
|
863
|
+
return _has_dependency(ctx.deps, secrets_deps) or _has_import(ctx.imports, secrets_imports)
|
|
864
|
+
|
|
865
|
+
|
|
866
|
+
def _check_service_mesh_security(*args) -> bool:
|
|
867
|
+
"""Activate for projects deployed on a service mesh (Istio/Linkerd)."""
|
|
868
|
+
ctx = _DomainContext.from_args(args)
|
|
869
|
+
|
|
870
|
+
mesh_infra = [
|
|
871
|
+
"istio",
|
|
872
|
+
"linkerd",
|
|
873
|
+
"virtualservice",
|
|
874
|
+
"destinationrule",
|
|
875
|
+
"peerauthoration",
|
|
876
|
+
"authorizationpolicy",
|
|
877
|
+
]
|
|
878
|
+
mesh_deps = ["istio-client", "linkerd2"]
|
|
879
|
+
mesh_imports = ["istio", "linkerd"]
|
|
880
|
+
|
|
881
|
+
return (
|
|
882
|
+
_has_infra_file(ctx.infra_files, mesh_infra)
|
|
883
|
+
or _has_dependency(ctx.deps, mesh_deps)
|
|
884
|
+
or _has_import(ctx.imports, mesh_imports)
|
|
885
|
+
)
|
|
886
|
+
|
|
887
|
+
|
|
888
|
+
def _check_kubernetes_hardening(*args) -> bool:
|
|
889
|
+
"""Activate for projects deployed on Kubernetes."""
|
|
890
|
+
ctx = _DomainContext.from_args(args)
|
|
891
|
+
|
|
892
|
+
k8s_infra = [
|
|
893
|
+
"deployment.yaml",
|
|
894
|
+
"service.yaml",
|
|
895
|
+
"statefulset.yaml",
|
|
896
|
+
"daemonset.yaml",
|
|
897
|
+
"role.yaml",
|
|
898
|
+
"clusterrole.yaml",
|
|
899
|
+
"networkpolicy.yaml",
|
|
900
|
+
"helmfile.yaml",
|
|
901
|
+
"chart.yaml",
|
|
902
|
+
"values.yaml",
|
|
903
|
+
"kustomization.yaml",
|
|
904
|
+
]
|
|
905
|
+
k8s_imports = ["kubernetes", "kubectl", "kube", "helm"]
|
|
906
|
+
k8s_deps = ["kubernetes", "pykube", "lightkube", "helm"]
|
|
907
|
+
|
|
908
|
+
has_k8s_yaml = any(
|
|
909
|
+
"k8s" in f.lower() or "kubernetes" in f.lower() or "deploy" in f.lower()
|
|
910
|
+
for f in ctx.infra_files
|
|
911
|
+
)
|
|
912
|
+
|
|
913
|
+
return (
|
|
914
|
+
_has_infra_file(ctx.infra_files, k8s_infra)
|
|
915
|
+
or has_k8s_yaml
|
|
916
|
+
or _has_import(ctx.imports, k8s_imports)
|
|
917
|
+
or _has_dependency(ctx.deps, k8s_deps)
|
|
918
|
+
)
|
|
919
|
+
|
|
920
|
+
|
|
921
|
+
# ---------------------------------------------------------------------------
|
|
922
|
+
# Auto-generated checkers for 253 remaining domains (2026-08-06)
|
|
923
|
+
# ---------------------------------------------------------------------------
|
|
924
|
+
_GENERATED_CHECKERS: dict[str, _DomainCheckerFn] = {}
|
|
925
|
+
try:
|
|
926
|
+
import patchi.core.brain._generated_checkers as _gen_mod
|
|
927
|
+
|
|
928
|
+
for _name in dir(_gen_mod):
|
|
929
|
+
if _name.startswith("_check_"):
|
|
930
|
+
_fn = getattr(_gen_mod, _name)
|
|
931
|
+
if callable(_fn):
|
|
932
|
+
# Extract domain slug from function name: _check_foo_bar -> foo-bar
|
|
933
|
+
_slug = _name[7:].replace("_", "-")
|
|
934
|
+
_GENERATED_CHECKERS[_slug] = _fn
|
|
935
|
+
except ImportError:
|
|
936
|
+
pass
|
|
937
|
+
|
|
938
|
+
|
|
939
|
+
# ---------------------------------------------------------------------------
|
|
940
|
+
# New domain checkers — 27 additional technology-stack domains (2026-07-12)
|
|
941
|
+
# ---------------------------------------------------------------------------
|
|
942
|
+
|
|
943
|
+
|
|
944
|
+
def _check_access_control_authz(*args) -> bool:
|
|
945
|
+
"""Activate for projects with role/permission checks, admin routes, or multi-tenant data."""
|
|
946
|
+
ctx = _DomainContext.from_args(args)
|
|
947
|
+
|
|
948
|
+
az_imports = [
|
|
949
|
+
"authorize",
|
|
950
|
+
"permission",
|
|
951
|
+
"has_role",
|
|
952
|
+
"has_permission",
|
|
953
|
+
"is_admin",
|
|
954
|
+
"check_access",
|
|
955
|
+
"rbac",
|
|
956
|
+
"acl",
|
|
957
|
+
"can_access",
|
|
958
|
+
"require_role",
|
|
959
|
+
"login_required",
|
|
960
|
+
"permission_required",
|
|
961
|
+
"@requires_permissions",
|
|
962
|
+
"CurrentPrincipal",
|
|
963
|
+
"SecurityContext",
|
|
964
|
+
"AuthorizationService",
|
|
965
|
+
"policy.enforce",
|
|
966
|
+
"guard.can",
|
|
967
|
+
"authz",
|
|
968
|
+
]
|
|
969
|
+
az_deps = [
|
|
970
|
+
"casbin",
|
|
971
|
+
"pycasbin",
|
|
972
|
+
"rbac",
|
|
973
|
+
"accesscontrol",
|
|
974
|
+
"casbin-rs",
|
|
975
|
+
"spring-security",
|
|
976
|
+
"django-guardian",
|
|
977
|
+
"django-rules",
|
|
978
|
+
"pundit",
|
|
979
|
+
"cancancan",
|
|
980
|
+
"policy_machine",
|
|
981
|
+
]
|
|
982
|
+
|
|
983
|
+
return (
|
|
984
|
+
_has_import(ctx.imports, az_imports)
|
|
985
|
+
or _has_dependency(ctx.deps, az_deps)
|
|
986
|
+
or _has_config_key(ctx.config_keys, ["rbac", "permissions", "roles", "authorization"])
|
|
987
|
+
)
|
|
988
|
+
|
|
989
|
+
|
|
990
|
+
def _check_agent_orchestration(*args) -> bool:
|
|
991
|
+
"""Activate for multi-agent / agentic-loop frameworks (LangGraph, CrewAI, AutoGen)."""
|
|
992
|
+
ctx = _DomainContext.from_args(args)
|
|
993
|
+
|
|
994
|
+
agt_deps = [
|
|
995
|
+
"langgraph",
|
|
996
|
+
"crewai",
|
|
997
|
+
"autogen",
|
|
998
|
+
"openai-agents",
|
|
999
|
+
"swarm",
|
|
1000
|
+
"semantic-kernel",
|
|
1001
|
+
"langchain",
|
|
1002
|
+
"llamaindex",
|
|
1003
|
+
]
|
|
1004
|
+
agt_imports = [
|
|
1005
|
+
"langgraph",
|
|
1006
|
+
"crewai",
|
|
1007
|
+
"autogen",
|
|
1008
|
+
"AgentExecutor",
|
|
1009
|
+
"ToolNode",
|
|
1010
|
+
"create_react_agent",
|
|
1011
|
+
"StateGraph",
|
|
1012
|
+
"AgentGroupChat",
|
|
1013
|
+
"openai.agents",
|
|
1014
|
+
"Swarm",
|
|
1015
|
+
]
|
|
1016
|
+
|
|
1017
|
+
return _has_import(ctx.imports, agt_imports) or _has_dependency(ctx.deps, agt_deps)
|
|
1018
|
+
|
|
1019
|
+
|
|
1020
|
+
def _check_auth_session(*args) -> bool:
|
|
1021
|
+
"""Activate for projects with session management, login, password handling, or JWT."""
|
|
1022
|
+
ctx = _DomainContext.from_args(args)
|
|
1023
|
+
|
|
1024
|
+
as_deps = [
|
|
1025
|
+
"express-session",
|
|
1026
|
+
"cookie-session",
|
|
1027
|
+
"passport",
|
|
1028
|
+
"next-auth",
|
|
1029
|
+
"flask-login",
|
|
1030
|
+
"flask-session",
|
|
1031
|
+
"django.contrib.sessions",
|
|
1032
|
+
"devise",
|
|
1033
|
+
"warden",
|
|
1034
|
+
"spring-security",
|
|
1035
|
+
"jsonwebtoken",
|
|
1036
|
+
"pyjwt",
|
|
1037
|
+
"jose",
|
|
1038
|
+
"ruby-jwt",
|
|
1039
|
+
"jjwt",
|
|
1040
|
+
"bcrypt",
|
|
1041
|
+
"argon2-cffi",
|
|
1042
|
+
"passlib",
|
|
1043
|
+
]
|
|
1044
|
+
as_imports = [
|
|
1045
|
+
"login",
|
|
1046
|
+
"signin",
|
|
1047
|
+
"session.create",
|
|
1048
|
+
"session.destroy",
|
|
1049
|
+
"bcrypt",
|
|
1050
|
+
"argon2",
|
|
1051
|
+
"password_hash",
|
|
1052
|
+
"check_password",
|
|
1053
|
+
"jwt.verify",
|
|
1054
|
+
"jwt.decode",
|
|
1055
|
+
"jwt.sign",
|
|
1056
|
+
"passport.authenticate",
|
|
1057
|
+
"sessions.create",
|
|
1058
|
+
"LoginView",
|
|
1059
|
+
"LoginController",
|
|
1060
|
+
"authenticat",
|
|
1061
|
+
]
|
|
1062
|
+
|
|
1063
|
+
return _has_import(ctx.imports, as_imports) or _has_dependency(ctx.deps, as_deps)
|
|
1064
|
+
|
|
1065
|
+
|
|
1066
|
+
def _check_cicd_pipeline(*args) -> bool:
|
|
1067
|
+
"""Activate for repos with CI/CD configuration files."""
|
|
1068
|
+
ctx = _DomainContext.from_args(args)
|
|
1069
|
+
|
|
1070
|
+
ci_infra = [
|
|
1071
|
+
".github/workflows",
|
|
1072
|
+
".gitlab-ci.yml",
|
|
1073
|
+
"jenkinsfile",
|
|
1074
|
+
".circleci/config.yml",
|
|
1075
|
+
"azure-pipelines.yml",
|
|
1076
|
+
"bitbucket-pipelines.yml",
|
|
1077
|
+
".travis.yml",
|
|
1078
|
+
"buildkite.yml",
|
|
1079
|
+
"cloudbuild.yaml",
|
|
1080
|
+
".drone.yml",
|
|
1081
|
+
"taskcluster.yml",
|
|
1082
|
+
]
|
|
1083
|
+
|
|
1084
|
+
return _has_infra_file(ctx.infra_files, ci_infra)
|
|
1085
|
+
|
|
1086
|
+
|
|
1087
|
+
def _check_configuration_hardening(*args) -> bool:
|
|
1088
|
+
"""Activate for projects with deployable server config or secrets usage."""
|
|
1089
|
+
ctx = _DomainContext.from_args(args)
|
|
1090
|
+
|
|
1091
|
+
ch_infra = [
|
|
1092
|
+
"nginx.conf",
|
|
1093
|
+
"apache2.conf",
|
|
1094
|
+
"httpd.conf",
|
|
1095
|
+
".env",
|
|
1096
|
+
".env.production",
|
|
1097
|
+
".env.local",
|
|
1098
|
+
"config.yaml",
|
|
1099
|
+
"config.yml",
|
|
1100
|
+
"application.yml",
|
|
1101
|
+
"application.properties",
|
|
1102
|
+
]
|
|
1103
|
+
ch_imports = [
|
|
1104
|
+
"os.environ",
|
|
1105
|
+
"process.env",
|
|
1106
|
+
"dotenv",
|
|
1107
|
+
"load_dotenv",
|
|
1108
|
+
"config.get",
|
|
1109
|
+
"configparser",
|
|
1110
|
+
"yaml.safe_load",
|
|
1111
|
+
"getenv",
|
|
1112
|
+
"environ",
|
|
1113
|
+
]
|
|
1114
|
+
|
|
1115
|
+
return (
|
|
1116
|
+
_has_infra_file(ctx.infra_files, ch_infra)
|
|
1117
|
+
or _has_import(ctx.imports, ch_imports)
|
|
1118
|
+
or _has_config_key(
|
|
1119
|
+
ctx.config_keys, ["secret", "password", "api_key", "token", "credentials"]
|
|
1120
|
+
)
|
|
1121
|
+
)
|
|
1122
|
+
|
|
1123
|
+
|
|
1124
|
+
def _check_container_infra(*args) -> bool:
|
|
1125
|
+
"""Activate for projects with Dockerfiles, docker-compose, or K8s manifests."""
|
|
1126
|
+
ctx = _DomainContext.from_args(args)
|
|
1127
|
+
|
|
1128
|
+
ci_infra = [
|
|
1129
|
+
"dockerfile",
|
|
1130
|
+
"docker-compose.yml",
|
|
1131
|
+
"docker-compose.yaml",
|
|
1132
|
+
"compose.yaml",
|
|
1133
|
+
"compose.yml",
|
|
1134
|
+
".dockerignore",
|
|
1135
|
+
"deployment.yaml",
|
|
1136
|
+
"service.yaml",
|
|
1137
|
+
"statefulset.yaml",
|
|
1138
|
+
"daemonset.yaml",
|
|
1139
|
+
"chart.yaml",
|
|
1140
|
+
"values.yaml",
|
|
1141
|
+
"kustomization.yaml",
|
|
1142
|
+
"helmfile.yaml",
|
|
1143
|
+
]
|
|
1144
|
+
|
|
1145
|
+
return _has_infra_file(ctx.infra_files, ci_infra) or _has_import(
|
|
1146
|
+
ctx.imports, ["docker", "kubernetes", "kubectl", "helm"]
|
|
1147
|
+
)
|
|
1148
|
+
|
|
1149
|
+
|
|
1150
|
+
def _check_data_layer(*args) -> bool:
|
|
1151
|
+
"""Activate for projects using databases (drivers, ORMs, migrations)."""
|
|
1152
|
+
ctx = _DomainContext.from_args(args)
|
|
1153
|
+
|
|
1154
|
+
dl_deps = [
|
|
1155
|
+
"sqlalchemy",
|
|
1156
|
+
"psycopg2",
|
|
1157
|
+
"asyncpg",
|
|
1158
|
+
"mysqlclient",
|
|
1159
|
+
"pymysql",
|
|
1160
|
+
"pymongo",
|
|
1161
|
+
"motor",
|
|
1162
|
+
"django",
|
|
1163
|
+
"prisma",
|
|
1164
|
+
"typeorm",
|
|
1165
|
+
"sequelize",
|
|
1166
|
+
"knex",
|
|
1167
|
+
"sequelize",
|
|
1168
|
+
"pg",
|
|
1169
|
+
"mysql2",
|
|
1170
|
+
"better-sqlite3",
|
|
1171
|
+
"sqlite3",
|
|
1172
|
+
"alembic",
|
|
1173
|
+
"django.db",
|
|
1174
|
+
"drizzle-orm",
|
|
1175
|
+
]
|
|
1176
|
+
dl_imports = [
|
|
1177
|
+
"sqlalchemy",
|
|
1178
|
+
"psycopg2",
|
|
1179
|
+
"asyncpg",
|
|
1180
|
+
"pymysql",
|
|
1181
|
+
"pymongo",
|
|
1182
|
+
"mongoose",
|
|
1183
|
+
"prisma",
|
|
1184
|
+
"typeorm",
|
|
1185
|
+
"sequelize",
|
|
1186
|
+
"knex",
|
|
1187
|
+
"cursor.execute",
|
|
1188
|
+
"db.session",
|
|
1189
|
+
"connection.execute",
|
|
1190
|
+
"create_engine",
|
|
1191
|
+
"sessionmaker",
|
|
1192
|
+
"Base.metadata",
|
|
1193
|
+
"migration",
|
|
1194
|
+
"alembic",
|
|
1195
|
+
"db:migrate",
|
|
1196
|
+
]
|
|
1197
|
+
|
|
1198
|
+
return _has_import(ctx.imports, dl_imports) or _has_dependency(ctx.deps, dl_deps)
|
|
1199
|
+
|
|
1200
|
+
|
|
1201
|
+
def _check_data_protection_privacy(*args) -> bool:
|
|
1202
|
+
"""Activate for projects handling PII, client-side storage, or caching layers."""
|
|
1203
|
+
ctx = _DomainContext.from_args(args)
|
|
1204
|
+
|
|
1205
|
+
dp_deps = [
|
|
1206
|
+
"cookie",
|
|
1207
|
+
"redis",
|
|
1208
|
+
"memcached",
|
|
1209
|
+
"localforage",
|
|
1210
|
+
"client-session",
|
|
1211
|
+
"express-session",
|
|
1212
|
+
]
|
|
1213
|
+
dp_imports = [
|
|
1214
|
+
"localStorage",
|
|
1215
|
+
"sessionStorage",
|
|
1216
|
+
"indexedDB",
|
|
1217
|
+
"cookies.set",
|
|
1218
|
+
"cookies.get",
|
|
1219
|
+
"setCookie",
|
|
1220
|
+
"cache",
|
|
1221
|
+
"Cache-Control",
|
|
1222
|
+
"Vary",
|
|
1223
|
+
"pii",
|
|
1224
|
+
"personal_data",
|
|
1225
|
+
"gdpr",
|
|
1226
|
+
"ccpa",
|
|
1227
|
+
]
|
|
1228
|
+
|
|
1229
|
+
return (
|
|
1230
|
+
_has_import(ctx.imports, dp_imports)
|
|
1231
|
+
or _has_dependency(ctx.deps, dp_deps)
|
|
1232
|
+
or _has_config_key(ctx.config_keys, ["privacy", "pii", "gdpr", "ccpa", "data_protection"])
|
|
1233
|
+
)
|
|
1234
|
+
|
|
1235
|
+
|
|
1236
|
+
def _check_desktop_app(*args) -> bool:
|
|
1237
|
+
"""Activate for Electron desktop applications."""
|
|
1238
|
+
ctx = _DomainContext.from_args(args)
|
|
1239
|
+
|
|
1240
|
+
da_deps = ["electron", "electron-builder", "electron-forge", "electron-packager"]
|
|
1241
|
+
da_imports = [
|
|
1242
|
+
"BrowserWindow",
|
|
1243
|
+
"electron",
|
|
1244
|
+
"ipcMain",
|
|
1245
|
+
"ipcRenderer",
|
|
1246
|
+
"contextBridge",
|
|
1247
|
+
"webContents",
|
|
1248
|
+
"app.getPath",
|
|
1249
|
+
"shell.openExternal",
|
|
1250
|
+
]
|
|
1251
|
+
da_infra = ["electron-builder.yml", "electron-builder.yaml", "forge.config.js"]
|
|
1252
|
+
|
|
1253
|
+
return (
|
|
1254
|
+
_has_import(ctx.imports, da_imports)
|
|
1255
|
+
or _has_dependency(ctx.deps, da_deps)
|
|
1256
|
+
or _has_infra_file(ctx.infra_files, da_infra)
|
|
1257
|
+
)
|
|
1258
|
+
|
|
1259
|
+
|
|
1260
|
+
def _check_file_handling(*args) -> bool:
|
|
1261
|
+
"""Activate for projects with file upload/download or archive extraction."""
|
|
1262
|
+
ctx = _DomainContext.from_args(args)
|
|
1263
|
+
|
|
1264
|
+
fh_imports = [
|
|
1265
|
+
"multer",
|
|
1266
|
+
"busboy",
|
|
1267
|
+
"formidable",
|
|
1268
|
+
"multipart",
|
|
1269
|
+
"zip",
|
|
1270
|
+
"tar",
|
|
1271
|
+
"gunzip",
|
|
1272
|
+
"zlib",
|
|
1273
|
+
"adm-zip",
|
|
1274
|
+
"unzipper",
|
|
1275
|
+
"send_file",
|
|
1276
|
+
"sendFile",
|
|
1277
|
+
"Content-Disposition",
|
|
1278
|
+
"open(",
|
|
1279
|
+
"write(",
|
|
1280
|
+
"os.path.join",
|
|
1281
|
+
"path.join",
|
|
1282
|
+
]
|
|
1283
|
+
fh_deps = [
|
|
1284
|
+
"multer",
|
|
1285
|
+
"busboy",
|
|
1286
|
+
"formidable",
|
|
1287
|
+
"archiver",
|
|
1288
|
+
"adm-zip",
|
|
1289
|
+
"node-tar",
|
|
1290
|
+
"unzipper",
|
|
1291
|
+
"yauzl",
|
|
1292
|
+
"yazl",
|
|
1293
|
+
]
|
|
1294
|
+
|
|
1295
|
+
return _has_import(ctx.imports, fh_imports) or _has_dependency(ctx.deps, fh_deps)
|
|
1296
|
+
|
|
1297
|
+
|
|
1298
|
+
def _check_general_cryptography(*args) -> bool:
|
|
1299
|
+
"""Activate for projects using crypto libraries or encryption."""
|
|
1300
|
+
ctx = _DomainContext.from_args(args)
|
|
1301
|
+
|
|
1302
|
+
gc_imports = [
|
|
1303
|
+
"crypto",
|
|
1304
|
+
"hashlib",
|
|
1305
|
+
"hmac",
|
|
1306
|
+
"cryptography",
|
|
1307
|
+
"openssl",
|
|
1308
|
+
"libsodium",
|
|
1309
|
+
"bcrypt",
|
|
1310
|
+
"argon2",
|
|
1311
|
+
"scrypt",
|
|
1312
|
+
"pbkdf2",
|
|
1313
|
+
"AES",
|
|
1314
|
+
"RSA",
|
|
1315
|
+
"ECDSA",
|
|
1316
|
+
"HMAC",
|
|
1317
|
+
"from cryptography",
|
|
1318
|
+
"import hashlib",
|
|
1319
|
+
"subtle.encrypt",
|
|
1320
|
+
"subtle.decrypt",
|
|
1321
|
+
"subtle.sign",
|
|
1322
|
+
]
|
|
1323
|
+
gc_deps = [
|
|
1324
|
+
"cryptography",
|
|
1325
|
+
"pycryptodome",
|
|
1326
|
+
"libsodium",
|
|
1327
|
+
"bcrypt",
|
|
1328
|
+
"argon2-cffi",
|
|
1329
|
+
"node-forge",
|
|
1330
|
+
"sjcl",
|
|
1331
|
+
"noble",
|
|
1332
|
+
"@noble/hashes",
|
|
1333
|
+
]
|
|
1334
|
+
|
|
1335
|
+
return _has_import(ctx.imports, gc_imports) or _has_dependency(ctx.deps, gc_deps)
|
|
1336
|
+
|
|
1337
|
+
|
|
1338
|
+
def _check_github_app_bot(*args) -> bool:
|
|
1339
|
+
"""Activate for repos operating GitHub Apps or bots with automated PR/issue actions."""
|
|
1340
|
+
ctx = _DomainContext.from_args(args)
|
|
1341
|
+
|
|
1342
|
+
gh_deps = [
|
|
1343
|
+
"@octokit/auth-app",
|
|
1344
|
+
"@octokit/rest",
|
|
1345
|
+
"pygithub",
|
|
1346
|
+
"probot",
|
|
1347
|
+
"github-app",
|
|
1348
|
+
]
|
|
1349
|
+
gh_imports = [
|
|
1350
|
+
"octokit",
|
|
1351
|
+
"App",
|
|
1352
|
+
"createAppAuth",
|
|
1353
|
+
"getInstallationAccessToken",
|
|
1354
|
+
"pull_request_target",
|
|
1355
|
+
"workflow_run",
|
|
1356
|
+
]
|
|
1357
|
+
gh_infra = [".github/workflows"]
|
|
1358
|
+
|
|
1359
|
+
return (
|
|
1360
|
+
_has_import(ctx.imports, gh_imports)
|
|
1361
|
+
or _has_dependency(ctx.deps, gh_deps)
|
|
1362
|
+
or _has_infra_file(ctx.infra_files, gh_infra)
|
|
1363
|
+
)
|
|
1364
|
+
|
|
1365
|
+
|
|
1366
|
+
def _check_graphql_api_security(*args) -> bool:
|
|
1367
|
+
"""Activate for projects exposing GraphQL endpoints."""
|
|
1368
|
+
ctx = _DomainContext.from_args(args)
|
|
1369
|
+
|
|
1370
|
+
gql_deps = [
|
|
1371
|
+
"graphql",
|
|
1372
|
+
"apollo-server",
|
|
1373
|
+
"@apollo/server",
|
|
1374
|
+
"graphene",
|
|
1375
|
+
"strawberry-graphql",
|
|
1376
|
+
"ariadne",
|
|
1377
|
+
"hasura",
|
|
1378
|
+
"type-graphql",
|
|
1379
|
+
]
|
|
1380
|
+
gql_imports = [
|
|
1381
|
+
"GraphQLSchema",
|
|
1382
|
+
"graphql",
|
|
1383
|
+
"apollo-server",
|
|
1384
|
+
"gql",
|
|
1385
|
+
"strawberry",
|
|
1386
|
+
"ariadne",
|
|
1387
|
+
"buildSchema",
|
|
1388
|
+
"makeExecutableSchema",
|
|
1389
|
+
]
|
|
1390
|
+
gql_infra = [".graphql", ".gql", "schema.graphql", "schema.gql"]
|
|
1391
|
+
|
|
1392
|
+
return (
|
|
1393
|
+
_has_import(ctx.imports, gql_imports)
|
|
1394
|
+
or _has_dependency(ctx.deps, gql_deps)
|
|
1395
|
+
or _has_infra_file(ctx.infra_files, gql_infra)
|
|
1396
|
+
)
|
|
1397
|
+
|
|
1398
|
+
|
|
1399
|
+
def _check_input_validation_business_logic(*args) -> bool:
|
|
1400
|
+
"""Activate for projects with multi-step business flows or validation logic."""
|
|
1401
|
+
ctx = _DomainContext.from_args(args)
|
|
1402
|
+
|
|
1403
|
+
iv_deps = [
|
|
1404
|
+
"joi",
|
|
1405
|
+
"yup",
|
|
1406
|
+
"zod",
|
|
1407
|
+
"ajv",
|
|
1408
|
+
"class-validator",
|
|
1409
|
+
"marshmallow",
|
|
1410
|
+
"pydantic",
|
|
1411
|
+
"cerberus",
|
|
1412
|
+
"jsonschema",
|
|
1413
|
+
"express-validator",
|
|
1414
|
+
"drf-validation",
|
|
1415
|
+
"django.forms",
|
|
1416
|
+
]
|
|
1417
|
+
iv_imports = [
|
|
1418
|
+
"validate",
|
|
1419
|
+
"sanitize",
|
|
1420
|
+
"whitelist",
|
|
1421
|
+
"blacklist",
|
|
1422
|
+
"InputValidator",
|
|
1423
|
+
"RequestValidator",
|
|
1424
|
+
"Schema.validate",
|
|
1425
|
+
"checkConstraint",
|
|
1426
|
+
"assertValid",
|
|
1427
|
+
]
|
|
1428
|
+
|
|
1429
|
+
return _has_import(ctx.imports, iv_imports) or _has_dependency(ctx.deps, iv_deps)
|
|
1430
|
+
|
|
1431
|
+
|
|
1432
|
+
def _check_llm_integration(*args) -> bool:
|
|
1433
|
+
"""Activate for projects using LLM SDKs, vector DBs, or prompt templates."""
|
|
1434
|
+
ctx = _DomainContext.from_args(args)
|
|
1435
|
+
|
|
1436
|
+
llm_deps = [
|
|
1437
|
+
"openai",
|
|
1438
|
+
"anthropic",
|
|
1439
|
+
"@anthropic-ai/sdk",
|
|
1440
|
+
"langchain",
|
|
1441
|
+
"llamaindex",
|
|
1442
|
+
"google-generativeai",
|
|
1443
|
+
"cohere-ai",
|
|
1444
|
+
"transformers",
|
|
1445
|
+
"pinecone-client",
|
|
1446
|
+
"chromadb",
|
|
1447
|
+
"weaviate-client",
|
|
1448
|
+
"qdrant-client",
|
|
1449
|
+
"pgvector",
|
|
1450
|
+
"faiss",
|
|
1451
|
+
]
|
|
1452
|
+
llm_imports = [
|
|
1453
|
+
"openai",
|
|
1454
|
+
"anthropic",
|
|
1455
|
+
"ChatOpenAI",
|
|
1456
|
+
"OpenAIEmbeddings",
|
|
1457
|
+
"PineconeVectorStore",
|
|
1458
|
+
"Chroma",
|
|
1459
|
+
"FAISS",
|
|
1460
|
+
"generateText",
|
|
1461
|
+
"generateContent",
|
|
1462
|
+
"messages.create",
|
|
1463
|
+
]
|
|
1464
|
+
|
|
1465
|
+
return _has_import(ctx.imports, llm_imports) or _has_dependency(ctx.deps, llm_deps)
|
|
1466
|
+
|
|
1467
|
+
|
|
1468
|
+
def _check_logging_error_handling(*args) -> bool:
|
|
1469
|
+
"""Activate for projects with logging libraries and error handling middleware."""
|
|
1470
|
+
ctx = _DomainContext.from_args(args)
|
|
1471
|
+
|
|
1472
|
+
le_deps = [
|
|
1473
|
+
"winston",
|
|
1474
|
+
"pino",
|
|
1475
|
+
"bunyan",
|
|
1476
|
+
"log4j",
|
|
1477
|
+
"logback",
|
|
1478
|
+
"slog",
|
|
1479
|
+
"zap",
|
|
1480
|
+
"logrus",
|
|
1481
|
+
"structlog",
|
|
1482
|
+
"loguru",
|
|
1483
|
+
]
|
|
1484
|
+
le_imports = [
|
|
1485
|
+
"logging",
|
|
1486
|
+
"logger",
|
|
1487
|
+
"log.error",
|
|
1488
|
+
"log.warn",
|
|
1489
|
+
"log.info",
|
|
1490
|
+
"winston.createLogger",
|
|
1491
|
+
"pino",
|
|
1492
|
+
"console.error",
|
|
1493
|
+
"errorHandler",
|
|
1494
|
+
"ErrorMiddleware",
|
|
1495
|
+
"onerror",
|
|
1496
|
+
"try:",
|
|
1497
|
+
"catch",
|
|
1498
|
+
"except",
|
|
1499
|
+
"rescue",
|
|
1500
|
+
]
|
|
1501
|
+
|
|
1502
|
+
return _has_import(ctx.imports, le_imports) or _has_dependency(ctx.deps, le_deps)
|
|
1503
|
+
|
|
1504
|
+
|
|
1505
|
+
def _check_mcp_tool_surface(*args) -> bool:
|
|
1506
|
+
"""Activate for projects implementing an MCP server (Model Context Protocol)."""
|
|
1507
|
+
ctx = _DomainContext.from_args(args)
|
|
1508
|
+
|
|
1509
|
+
mcp_deps = ["mcp", "@modelcontextprotocol/sdk"]
|
|
1510
|
+
mcp_imports = [
|
|
1511
|
+
"mcp.server",
|
|
1512
|
+
"MCPServer",
|
|
1513
|
+
"tools/list",
|
|
1514
|
+
"tools/call",
|
|
1515
|
+
"resources/list",
|
|
1516
|
+
"resources/read",
|
|
1517
|
+
"prompts/list",
|
|
1518
|
+
"ListToolsRequest",
|
|
1519
|
+
"CallToolRequest",
|
|
1520
|
+
"ServerSession",
|
|
1521
|
+
]
|
|
1522
|
+
|
|
1523
|
+
return _has_import(ctx.imports, mcp_imports) or _has_dependency(ctx.deps, mcp_deps)
|
|
1524
|
+
|
|
1525
|
+
|
|
1526
|
+
def _check_message_queue_event_driven(*args) -> bool:
|
|
1527
|
+
"""Activate for projects using message brokers (Kafka, RabbitMQ, SQS, Pub/Sub)."""
|
|
1528
|
+
ctx = _DomainContext.from_args(args)
|
|
1529
|
+
|
|
1530
|
+
mq_deps = [
|
|
1531
|
+
"kafkajs",
|
|
1532
|
+
"kafka-node",
|
|
1533
|
+
"kafka-python",
|
|
1534
|
+
"confluent-kafka",
|
|
1535
|
+
"amqplib",
|
|
1536
|
+
"pika",
|
|
1537
|
+
"rabbitmq",
|
|
1538
|
+
"celery",
|
|
1539
|
+
"boto3",
|
|
1540
|
+
"aws-sdk",
|
|
1541
|
+
"sqs",
|
|
1542
|
+
"sns",
|
|
1543
|
+
"google-cloud-pubsub",
|
|
1544
|
+
"nats",
|
|
1545
|
+
"bull",
|
|
1546
|
+
"bullmq",
|
|
1547
|
+
"redis",
|
|
1548
|
+
]
|
|
1549
|
+
mq_imports = [
|
|
1550
|
+
"kafka",
|
|
1551
|
+
"KafkaJS",
|
|
1552
|
+
"Consumer",
|
|
1553
|
+
"Producer",
|
|
1554
|
+
"Broker",
|
|
1555
|
+
"pika",
|
|
1556
|
+
"amqplib",
|
|
1557
|
+
"celery",
|
|
1558
|
+
"SQS",
|
|
1559
|
+
"sqs",
|
|
1560
|
+
"SNS",
|
|
1561
|
+
"sns",
|
|
1562
|
+
"PubSub",
|
|
1563
|
+
"pubsub",
|
|
1564
|
+
"Subscriber",
|
|
1565
|
+
"nats.connect",
|
|
1566
|
+
"Bull",
|
|
1567
|
+
]
|
|
1568
|
+
|
|
1569
|
+
return _has_import(ctx.imports, mq_imports) or _has_dependency(ctx.deps, mq_deps)
|
|
1570
|
+
|
|
1571
|
+
|
|
1572
|
+
def _check_mobile(*args) -> bool:
|
|
1573
|
+
"""Activate for Android/iOS or cross-platform mobile (React Native/Flutter/Xamarin)."""
|
|
1574
|
+
ctx = _DomainContext.from_args(args)
|
|
1575
|
+
|
|
1576
|
+
mob_deps = [
|
|
1577
|
+
"react-native",
|
|
1578
|
+
"flutter",
|
|
1579
|
+
"xamarin",
|
|
1580
|
+
"ionic",
|
|
1581
|
+
"capacitor",
|
|
1582
|
+
"cordova",
|
|
1583
|
+
"expo",
|
|
1584
|
+
"@ionic/core",
|
|
1585
|
+
]
|
|
1586
|
+
mob_imports = [
|
|
1587
|
+
"ReactNative",
|
|
1588
|
+
"react-native",
|
|
1589
|
+
"flutter",
|
|
1590
|
+
"dart:ui",
|
|
1591
|
+
"Xamarin.Forms",
|
|
1592
|
+
"Capacitor",
|
|
1593
|
+
"Ionic",
|
|
1594
|
+
]
|
|
1595
|
+
mob_infra = [
|
|
1596
|
+
"androidmanifest.xml",
|
|
1597
|
+
"info.plist",
|
|
1598
|
+
"pubspec.yaml",
|
|
1599
|
+
"config.xml",
|
|
1600
|
+
"build.gradle",
|
|
1601
|
+
"app.json",
|
|
1602
|
+
"android/app",
|
|
1603
|
+
"ios/app",
|
|
1604
|
+
]
|
|
1605
|
+
|
|
1606
|
+
return (
|
|
1607
|
+
_has_import(ctx.imports, mob_imports)
|
|
1608
|
+
or _has_dependency(ctx.deps, mob_deps)
|
|
1609
|
+
or _has_infra_file(ctx.infra_files, mob_infra)
|
|
1610
|
+
or ctx.has_mobile
|
|
1611
|
+
)
|
|
1612
|
+
|
|
1613
|
+
|
|
1614
|
+
def _check_oauth_oidc(*args) -> bool:
|
|
1615
|
+
"""Activate for projects using OAuth 2.0 or OpenID Connect."""
|
|
1616
|
+
ctx = _DomainContext.from_args(args)
|
|
1617
|
+
|
|
1618
|
+
oa_deps = [
|
|
1619
|
+
"passport",
|
|
1620
|
+
"passport-oauth2",
|
|
1621
|
+
"next-auth",
|
|
1622
|
+
"authlib",
|
|
1623
|
+
"django-allauth",
|
|
1624
|
+
"social-auth-app",
|
|
1625
|
+
"omniauth",
|
|
1626
|
+
"spring-security-oauth2",
|
|
1627
|
+
"oidc-client",
|
|
1628
|
+
"oidc",
|
|
1629
|
+
]
|
|
1630
|
+
oa_imports = [
|
|
1631
|
+
"oauth",
|
|
1632
|
+
"OAuth",
|
|
1633
|
+
"OIDC",
|
|
1634
|
+
"openid",
|
|
1635
|
+
"OpenID",
|
|
1636
|
+
"passport.authenticate",
|
|
1637
|
+
"token.exchange",
|
|
1638
|
+
"authorization_code",
|
|
1639
|
+
"client_credentials",
|
|
1640
|
+
"refresh_token",
|
|
1641
|
+
"access_token",
|
|
1642
|
+
"googleapis/auth",
|
|
1643
|
+
"auth0",
|
|
1644
|
+
]
|
|
1645
|
+
|
|
1646
|
+
return _has_import(ctx.imports, oa_imports) or _has_dependency(ctx.deps, oa_deps)
|
|
1647
|
+
|
|
1648
|
+
|
|
1649
|
+
def _check_secure_coding_architecture(*args) -> bool:
|
|
1650
|
+
"""Activate for projects with multi-threaded/async code and third-party deps."""
|
|
1651
|
+
ctx = _DomainContext.from_args(args)
|
|
1652
|
+
|
|
1653
|
+
sc_imports = [
|
|
1654
|
+
"threading",
|
|
1655
|
+
"multiprocessing",
|
|
1656
|
+
"concurrent.futures",
|
|
1657
|
+
"asyncio",
|
|
1658
|
+
"ThreadPoolExecutor",
|
|
1659
|
+
"ProcessPoolExecutor",
|
|
1660
|
+
"WorkerPool",
|
|
1661
|
+
"TaskGroup",
|
|
1662
|
+
"spawn",
|
|
1663
|
+
]
|
|
1664
|
+
sc_deps = [
|
|
1665
|
+
"celery",
|
|
1666
|
+
"rq",
|
|
1667
|
+
"huey",
|
|
1668
|
+
"dramatiq",
|
|
1669
|
+
"gunicorn",
|
|
1670
|
+
"uvicorn",
|
|
1671
|
+
"hypercorn",
|
|
1672
|
+
]
|
|
1673
|
+
|
|
1674
|
+
return (
|
|
1675
|
+
_has_import(ctx.imports, sc_imports)
|
|
1676
|
+
or _has_dependency(ctx.deps, sc_deps)
|
|
1677
|
+
or len(list(ctx.deps)) > 5
|
|
1678
|
+
)
|
|
1679
|
+
|
|
1680
|
+
|
|
1681
|
+
def _check_secure_communication_tls(*args) -> bool:
|
|
1682
|
+
"""Activate for projects with HTTPS endpoints, mTLS, or TLS client connections."""
|
|
1683
|
+
ctx = _DomainContext.from_args(args)
|
|
1684
|
+
|
|
1685
|
+
tls_imports = [
|
|
1686
|
+
"ssl",
|
|
1687
|
+
"tls",
|
|
1688
|
+
"https",
|
|
1689
|
+
"mTLS",
|
|
1690
|
+
"certificate",
|
|
1691
|
+
"SSLContext",
|
|
1692
|
+
"create_default_context",
|
|
1693
|
+
"CERT_REQUIRED",
|
|
1694
|
+
"verify=True",
|
|
1695
|
+
"verify_ssl",
|
|
1696
|
+
]
|
|
1697
|
+
tls_deps = [
|
|
1698
|
+
"pyopenssl",
|
|
1699
|
+
"trustme",
|
|
1700
|
+
"certifi",
|
|
1701
|
+
"ssl",
|
|
1702
|
+
]
|
|
1703
|
+
tls_config = ["ssl", "tls", "https", "certificate", "cert"]
|
|
1704
|
+
|
|
1705
|
+
return (
|
|
1706
|
+
_has_import(ctx.imports, tls_imports)
|
|
1707
|
+
or _has_dependency(ctx.deps, tls_deps)
|
|
1708
|
+
or _has_config_key(ctx.config_keys, tls_config)
|
|
1709
|
+
)
|
|
1710
|
+
|
|
1711
|
+
|
|
1712
|
+
def _check_self_contained_tokens(*args) -> bool:
|
|
1713
|
+
"""Activate for projects issuing or validating JWT/SAML tokens."""
|
|
1714
|
+
ctx = _DomainContext.from_args(args)
|
|
1715
|
+
|
|
1716
|
+
sct_deps = [
|
|
1717
|
+
"jsonwebtoken",
|
|
1718
|
+
"jose",
|
|
1719
|
+
"pyjwt",
|
|
1720
|
+
"ruby-jwt",
|
|
1721
|
+
"jjwt",
|
|
1722
|
+
"pysaml2",
|
|
1723
|
+
"python3-saml",
|
|
1724
|
+
"onelogin",
|
|
1725
|
+
]
|
|
1726
|
+
sct_imports = [
|
|
1727
|
+
"jwt.sign",
|
|
1728
|
+
"jwt.verify",
|
|
1729
|
+
"jwt.decode",
|
|
1730
|
+
"JWS",
|
|
1731
|
+
"JWE",
|
|
1732
|
+
"JWK",
|
|
1733
|
+
"JWA",
|
|
1734
|
+
"saml2",
|
|
1735
|
+
"SAMLResponse",
|
|
1736
|
+
"Assertion",
|
|
1737
|
+
"RS256",
|
|
1738
|
+
"ES256",
|
|
1739
|
+
"HS256",
|
|
1740
|
+
]
|
|
1741
|
+
|
|
1742
|
+
return _has_import(ctx.imports, sct_imports) or _has_dependency(ctx.deps, sct_deps)
|
|
1743
|
+
|
|
1744
|
+
|
|
1745
|
+
def _check_supply_chain_local_tool(*args) -> bool:
|
|
1746
|
+
"""Activate for CLI tools with release pipelines or install scripts."""
|
|
1747
|
+
ctx = _DomainContext.from_args(args)
|
|
1748
|
+
|
|
1749
|
+
sc_infra = [
|
|
1750
|
+
"goreleaser.yml",
|
|
1751
|
+
".goreleaser.yml",
|
|
1752
|
+
"release.yml",
|
|
1753
|
+
"install.sh",
|
|
1754
|
+
"install.ps1",
|
|
1755
|
+
]
|
|
1756
|
+
has_bin = any("bin" in str(f).lower() for f in ctx.infra_files)
|
|
1757
|
+
|
|
1758
|
+
return _has_infra_file(ctx.infra_files, sc_infra) or has_bin or ctx.has_cli
|
|
1759
|
+
|
|
1760
|
+
|
|
1761
|
+
def _check_web_frontend(*args) -> bool:
|
|
1762
|
+
"""Activate for browser-based frontends (React, Vue, Angular, Svelte)."""
|
|
1763
|
+
ctx = _DomainContext.from_args(args)
|
|
1764
|
+
|
|
1765
|
+
wf_deps = [
|
|
1766
|
+
"react",
|
|
1767
|
+
"react-dom",
|
|
1768
|
+
"vue",
|
|
1769
|
+
"@vue/cli",
|
|
1770
|
+
"@angular/core",
|
|
1771
|
+
"svelte",
|
|
1772
|
+
"solid-js",
|
|
1773
|
+
"preact",
|
|
1774
|
+
"lit",
|
|
1775
|
+
"ember-cli",
|
|
1776
|
+
]
|
|
1777
|
+
wf_imports = [
|
|
1778
|
+
"ReactDOM",
|
|
1779
|
+
"createRoot",
|
|
1780
|
+
"Vue.createApp",
|
|
1781
|
+
"angular.module",
|
|
1782
|
+
"Component",
|
|
1783
|
+
"OnInit",
|
|
1784
|
+
"svelte.mount",
|
|
1785
|
+
"render",
|
|
1786
|
+
]
|
|
1787
|
+
wf_infra = [
|
|
1788
|
+
"webpack.config",
|
|
1789
|
+
"vite.config",
|
|
1790
|
+
"rollup.config",
|
|
1791
|
+
"next.config",
|
|
1792
|
+
"nuxt.config",
|
|
1793
|
+
"angular.json",
|
|
1794
|
+
"tsconfig.json",
|
|
1795
|
+
"index.html",
|
|
1796
|
+
]
|
|
1797
|
+
|
|
1798
|
+
return (
|
|
1799
|
+
_has_import(ctx.imports, wf_imports)
|
|
1800
|
+
or _has_dependency(ctx.deps, wf_deps)
|
|
1801
|
+
or _has_infra_file(ctx.infra_files, wf_infra)
|
|
1802
|
+
)
|
|
1803
|
+
|
|
1804
|
+
|
|
1805
|
+
def _check_webrtc_communication(*args) -> bool:
|
|
1806
|
+
"""Activate for projects running TURN servers or WebRTC media/signaling."""
|
|
1807
|
+
ctx = _DomainContext.from_args(args)
|
|
1808
|
+
|
|
1809
|
+
wrtc_deps = [
|
|
1810
|
+
"simple-peer",
|
|
1811
|
+
"peerjs",
|
|
1812
|
+
"werift",
|
|
1813
|
+
"pion/webrtc",
|
|
1814
|
+
"mediasoup",
|
|
1815
|
+
"janus",
|
|
1816
|
+
"kurento",
|
|
1817
|
+
"coturn",
|
|
1818
|
+
]
|
|
1819
|
+
wrtc_imports = [
|
|
1820
|
+
"RTCPeerConnection",
|
|
1821
|
+
"RTCSessionDescription",
|
|
1822
|
+
"webrtc",
|
|
1823
|
+
"PeerConnection",
|
|
1824
|
+
"MediaStream",
|
|
1825
|
+
"createOffer",
|
|
1826
|
+
"createAnswer",
|
|
1827
|
+
"addIceCandidate",
|
|
1828
|
+
]
|
|
1829
|
+
|
|
1830
|
+
return _has_import(ctx.imports, wrtc_imports) or _has_dependency(ctx.deps, wrtc_deps)
|
|
1831
|
+
|
|
1832
|
+
|
|
1833
|
+
def _check_security_scanner_tool(*args) -> bool:
|
|
1834
|
+
"""Activate for security scanner/analysis tools (SAST, DAST, SCA, agents)."""
|
|
1835
|
+
ctx = _DomainContext.from_args(args)
|
|
1836
|
+
|
|
1837
|
+
scanner_imports = [
|
|
1838
|
+
"scan",
|
|
1839
|
+
"detect",
|
|
1840
|
+
"finding",
|
|
1841
|
+
"vulnerability",
|
|
1842
|
+
"cve",
|
|
1843
|
+
"semgrep",
|
|
1844
|
+
"bandit",
|
|
1845
|
+
"safety",
|
|
1846
|
+
"trivy",
|
|
1847
|
+
"grype",
|
|
1848
|
+
"BaseAgent",
|
|
1849
|
+
"AgentInput",
|
|
1850
|
+
"AgentResult",
|
|
1851
|
+
"coordinator",
|
|
1852
|
+
"governor",
|
|
1853
|
+
"dispatcher",
|
|
1854
|
+
"domain_activator",
|
|
1855
|
+
"domain_loader",
|
|
1856
|
+
"security_probe",
|
|
1857
|
+
"security_config",
|
|
1858
|
+
]
|
|
1859
|
+
scanner_deps = [
|
|
1860
|
+
"semgrep",
|
|
1861
|
+
"bandit",
|
|
1862
|
+
"safety",
|
|
1863
|
+
"trivy",
|
|
1864
|
+
"grype",
|
|
1865
|
+
"syft",
|
|
1866
|
+
"cosign",
|
|
1867
|
+
"checkov",
|
|
1868
|
+
"tfsec",
|
|
1869
|
+
"kics",
|
|
1870
|
+
]
|
|
1871
|
+
|
|
1872
|
+
has_scanner_code = _has_import(ctx.imports, scanner_imports) or _has_dependency(
|
|
1873
|
+
ctx.deps, scanner_deps
|
|
1874
|
+
)
|
|
1875
|
+
has_scanner_structure = any(
|
|
1876
|
+
any(seg in f.lower() for seg in ["scan", "detect", "finding", "agent", "security"])
|
|
1877
|
+
for f in ctx.infra_files
|
|
1878
|
+
)
|
|
1879
|
+
|
|
1880
|
+
return has_scanner_code or has_scanner_structure
|
|
1881
|
+
|
|
1882
|
+
|
|
1883
|
+
def _check_websocket_security(*args) -> bool:
|
|
1884
|
+
"""Activate for projects using WebSocket server or client libraries."""
|
|
1885
|
+
ctx = _DomainContext.from_args(args)
|
|
1886
|
+
|
|
1887
|
+
ws_deps = [
|
|
1888
|
+
"ws",
|
|
1889
|
+
"socket.io",
|
|
1890
|
+
"sockjs",
|
|
1891
|
+
"websocket",
|
|
1892
|
+
"channels",
|
|
1893
|
+
"django-channels",
|
|
1894
|
+
"signalr",
|
|
1895
|
+
]
|
|
1896
|
+
ws_imports = [
|
|
1897
|
+
"WebSocket",
|
|
1898
|
+
"Socket.IO",
|
|
1899
|
+
"socket.io",
|
|
1900
|
+
"ws.Server",
|
|
1901
|
+
"socketio",
|
|
1902
|
+
"SignalR",
|
|
1903
|
+
"channels",
|
|
1904
|
+
"on('connection')",
|
|
1905
|
+
"wss://",
|
|
1906
|
+
]
|
|
1907
|
+
|
|
1908
|
+
return _has_import(ctx.imports, ws_imports) or _has_dependency(ctx.deps, ws_deps)
|
|
1909
|
+
|
|
1910
|
+
|
|
1911
|
+
# ---------------------------------------------------------------------------
|
|
1912
|
+
|
|
1913
|
+
# Type alias for clarity
|
|
1914
|
+
_DomainCheckerFn = Callable[..., bool]
|
|
1915
|
+
|
|
1916
|
+
_DOMAIN_CHECKERS: dict[str, _DomainCheckerFn] = {
|
|
1917
|
+
# Original 13 technology-stack domains
|
|
1918
|
+
"native-code-safety": _check_native_code_safety,
|
|
1919
|
+
"go-concurrency": _check_go_concurrency,
|
|
1920
|
+
"jvm-hardening": _check_jvm_hardening,
|
|
1921
|
+
"mobile-native": _check_mobile_native,
|
|
1922
|
+
"ruby-rails": _check_ruby_rails,
|
|
1923
|
+
"svelte-ssr": _check_svelte_ssr,
|
|
1924
|
+
"cargo-supply-chain": _check_cargo_supply_chain,
|
|
1925
|
+
"nodejs-runtime": _check_nodejs_runtime,
|
|
1926
|
+
"express-web": _check_express_web,
|
|
1927
|
+
"nextjs-app": _check_nextjs_app,
|
|
1928
|
+
"python-runtime": _check_python_runtime,
|
|
1929
|
+
"django-hardening": _check_django_hardening,
|
|
1930
|
+
"flask-hardening": _check_flask_hardening,
|
|
1931
|
+
# 8 security-domain agents (added 2026-07-12)
|
|
1932
|
+
"cdn-cache-security": _check_cdn_cache_security,
|
|
1933
|
+
"dns-security": _check_dns_security,
|
|
1934
|
+
"email-authentication": _check_email_authentication,
|
|
1935
|
+
"push-notification-security": _check_push_notification_security,
|
|
1936
|
+
"saml-sso-security": _check_saml_sso_security,
|
|
1937
|
+
"secrets-runtime-management": _check_secrets_runtime_management,
|
|
1938
|
+
"service-mesh-security": _check_service_mesh_security,
|
|
1939
|
+
"kubernetes-hardening": _check_kubernetes_hardening,
|
|
1940
|
+
# 27 additional technology-stack domains (added 2026-07-12)
|
|
1941
|
+
"access-control-authz": _check_access_control_authz,
|
|
1942
|
+
"agent-orchestration": _check_agent_orchestration,
|
|
1943
|
+
"auth-session": _check_auth_session,
|
|
1944
|
+
"cicd-pipeline": _check_cicd_pipeline,
|
|
1945
|
+
"configuration-hardening": _check_configuration_hardening,
|
|
1946
|
+
"container-infra": _check_container_infra,
|
|
1947
|
+
"data-layer": _check_data_layer,
|
|
1948
|
+
"data-protection-privacy": _check_data_protection_privacy,
|
|
1949
|
+
"desktop-app": _check_desktop_app,
|
|
1950
|
+
"file-handling": _check_file_handling,
|
|
1951
|
+
"general-cryptography": _check_general_cryptography,
|
|
1952
|
+
"github-app-bot": _check_github_app_bot,
|
|
1953
|
+
"graphql-api-security": _check_graphql_api_security,
|
|
1954
|
+
"input-validation-business-logic": _check_input_validation_business_logic,
|
|
1955
|
+
"llm-integration": _check_llm_integration,
|
|
1956
|
+
"logging-error-handling": _check_logging_error_handling,
|
|
1957
|
+
"mcp-tool-surface": _check_mcp_tool_surface,
|
|
1958
|
+
"message-queue-event-driven": _check_message_queue_event_driven,
|
|
1959
|
+
"mobile": _check_mobile,
|
|
1960
|
+
"oauth-oidc": _check_oauth_oidc,
|
|
1961
|
+
"secure-coding-architecture": _check_secure_coding_architecture,
|
|
1962
|
+
"secure-communication-tls": _check_secure_communication_tls,
|
|
1963
|
+
"self-contained-tokens": _check_self_contained_tokens,
|
|
1964
|
+
"supply-chain-local-tool": _check_supply_chain_local_tool,
|
|
1965
|
+
"web-frontend": _check_web_frontend,
|
|
1966
|
+
"webrtc-communication": _check_webrtc_communication,
|
|
1967
|
+
"websocket-security": _check_websocket_security,
|
|
1968
|
+
"security-scanner-tool": _check_security_scanner_tool,
|
|
1969
|
+
}
|
|
1970
|
+
|
|
1971
|
+
# Merge auto-generated checkers (253 domains) — existing entries take precedence
|
|
1972
|
+
_DOMAIN_CHECKERS.update({k: v for k, v in _GENERATED_CHECKERS.items() if k not in _DOMAIN_CHECKERS})
|
|
1973
|
+
|
|
1974
|
+
|
|
1975
|
+
def activate_domains(
|
|
1976
|
+
args: Sequence,
|
|
1977
|
+
only: Iterable[str] | None = None,
|
|
1978
|
+
) -> list[str]:
|
|
1979
|
+
"""Return the sorted list of domain IDs that should activate for ``args``.
|
|
1980
|
+
|
|
1981
|
+
Parameters
|
|
1982
|
+
----------
|
|
1983
|
+
args:
|
|
1984
|
+
The 11-element tuple as described in :class:`_DomainContext`.
|
|
1985
|
+
only:
|
|
1986
|
+
Optional iterable of domain IDs to restrict the check to. ``None``
|
|
1987
|
+
means check all registered domains.
|
|
1988
|
+
"""
|
|
1989
|
+
candidate_ids = list(only) if only is not None else list(_DOMAIN_CHECKERS)
|
|
1990
|
+
activated: list[str] = []
|
|
1991
|
+
for domain_id in candidate_ids:
|
|
1992
|
+
checker = _DOMAIN_CHECKERS.get(domain_id)
|
|
1993
|
+
if checker is None:
|
|
1994
|
+
continue
|
|
1995
|
+
try:
|
|
1996
|
+
if checker(*args):
|
|
1997
|
+
activated.append(domain_id)
|
|
1998
|
+
except Exception as e:
|
|
1999
|
+
# A single checker failure must not block other domains.
|
|
2000
|
+
# Log to the patchi worklog in a production deployment.
|
|
2001
|
+
_log.warning("activate_domains failed: %s", e)
|
|
2002
|
+
continue
|
|
2003
|
+
return sorted(activated)
|
|
2004
|
+
|
|
2005
|
+
|
|
2006
|
+
# ---------------------------------------------------------------------------
|
|
2007
|
+
# Legacy compatibility layer
|
|
2008
|
+
# ---------------------------------------------------------------------------
|
|
2009
|
+
|
|
2010
|
+
_LANG_EXT_MAP: dict[str, str] = {
|
|
2011
|
+
".py": "python",
|
|
2012
|
+
".pyw": "python",
|
|
2013
|
+
".pyi": "python",
|
|
2014
|
+
".js": "javascript",
|
|
2015
|
+
".jsx": "javascript",
|
|
2016
|
+
".ts": "typescript",
|
|
2017
|
+
".tsx": "typescript",
|
|
2018
|
+
".java": "java",
|
|
2019
|
+
".kt": "kotlin",
|
|
2020
|
+
".kts": "kotlin",
|
|
2021
|
+
".go": "golang",
|
|
2022
|
+
".rs": "rust",
|
|
2023
|
+
".c": "c",
|
|
2024
|
+
".h": "c",
|
|
2025
|
+
".cpp": "cpp",
|
|
2026
|
+
".hpp": "cpp",
|
|
2027
|
+
".rb": "ruby",
|
|
2028
|
+
".swift": "swift",
|
|
2029
|
+
".svelte": "svelte",
|
|
2030
|
+
".vue": "vue",
|
|
2031
|
+
".dart": "dart",
|
|
2032
|
+
}
|
|
2033
|
+
|
|
2034
|
+
|
|
2035
|
+
def _infer_primary_language(exts: Iterable[str]) -> str:
|
|
2036
|
+
"""Guess the primary language from observed file extensions."""
|
|
2037
|
+
counts: dict[str, int] = {}
|
|
2038
|
+
for ext in exts:
|
|
2039
|
+
lang = _LANG_EXT_MAP.get(ext.lower())
|
|
2040
|
+
if lang:
|
|
2041
|
+
counts[lang] = counts.get(lang, 0) + 1
|
|
2042
|
+
if not counts:
|
|
2043
|
+
return ""
|
|
2044
|
+
return max(counts, key=counts.get)
|
|
2045
|
+
|
|
2046
|
+
|
|
2047
|
+
def _infer_app_type(
|
|
2048
|
+
active_domains: list[str],
|
|
2049
|
+
has_web: bool,
|
|
2050
|
+
has_cli: bool,
|
|
2051
|
+
has_mobile: bool,
|
|
2052
|
+
) -> str:
|
|
2053
|
+
"""Infer the project's application type from activated domains."""
|
|
2054
|
+
if has_mobile or "mobile" in active_domains or "mobile-native" in active_domains:
|
|
2055
|
+
return "mobile-app"
|
|
2056
|
+
if has_web:
|
|
2057
|
+
return "web-app"
|
|
2058
|
+
if has_cli:
|
|
2059
|
+
return "cli-tool"
|
|
2060
|
+
return "library"
|
|
2061
|
+
|
|
2062
|
+
|
|
2063
|
+
def _infer_deployment(active_domains: list[str], infra_files: list[str]) -> str:
|
|
2064
|
+
"""Infer deployment model from activated domains and infra files."""
|
|
2065
|
+
if "container-infra" in active_domains:
|
|
2066
|
+
return "containerized"
|
|
2067
|
+
if "cicd-pipeline" in active_domains:
|
|
2068
|
+
return "pipeline"
|
|
2069
|
+
has_kubernetes = any("k8s" in f.lower() or "kubernetes" in f.lower() for f in infra_files)
|
|
2070
|
+
if has_kubernetes:
|
|
2071
|
+
return "kubernetes"
|
|
2072
|
+
has_docker = any("docker" in f.lower() for f in infra_files)
|
|
2073
|
+
if has_docker:
|
|
2074
|
+
return "docker"
|
|
2075
|
+
return "local"
|
|
2076
|
+
|
|
2077
|
+
|
|
2078
|
+
def build_project_context(
|
|
2079
|
+
file_infos: Any = None,
|
|
2080
|
+
detected_imports: set[str] | None = None,
|
|
2081
|
+
dependency_names: set[str] | None = None,
|
|
2082
|
+
route_paths: list[str] | None = None,
|
|
2083
|
+
config_keys: set[str] | None = None,
|
|
2084
|
+
infrastructure_files: list[str] | None = None,
|
|
2085
|
+
has_web_framework: bool = False,
|
|
2086
|
+
has_cli_framework: bool = False,
|
|
2087
|
+
has_mobile_code: bool = False,
|
|
2088
|
+
file_extensions: set[str] | None = None,
|
|
2089
|
+
**kwargs: Any,
|
|
2090
|
+
) -> dict[str, Any]:
|
|
2091
|
+
"""Build a rich project context dict with discovered info and active domains.
|
|
2092
|
+
|
|
2093
|
+
Legacy compatibility wrapper around :func:`activate_domains`.
|
|
2094
|
+
Accepts the same keyword parameters as the original
|
|
2095
|
+
``patchi.core.brain.domain_activator.build_project_context``.
|
|
2096
|
+
"""
|
|
2097
|
+
exts = sorted(file_extensions or [])
|
|
2098
|
+
args = [
|
|
2099
|
+
_infer_primary_language(exts), # language
|
|
2100
|
+
"", # framework (inferred elsewhere)
|
|
2101
|
+
list(detected_imports or []), # imports
|
|
2102
|
+
list(dependency_names or []), # deps
|
|
2103
|
+
list(route_paths or []), # routes
|
|
2104
|
+
list(config_keys or []), # config_keys
|
|
2105
|
+
list(infrastructure_files or []), # infra_files
|
|
2106
|
+
bool(has_web_framework), # has_web
|
|
2107
|
+
bool(has_cli_framework), # has_cli
|
|
2108
|
+
bool(has_mobile_code), # has_mobile
|
|
2109
|
+
exts, # exts
|
|
2110
|
+
]
|
|
2111
|
+
active = activate_domains(args)
|
|
2112
|
+
|
|
2113
|
+
context: dict[str, Any] = {
|
|
2114
|
+
"relevant_domains": active,
|
|
2115
|
+
"app_type": _infer_app_type(active, has_web_framework, has_cli_framework, has_mobile_code),
|
|
2116
|
+
"deployment_model": _infer_deployment(active, infrastructure_files or []),
|
|
2117
|
+
"has_user_auth": "auth-session" in active or "oauth-oidc" in active,
|
|
2118
|
+
"active_domain_count": len(active),
|
|
2119
|
+
}
|
|
2120
|
+
return context
|
|
2121
|
+
|
|
2122
|
+
|
|
2123
|
+
__all__ = [
|
|
2124
|
+
"_DomainContext",
|
|
2125
|
+
"_DOMAIN_CHECKERS",
|
|
2126
|
+
"_has_import",
|
|
2127
|
+
"_has_dependency",
|
|
2128
|
+
"_has_ext",
|
|
2129
|
+
"_has_infra_file",
|
|
2130
|
+
"_has_config_key",
|
|
2131
|
+
"activate_domains",
|
|
2132
|
+
# Original 13
|
|
2133
|
+
"_check_native_code_safety",
|
|
2134
|
+
"_check_go_concurrency",
|
|
2135
|
+
"_check_jvm_hardening",
|
|
2136
|
+
"_check_mobile_native",
|
|
2137
|
+
"_check_ruby_rails",
|
|
2138
|
+
"_check_svelte_ssr",
|
|
2139
|
+
"_check_cargo_supply_chain",
|
|
2140
|
+
"_check_nodejs_runtime",
|
|
2141
|
+
"_check_express_web",
|
|
2142
|
+
"_check_nextjs_app",
|
|
2143
|
+
"_check_python_runtime",
|
|
2144
|
+
"_check_django_hardening",
|
|
2145
|
+
"_check_flask_hardening",
|
|
2146
|
+
# 8 security-domain agents
|
|
2147
|
+
"_check_cdn_cache_security",
|
|
2148
|
+
"_check_dns_security",
|
|
2149
|
+
"_check_email_authentication",
|
|
2150
|
+
"_check_push_notification_security",
|
|
2151
|
+
"_check_saml_sso_security",
|
|
2152
|
+
"_check_secrets_runtime_management",
|
|
2153
|
+
"_check_service_mesh_security",
|
|
2154
|
+
"_check_kubernetes_hardening",
|
|
2155
|
+
# 27 additional technology-stack domains
|
|
2156
|
+
"_check_access_control_authz",
|
|
2157
|
+
"_check_agent_orchestration",
|
|
2158
|
+
"_check_auth_session",
|
|
2159
|
+
"_check_cicd_pipeline",
|
|
2160
|
+
"_check_configuration_hardening",
|
|
2161
|
+
"_check_container_infra",
|
|
2162
|
+
"_check_data_layer",
|
|
2163
|
+
"_check_data_protection_privacy",
|
|
2164
|
+
"_check_desktop_app",
|
|
2165
|
+
"_check_file_handling",
|
|
2166
|
+
"_check_general_cryptography",
|
|
2167
|
+
"_check_github_app_bot",
|
|
2168
|
+
"_check_graphql_api_security",
|
|
2169
|
+
"_check_input_validation_business_logic",
|
|
2170
|
+
"_check_llm_integration",
|
|
2171
|
+
"_check_logging_error_handling",
|
|
2172
|
+
"_check_mcp_tool_surface",
|
|
2173
|
+
"_check_message_queue_event_driven",
|
|
2174
|
+
"_check_mobile",
|
|
2175
|
+
"_check_oauth_oidc",
|
|
2176
|
+
"_check_secure_coding_architecture",
|
|
2177
|
+
"_check_secure_communication_tls",
|
|
2178
|
+
"_check_self_contained_tokens",
|
|
2179
|
+
"_check_supply_chain_local_tool",
|
|
2180
|
+
"_check_web_frontend",
|
|
2181
|
+
"_check_webrtc_communication",
|
|
2182
|
+
"_check_websocket_security",
|
|
2183
|
+
]
|