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,271 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SensitiveDataAgent — PII and credential exposure detection.
|
|
3
|
+
|
|
4
|
+
Scans source code for:
|
|
5
|
+
- Hardcoded API keys, tokens, passwords, secrets
|
|
6
|
+
- PII patterns (emails, SSNs, credit cards, phone numbers, IPs)
|
|
7
|
+
- Connection strings with embedded credentials
|
|
8
|
+
- Private keys and certificates in source
|
|
9
|
+
- AWS/GCP/Azure credentials
|
|
10
|
+
- Database URLs with passwords
|
|
11
|
+
|
|
12
|
+
Uses regex pattern matching. Does NOT call AI.
|
|
13
|
+
Does NOT write to disk. Does NOT touch the queue.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import logging
|
|
19
|
+
import os
|
|
20
|
+
import re
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
|
|
23
|
+
from patchi.core.brain.languages import DEFAULT_IGNORE_DIRS
|
|
24
|
+
|
|
25
|
+
from ..agents.base import (
|
|
26
|
+
AgentDomain,
|
|
27
|
+
AgentGroup,
|
|
28
|
+
AgentInput,
|
|
29
|
+
AgentResult,
|
|
30
|
+
AgentStatus,
|
|
31
|
+
BaseAgent,
|
|
32
|
+
Finding,
|
|
33
|
+
Severity,
|
|
34
|
+
make_finding,
|
|
35
|
+
register,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
_log = logging.getLogger("patchi.security.sensitive_data_agent")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@register
|
|
42
|
+
class SensitiveDataAgent(BaseAgent):
|
|
43
|
+
"""Agent for detecting PII and credential exposure in source code."""
|
|
44
|
+
|
|
45
|
+
group = AgentGroup.SECURITY
|
|
46
|
+
domain = AgentDomain.SECURITY
|
|
47
|
+
name = "SensitiveDataAgent"
|
|
48
|
+
description = (
|
|
49
|
+
"PII/credential exposure: API keys, passwords, emails, SSNs, connection strings in code"
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
# ── Credential patterns ─────────────────────────────────────────────────
|
|
53
|
+
_CREDENTIAL_PATTERNS = [
|
|
54
|
+
# Generic API keys / secrets
|
|
55
|
+
(
|
|
56
|
+
re.compile(
|
|
57
|
+
r'(?:api[_-]?key|apikey|secret[_-]?key|access[_-]?key|auth[_-]?token)\s*[:=]\s*["\'][A-Za-z0-9+/=_-]{16,}["\']',
|
|
58
|
+
re.I,
|
|
59
|
+
),
|
|
60
|
+
Severity.CRITICAL,
|
|
61
|
+
"Hardcoded API key/token in source code",
|
|
62
|
+
),
|
|
63
|
+
(
|
|
64
|
+
re.compile(r'(?:password|passwd|pwd)\s*[:=]\s*["\'][^"\']{4,}["\']', re.I),
|
|
65
|
+
Severity.CRITICAL,
|
|
66
|
+
"Hardcoded password in source code",
|
|
67
|
+
),
|
|
68
|
+
(
|
|
69
|
+
re.compile(
|
|
70
|
+
r'(?:secret|client[_-]?secret)\s*[:=]\s*["\'][A-Za-z0-9+/=_-]{8,}["\']', re.I
|
|
71
|
+
),
|
|
72
|
+
Severity.CRITICAL,
|
|
73
|
+
"Hardcoded secret in source code",
|
|
74
|
+
),
|
|
75
|
+
# AWS
|
|
76
|
+
(
|
|
77
|
+
re.compile(r"AKIA[0-9A-Z]{16}", re.I),
|
|
78
|
+
Severity.CRITICAL,
|
|
79
|
+
"AWS Access Key ID found in source code",
|
|
80
|
+
),
|
|
81
|
+
(
|
|
82
|
+
re.compile(
|
|
83
|
+
r'(?:aws[_-]?secret|aws[_-]?access[_-]?key)\s*[:=]\s*["\'][^"\']{8,}["\']', re.I
|
|
84
|
+
),
|
|
85
|
+
Severity.CRITICAL,
|
|
86
|
+
"AWS credential in source code",
|
|
87
|
+
),
|
|
88
|
+
# GCP
|
|
89
|
+
(
|
|
90
|
+
re.compile(r'["\']AIza[0-9A-Za-z_-]{35}["\']', re.I),
|
|
91
|
+
Severity.CRITICAL,
|
|
92
|
+
"GCP API key found in source code",
|
|
93
|
+
),
|
|
94
|
+
(
|
|
95
|
+
re.compile(
|
|
96
|
+
r'(?:gcp[_-]?key|google[_-]?api[_-]?key)\s*[:=]\s*["\'][^"\']{8,}["\']', re.I
|
|
97
|
+
),
|
|
98
|
+
Severity.CRITICAL,
|
|
99
|
+
"GCP credential in source code",
|
|
100
|
+
),
|
|
101
|
+
# Azure
|
|
102
|
+
(
|
|
103
|
+
re.compile(
|
|
104
|
+
r'(?:azure|az)[_-]?(?:key|secret|token)\s*[:=]\s*["\'][^"\']{8,}["\']', re.I
|
|
105
|
+
),
|
|
106
|
+
Severity.CRITICAL,
|
|
107
|
+
"Azure credential in source code",
|
|
108
|
+
),
|
|
109
|
+
# Private keys
|
|
110
|
+
(
|
|
111
|
+
re.compile(r"-----BEGIN\s+(?:RSA\s+)?PRIVATE\s+KEY-----", re.I),
|
|
112
|
+
Severity.CRITICAL,
|
|
113
|
+
"Private key embedded in source code",
|
|
114
|
+
),
|
|
115
|
+
(
|
|
116
|
+
re.compile(r"-----BEGIN\s+OPENSSH\s+PRIVATE\s+KEY-----", re.I),
|
|
117
|
+
Severity.CRITICAL,
|
|
118
|
+
"SSH private key embedded in source code",
|
|
119
|
+
),
|
|
120
|
+
# Connection strings with passwords
|
|
121
|
+
(
|
|
122
|
+
re.compile(r"(?:mongodb|mysql|postgres|redis|amqp|smtp)://[^:]+:[^@]+@", re.I),
|
|
123
|
+
Severity.HIGH,
|
|
124
|
+
"Connection string with embedded credentials",
|
|
125
|
+
),
|
|
126
|
+
(
|
|
127
|
+
re.compile(r'DATABASE_URL\s*[:=]\s*["\'][^"\']+:.*@', re.I),
|
|
128
|
+
Severity.HIGH,
|
|
129
|
+
"DATABASE_URL with embedded password",
|
|
130
|
+
),
|
|
131
|
+
# Generic high-entropy strings that look like secrets
|
|
132
|
+
(
|
|
133
|
+
re.compile(r'(?:token|bearer)\s*[:=]\s*["\'][A-Za-z0-9+/=_-]{32,}["\']', re.I),
|
|
134
|
+
Severity.HIGH,
|
|
135
|
+
"Long bearer/token string — possible hardcoded credential",
|
|
136
|
+
),
|
|
137
|
+
]
|
|
138
|
+
|
|
139
|
+
# ── PII patterns ────────────────────────────────────────────────────────
|
|
140
|
+
_PII_PATTERNS = [
|
|
141
|
+
# Email addresses
|
|
142
|
+
(
|
|
143
|
+
re.compile(r'["\'][a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}["\']', re.I),
|
|
144
|
+
Severity.MEDIUM,
|
|
145
|
+
"Email address hardcoded in source",
|
|
146
|
+
),
|
|
147
|
+
# SSN patterns (US)
|
|
148
|
+
(
|
|
149
|
+
re.compile(r'["\']?\d{3}-\d{2}-\d{4}["\']?'),
|
|
150
|
+
Severity.HIGH,
|
|
151
|
+
"Possible Social Security Number in source",
|
|
152
|
+
),
|
|
153
|
+
# Credit card numbers
|
|
154
|
+
(
|
|
155
|
+
re.compile(r'["\']?\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}["\']?'),
|
|
156
|
+
Severity.HIGH,
|
|
157
|
+
"Possible credit card number in source",
|
|
158
|
+
),
|
|
159
|
+
# Phone numbers (US format)
|
|
160
|
+
(
|
|
161
|
+
re.compile(r'["\']?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}["\']?'),
|
|
162
|
+
Severity.LOW,
|
|
163
|
+
"Phone number in source — verify if intentional",
|
|
164
|
+
),
|
|
165
|
+
# IP addresses with ports (possible internal infra exposure)
|
|
166
|
+
(
|
|
167
|
+
re.compile(r'["\']?\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+["\']?'),
|
|
168
|
+
Severity.MEDIUM,
|
|
169
|
+
"IP:port pair in source — verify if intentional",
|
|
170
|
+
),
|
|
171
|
+
]
|
|
172
|
+
|
|
173
|
+
_SKIP_DIRS = DEFAULT_IGNORE_DIRS | {"test", "tests", "test_*"}
|
|
174
|
+
|
|
175
|
+
# Test files are excluded from PII checks (may contain test data)
|
|
176
|
+
_SKIP_DIRS_PII = DEFAULT_IGNORE_DIRS | {"test", "tests"}
|
|
177
|
+
|
|
178
|
+
def _run(self, inp: AgentInput, result: AgentResult) -> None:
|
|
179
|
+
findings = []
|
|
180
|
+
files_scanned = 0
|
|
181
|
+
|
|
182
|
+
for p in self._safe_rglob(inp.root, "*.py"):
|
|
183
|
+
rel = str(p.relative_to(inp.root))
|
|
184
|
+
if self._should_skip(rel):
|
|
185
|
+
continue
|
|
186
|
+
files_scanned += 1
|
|
187
|
+
try:
|
|
188
|
+
content = p.read_text(encoding="utf-8", errors="ignore")
|
|
189
|
+
except Exception as e:
|
|
190
|
+
_log.warning("SensitiveDataAgent._run failed: %s", e)
|
|
191
|
+
continue
|
|
192
|
+
|
|
193
|
+
findings.extend(self._check_credentials(content, rel))
|
|
194
|
+
findings.extend(self._check_pii(content, rel))
|
|
195
|
+
|
|
196
|
+
result.status = AgentStatus.DONE
|
|
197
|
+
result.findings = findings
|
|
198
|
+
result.files_scanned = files_scanned
|
|
199
|
+
result.data.update({"files_scanned": files_scanned, "finding_count": len(findings)})
|
|
200
|
+
return
|
|
201
|
+
|
|
202
|
+
def _check_credentials(self, content: str, rel_path: str) -> list[Finding]:
|
|
203
|
+
findings = []
|
|
204
|
+
lines = content.splitlines()
|
|
205
|
+
for line_num, line in enumerate(lines, 1):
|
|
206
|
+
stripped = line.strip()
|
|
207
|
+
if stripped.startswith("#") or stripped.startswith("//"):
|
|
208
|
+
continue
|
|
209
|
+
# Skip obvious test/mock patterns (but not in the key value itself)
|
|
210
|
+
line_lower = stripped.lower()
|
|
211
|
+
if any(
|
|
212
|
+
kw in line_lower.split('"')[0] or kw in line_lower.split("'")[0]
|
|
213
|
+
for kw in ("placeholder", "xxx", "fake_", "mock_")
|
|
214
|
+
):
|
|
215
|
+
continue
|
|
216
|
+
|
|
217
|
+
for pattern, severity, message in self._CREDENTIAL_PATTERNS:
|
|
218
|
+
if pattern.search(line):
|
|
219
|
+
findings.append(
|
|
220
|
+
make_finding(
|
|
221
|
+
self.name,
|
|
222
|
+
"credential_exposure",
|
|
223
|
+
severity,
|
|
224
|
+
rel_path,
|
|
225
|
+
message,
|
|
226
|
+
line=line_num,
|
|
227
|
+
code_snippet=stripped[:120],
|
|
228
|
+
suggestion="Move credentials to .env file or secret manager. Never commit secrets.",
|
|
229
|
+
)
|
|
230
|
+
)
|
|
231
|
+
return findings
|
|
232
|
+
|
|
233
|
+
def _check_pii(self, content: str, rel_path: str) -> list[Finding]:
|
|
234
|
+
findings = []
|
|
235
|
+
lines = content.splitlines()
|
|
236
|
+
for line_num, line in enumerate(lines, 1):
|
|
237
|
+
stripped = line.strip()
|
|
238
|
+
if stripped.startswith("#") or stripped.startswith("//"):
|
|
239
|
+
continue
|
|
240
|
+
# Skip test files for PII (they use dummy data)
|
|
241
|
+
if "test" in rel_path.lower():
|
|
242
|
+
continue
|
|
243
|
+
|
|
244
|
+
for pattern, severity, message in self._PII_PATTERNS:
|
|
245
|
+
if pattern.search(line):
|
|
246
|
+
findings.append(
|
|
247
|
+
make_finding(
|
|
248
|
+
self.name,
|
|
249
|
+
"pii_exposure",
|
|
250
|
+
severity,
|
|
251
|
+
rel_path,
|
|
252
|
+
message,
|
|
253
|
+
line=line_num,
|
|
254
|
+
code_snippet=stripped[:120],
|
|
255
|
+
suggestion="Remove PII from source code. Use environment variables or test fixtures.",
|
|
256
|
+
)
|
|
257
|
+
)
|
|
258
|
+
return findings
|
|
259
|
+
|
|
260
|
+
def _should_skip(self, rel_path: str) -> bool:
|
|
261
|
+
parts = rel_path.replace("\\", "/").split("/")
|
|
262
|
+
return any(p in self._SKIP_DIRS for p in parts)
|
|
263
|
+
|
|
264
|
+
def _safe_rglob(self, root: Path, pattern: str):
|
|
265
|
+
"""Directory-walking with pruning."""
|
|
266
|
+
suffix = pattern.replace("*.", ".")
|
|
267
|
+
for dirpath, dirnames, filenames in os.walk(root):
|
|
268
|
+
dirnames[:] = [d for d in dirnames if d not in self._SKIP_DIRS]
|
|
269
|
+
for fn in filenames:
|
|
270
|
+
if fn.endswith(suffix):
|
|
271
|
+
yield Path(dirpath) / fn
|
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
"""Service mesh security agent — validates Istio/Linkerd mesh configuration."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import re
|
|
7
|
+
import shutil
|
|
8
|
+
import subprocess
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from ..agents.base import (
|
|
12
|
+
AgentDomain,
|
|
13
|
+
AgentGroup,
|
|
14
|
+
AgentInput,
|
|
15
|
+
AgentResult,
|
|
16
|
+
AgentStatus,
|
|
17
|
+
BaseAgent,
|
|
18
|
+
Finding,
|
|
19
|
+
Severity,
|
|
20
|
+
make_finding,
|
|
21
|
+
register,
|
|
22
|
+
safe_rglob,
|
|
23
|
+
)
|
|
24
|
+
from ..brain.trace_log import trace_agent
|
|
25
|
+
|
|
26
|
+
# Istio CRD patterns
|
|
27
|
+
ISTIO_CRDS = {
|
|
28
|
+
"VirtualService": {
|
|
29
|
+
"kind": r"kind:\s*VirtualService",
|
|
30
|
+
"hosts": r"hosts:",
|
|
31
|
+
"http": r"http:",
|
|
32
|
+
"route": r"route:",
|
|
33
|
+
"tls": r"tls:",
|
|
34
|
+
},
|
|
35
|
+
"DestinationRule": {
|
|
36
|
+
"kind": r"kind:\s*DestinationRule",
|
|
37
|
+
"mtls": r"mtls:",
|
|
38
|
+
"tls": r"tls:",
|
|
39
|
+
"traffic_policy": r"trafficPolicy:",
|
|
40
|
+
"connection_pool": r"connectionPool:",
|
|
41
|
+
},
|
|
42
|
+
"PeerAuthentication": {
|
|
43
|
+
"kind": r"kind:\s*PeerAuthentication",
|
|
44
|
+
"mtls": r"mtls:",
|
|
45
|
+
"strict": r"STRICT|mode:\s*STRICT",
|
|
46
|
+
"permissive": r"PERMISSIVE|mode:\s*PERMISSIVE",
|
|
47
|
+
},
|
|
48
|
+
"AuthorizationPolicy": {
|
|
49
|
+
"kind": r"kind:\s*AuthorizationPolicy",
|
|
50
|
+
"rules": r"rules:",
|
|
51
|
+
"action": r"action:",
|
|
52
|
+
"deny": r"DENY|action:\s*DENY",
|
|
53
|
+
"allow": r"ALLOW|action:\s*ALLOW",
|
|
54
|
+
},
|
|
55
|
+
"Gateway": {
|
|
56
|
+
"kind": r"kind:\s*Gateway",
|
|
57
|
+
"servers": r"servers:",
|
|
58
|
+
"tls_mode": r"tls:",
|
|
59
|
+
},
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
# Linkerd CRD patterns
|
|
63
|
+
LINKERD_CRDS = {
|
|
64
|
+
"ServiceProfile": {
|
|
65
|
+
"kind": r"kind:\s*ServiceProfile",
|
|
66
|
+
"routes": r"routes:",
|
|
67
|
+
},
|
|
68
|
+
"Server": {
|
|
69
|
+
"kind": r"kind:\s*Server",
|
|
70
|
+
"pod_selector": r"podSelector:",
|
|
71
|
+
"proxy_protocol": r"proxyProtocol:",
|
|
72
|
+
},
|
|
73
|
+
"ServerAuthorization": {
|
|
74
|
+
"kind": r"kind:\s*ServerAuthorization",
|
|
75
|
+
"client": r"client:",
|
|
76
|
+
"server": r"server:",
|
|
77
|
+
},
|
|
78
|
+
"TrafficSplit": {
|
|
79
|
+
"kind": r"kind:\s*TrafficSplit",
|
|
80
|
+
"backends": r"backends:",
|
|
81
|
+
"weighted": r"weighted:",
|
|
82
|
+
},
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
# mTLS mode patterns
|
|
86
|
+
MTLS_PATTERNS = [
|
|
87
|
+
(r"mode:\s*STRICT", "mTLS strict mode (good)", Severity.INFO),
|
|
88
|
+
(r"mode:\s*PERMISSIVE", "mTLS permissive mode (weak)", Severity.MEDIUM),
|
|
89
|
+
(r"mode:\s*DISABLE", "mTLS disabled", Severity.HIGH),
|
|
90
|
+
(r"mtls:\s*\{\s*\}", "mTLS empty config (defaults may apply)", Severity.MEDIUM),
|
|
91
|
+
]
|
|
92
|
+
|
|
93
|
+
# Sidecar injection patterns
|
|
94
|
+
SIDECAR_PATTERNS = [
|
|
95
|
+
(r"sidecar\.istio\.io/inject:\s*[\"']?true[\"']?", "Sidecar injection enabled"),
|
|
96
|
+
(r"sidecar\.istio\.io/inject:\s*[\"']?false[\"']?", "Sidecar injection disabled"),
|
|
97
|
+
(r"linkerd\.io/inject:\s*[\"']?enabled[\"']?", "Linkerd injection enabled"),
|
|
98
|
+
(r"linkerd\.io/inject:\s*[\"']?disabled[\"']?", "Linkerd injection disabled"),
|
|
99
|
+
]
|
|
100
|
+
|
|
101
|
+
# Default deny patterns
|
|
102
|
+
DEFAULT_DENY_PATTERNS = [
|
|
103
|
+
(r"action:\s*DENY.*\n.*rules:\s*\[\s*\]", "Default deny policy with empty rules"),
|
|
104
|
+
(r"action:\s*DENY", "DENY action policy"),
|
|
105
|
+
(r"action:\s*ALLOW", "ALLOW action policy"),
|
|
106
|
+
]
|
|
107
|
+
|
|
108
|
+
CONTROL_IDS = {
|
|
109
|
+
"MESH-01": ("mTLS not in strict mode", Severity.HIGH),
|
|
110
|
+
"MESH-02": ("Missing PeerAuthentication", Severity.MEDIUM),
|
|
111
|
+
"MESH-03": ("Sidecar injection not configured", Severity.MEDIUM),
|
|
112
|
+
"MESH-04": ("No AuthorizationPolicy (default allow)", Severity.MEDIUM),
|
|
113
|
+
"MESH-05": ("Missing default-deny policy", Severity.MEDIUM),
|
|
114
|
+
"MESH-06": ("VirtualService without TLS", Severity.MEDIUM),
|
|
115
|
+
"MESH-07": ("Service mesh CRD detected", Severity.INFO),
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
_log = logging.getLogger("patchi.security.service_mesh_agent")
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@register
|
|
123
|
+
class ServiceMeshAgent(BaseAgent):
|
|
124
|
+
name = "ServiceMeshAgent"
|
|
125
|
+
group = AgentGroup.SECURITY
|
|
126
|
+
domain = AgentDomain.SECURITY
|
|
127
|
+
description = "Service mesh security: mTLS enforcement, sidecar injection, RBAC least privilege, authorization policies"
|
|
128
|
+
|
|
129
|
+
def _run(self, inp: AgentInput, result: AgentResult) -> None:
|
|
130
|
+
with trace_agent(self.name, inp.root) as trace:
|
|
131
|
+
findings: list[Finding] = []
|
|
132
|
+
files_scanned = 0
|
|
133
|
+
|
|
134
|
+
# Track which CRDs we find for aggregate analysis
|
|
135
|
+
found_crds: dict[str, list[str]] = {}
|
|
136
|
+
has_peer_auth = False
|
|
137
|
+
has_authz_policy = False
|
|
138
|
+
has_default_deny = False
|
|
139
|
+
|
|
140
|
+
# Phase 1: Scan YAML/JSON for service mesh CRDs
|
|
141
|
+
for fpath in safe_rglob(inp.root, "*.yaml"):
|
|
142
|
+
files_scanned += 1
|
|
143
|
+
self._scan_manifest(fpath, inp.root, findings, found_crds)
|
|
144
|
+
for fpath in safe_rglob(inp.root, "*.yml"):
|
|
145
|
+
files_scanned += 1
|
|
146
|
+
self._scan_manifest(fpath, inp.root, findings, found_crds)
|
|
147
|
+
|
|
148
|
+
# Phase 2: Scan Helm charts and values
|
|
149
|
+
for fpath in safe_rglob(inp.root, "values.yaml"):
|
|
150
|
+
files_scanned += 1
|
|
151
|
+
self._scan_helm_values(fpath, inp.root, findings)
|
|
152
|
+
|
|
153
|
+
# Phase 3: Scan Helm templates for CRD generation
|
|
154
|
+
for fpath in safe_rglob(inp.root, "*.tpl"):
|
|
155
|
+
files_scanned += 1
|
|
156
|
+
self._scan_manifest(fpath, inp.root, findings, found_crds)
|
|
157
|
+
|
|
158
|
+
# Phase 4: Aggregate analysis
|
|
159
|
+
crd_types = set(found_crds.keys())
|
|
160
|
+
has_peer_auth = "PeerAuthentication" in crd_types
|
|
161
|
+
has_authz_policy = "AuthorizationPolicy" in crd_types
|
|
162
|
+
|
|
163
|
+
# Check for missing PeerAuthentication
|
|
164
|
+
if crd_types and not has_peer_auth:
|
|
165
|
+
findings.append(
|
|
166
|
+
make_finding(
|
|
167
|
+
Severity.MEDIUM,
|
|
168
|
+
"",
|
|
169
|
+
0,
|
|
170
|
+
"MESH-02: No PeerAuthentication found",
|
|
171
|
+
"Service mesh CRDs detected but no PeerAuthentication policy. mTLS may not be enforced.",
|
|
172
|
+
suggestion="Add PeerAuthentication with mode=STRICT for all namespaces",
|
|
173
|
+
control_id="MESH-02",
|
|
174
|
+
)
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
# Check for missing AuthorizationPolicy
|
|
178
|
+
if crd_types and not has_authz_policy:
|
|
179
|
+
findings.append(
|
|
180
|
+
make_finding(
|
|
181
|
+
Severity.MEDIUM,
|
|
182
|
+
"",
|
|
183
|
+
0,
|
|
184
|
+
"MESH-04: No AuthorizationPolicy (default allow)",
|
|
185
|
+
"Service mesh CRDs detected but no AuthorizationPolicy. All traffic may be allowed by default.",
|
|
186
|
+
suggestion="Implement default-deny AuthorizationPolicy and explicit ALLOW rules",
|
|
187
|
+
control_id="MESH-04",
|
|
188
|
+
)
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
# Check for missing default-deny
|
|
192
|
+
if crd_types and not has_default_deny:
|
|
193
|
+
findings.append(
|
|
194
|
+
make_finding(
|
|
195
|
+
Severity.MEDIUM,
|
|
196
|
+
"",
|
|
197
|
+
0,
|
|
198
|
+
"MESH-05: No default-deny policy detected",
|
|
199
|
+
"No DENY action policy found. Consider implementing default-deny for least privilege.",
|
|
200
|
+
suggestion="Create default-deny AuthorizationPolicy in each namespace",
|
|
201
|
+
control_id="MESH-05",
|
|
202
|
+
)
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
# Phase 5: Try external tools
|
|
206
|
+
# Try istioctl analyze
|
|
207
|
+
istioctl_path = shutil.which("istioctl")
|
|
208
|
+
if istioctl_path:
|
|
209
|
+
try:
|
|
210
|
+
proc = subprocess.run(
|
|
211
|
+
[istioctl_path, "analyze", "--namespace", "default", str(inp.root)],
|
|
212
|
+
capture_output=True,
|
|
213
|
+
text=True,
|
|
214
|
+
timeout=60,
|
|
215
|
+
)
|
|
216
|
+
output = proc.stdout + proc.stderr
|
|
217
|
+
if output and "Error" in output:
|
|
218
|
+
for line in output.split("\n"):
|
|
219
|
+
if "Error" in line or "Warning" in line:
|
|
220
|
+
severity = Severity.HIGH if "Error" in line else Severity.MEDIUM
|
|
221
|
+
findings.append(
|
|
222
|
+
make_finding(
|
|
223
|
+
severity,
|
|
224
|
+
"",
|
|
225
|
+
0,
|
|
226
|
+
f"istioctl: {line.strip()[:80]}",
|
|
227
|
+
line.strip(),
|
|
228
|
+
suggestion="Fix istioctl analysis finding",
|
|
229
|
+
)
|
|
230
|
+
)
|
|
231
|
+
except (subprocess.TimeoutExpired, Exception):
|
|
232
|
+
pass
|
|
233
|
+
|
|
234
|
+
# Try kube-linter
|
|
235
|
+
kube_linter_path = shutil.which("kube-linter")
|
|
236
|
+
if kube_linter_path:
|
|
237
|
+
try:
|
|
238
|
+
proc = subprocess.run(
|
|
239
|
+
[kube_linter_path, "lint", str(inp.root), "--format", "json"],
|
|
240
|
+
capture_output=True,
|
|
241
|
+
text=True,
|
|
242
|
+
timeout=60,
|
|
243
|
+
)
|
|
244
|
+
if proc.stdout:
|
|
245
|
+
import json
|
|
246
|
+
|
|
247
|
+
try:
|
|
248
|
+
data = json.loads(proc.stdout)
|
|
249
|
+
for item in data.get("objects", []):
|
|
250
|
+
for diag in item.get("diagnostics", []):
|
|
251
|
+
check = diag.get("check", "")
|
|
252
|
+
if any(
|
|
253
|
+
kw in check.lower()
|
|
254
|
+
for kw in ["mtls", "auth", "mesh", "sidecar"]
|
|
255
|
+
):
|
|
256
|
+
findings.append(
|
|
257
|
+
make_finding(
|
|
258
|
+
Severity.MEDIUM,
|
|
259
|
+
item.get("metadata", {}).get("filePath", ""),
|
|
260
|
+
0,
|
|
261
|
+
f"kube-linter: {check}",
|
|
262
|
+
diag.get("message", ""),
|
|
263
|
+
suggestion="Review kube-linter finding for mesh security impact",
|
|
264
|
+
)
|
|
265
|
+
)
|
|
266
|
+
except json.JSONDecodeError:
|
|
267
|
+
pass
|
|
268
|
+
except (subprocess.TimeoutExpired, Exception):
|
|
269
|
+
pass
|
|
270
|
+
|
|
271
|
+
result.findings = findings
|
|
272
|
+
result.files_scanned = files_scanned
|
|
273
|
+
result.status = AgentStatus.DONE
|
|
274
|
+
trace.findings = len(findings)
|
|
275
|
+
trace.files_scanned = files_scanned
|
|
276
|
+
|
|
277
|
+
return
|
|
278
|
+
|
|
279
|
+
def _scan_manifest(self, fpath: Path, root: Path, findings: list, found_crds: dict) -> None:
|
|
280
|
+
"""Scan a YAML/YML file for service mesh CRDs."""
|
|
281
|
+
try:
|
|
282
|
+
content = fpath.read_text(encoding="utf-8", errors="replace")
|
|
283
|
+
except Exception as e:
|
|
284
|
+
_log.warning("ServiceMeshAgent._scan_manifest failed: %s", e)
|
|
285
|
+
return
|
|
286
|
+
|
|
287
|
+
rel = str(fpath.relative_to(root))
|
|
288
|
+
|
|
289
|
+
# Check for Istio CRDs
|
|
290
|
+
for crd_name, patterns in ISTIO_CRDS.items():
|
|
291
|
+
if re.search(patterns["kind"], content):
|
|
292
|
+
found_crds.setdefault(crd_name, []).append(rel)
|
|
293
|
+
findings.append(
|
|
294
|
+
make_finding(
|
|
295
|
+
Severity.INFO,
|
|
296
|
+
rel,
|
|
297
|
+
0,
|
|
298
|
+
f"MESH-07: Istio CRD detected: {crd_name}",
|
|
299
|
+
f"Found {crd_name} custom resource. Verify security configuration.",
|
|
300
|
+
control_id="MESH-07",
|
|
301
|
+
)
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
# Check mTLS configuration in PeerAuthentication
|
|
305
|
+
if crd_name == "PeerAuthentication":
|
|
306
|
+
for pattern, desc, severity in MTLS_PATTERNS:
|
|
307
|
+
if re.search(pattern, content):
|
|
308
|
+
if severity != Severity.INFO:
|
|
309
|
+
findings.append(
|
|
310
|
+
make_finding(
|
|
311
|
+
severity,
|
|
312
|
+
rel,
|
|
313
|
+
0,
|
|
314
|
+
f"MESH-01: {desc}",
|
|
315
|
+
f"PeerAuthentication {desc}. All mesh traffic should use mTLS STRICT.",
|
|
316
|
+
suggestion="Set mTLS mode to STRICT for all PeerAuthentication policies",
|
|
317
|
+
control_id="MESH-01",
|
|
318
|
+
)
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
# Check for sidecar injection in Deployment/Service
|
|
322
|
+
if crd_name in ("VirtualService", "DestinationRule"):
|
|
323
|
+
for pattern, desc in SIDECAR_PATTERNS:
|
|
324
|
+
if re.search(pattern, content):
|
|
325
|
+
if "disabled" in desc.lower() or "false" in desc.lower():
|
|
326
|
+
findings.append(
|
|
327
|
+
make_finding(
|
|
328
|
+
Severity.MEDIUM,
|
|
329
|
+
rel,
|
|
330
|
+
0,
|
|
331
|
+
"MESH-03: Sidecar injection disabled",
|
|
332
|
+
desc,
|
|
333
|
+
suggestion="Enable sidecar injection for mesh security benefits",
|
|
334
|
+
control_id="MESH-03",
|
|
335
|
+
)
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
# Check for Linkerd CRDs
|
|
339
|
+
for crd_name, patterns in LINKERD_CRDS.items():
|
|
340
|
+
if re.search(patterns["kind"], content):
|
|
341
|
+
found_crds.setdefault(crd_name, []).append(rel)
|
|
342
|
+
findings.append(
|
|
343
|
+
make_finding(
|
|
344
|
+
Severity.INFO,
|
|
345
|
+
rel,
|
|
346
|
+
0,
|
|
347
|
+
f"MESH-07: Linkerd CRD detected: {crd_name}",
|
|
348
|
+
f"Found {crd_name} custom resource. Verify security configuration.",
|
|
349
|
+
control_id="MESH-07",
|
|
350
|
+
)
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
# Check for default-deny patterns
|
|
354
|
+
for pattern, desc in DEFAULT_DENY_PATTERNS:
|
|
355
|
+
if re.search(pattern, content, re.DOTALL):
|
|
356
|
+
if "DENY" in desc:
|
|
357
|
+
findings.append(
|
|
358
|
+
make_finding(
|
|
359
|
+
Severity.INFO,
|
|
360
|
+
rel,
|
|
361
|
+
0,
|
|
362
|
+
f"MESH-05: {desc}",
|
|
363
|
+
desc,
|
|
364
|
+
control_id="MESH-05",
|
|
365
|
+
)
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
def _scan_helm_values(self, fpath: Path, root: Path, findings: list) -> None:
|
|
369
|
+
"""Scan Helm values files for mesh configuration."""
|
|
370
|
+
try:
|
|
371
|
+
content = fpath.read_text(encoding="utf-8", errors="replace")
|
|
372
|
+
except Exception as e:
|
|
373
|
+
_log.warning("ServiceMeshAgent._scan_helm_values failed: %s", e)
|
|
374
|
+
return
|
|
375
|
+
|
|
376
|
+
rel = str(fpath.relative_to(root))
|
|
377
|
+
|
|
378
|
+
# Check for Istio Helm values
|
|
379
|
+
if re.search(r"istio|mesh|sidecar", content, re.IGNORECASE):
|
|
380
|
+
# Check mTLS settings
|
|
381
|
+
mtls_match = re.search(r"mtls:\s*\n\s*(?:enabled|mode):\s*(\w+)", content)
|
|
382
|
+
if mtls_match:
|
|
383
|
+
mode = mtls_match.group(1)
|
|
384
|
+
if mode.lower() not in ("strict", "true"):
|
|
385
|
+
findings.append(
|
|
386
|
+
make_finding(
|
|
387
|
+
Severity.MEDIUM,
|
|
388
|
+
rel,
|
|
389
|
+
0,
|
|
390
|
+
"MESH-01: mTLS not in strict mode in Helm values",
|
|
391
|
+
f"Helm values specify mTLS mode={mode}. Use STRICT for production.",
|
|
392
|
+
suggestion="Set mtls.enabled=true or mtls.mode=STRICT",
|
|
393
|
+
control_id="MESH-01",
|
|
394
|
+
)
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
# Check sidecar injection
|
|
398
|
+
injection_match = re.search(
|
|
399
|
+
r"sidecar.*inject(?:ion)?:\s*\n\s*enabled:\s*(\w+)", content
|
|
400
|
+
)
|
|
401
|
+
if injection_match and injection_match.group(1).lower() == "false":
|
|
402
|
+
findings.append(
|
|
403
|
+
make_finding(
|
|
404
|
+
Severity.MEDIUM,
|
|
405
|
+
rel,
|
|
406
|
+
0,
|
|
407
|
+
"MESH-03: Sidecar injection disabled in Helm values",
|
|
408
|
+
"Helm values have sidecar injection disabled. Services won't get mesh security.",
|
|
409
|
+
suggestion="Enable sidecar injection in Helm values",
|
|
410
|
+
control_id="MESH-03",
|
|
411
|
+
)
|
|
412
|
+
)
|