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,638 @@
|
|
|
1
|
+
"""Kubernetes cluster hardening agent — validates manifests against CIS Benchmarks."""
|
|
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
|
+
# Kubernetes manifest detection
|
|
27
|
+
K8S_KINDS = {
|
|
28
|
+
"Pod",
|
|
29
|
+
"Deployment",
|
|
30
|
+
"StatefulSet",
|
|
31
|
+
"DaemonSet",
|
|
32
|
+
"ReplicaSet",
|
|
33
|
+
"Service",
|
|
34
|
+
"ConfigMap",
|
|
35
|
+
"Secret",
|
|
36
|
+
"ServiceAccount",
|
|
37
|
+
"ClusterRole",
|
|
38
|
+
"ClusterRoleBinding",
|
|
39
|
+
"Role",
|
|
40
|
+
"RoleBinding",
|
|
41
|
+
"NetworkPolicy",
|
|
42
|
+
"Ingress",
|
|
43
|
+
"PersistentVolumeClaim",
|
|
44
|
+
"PersistentVolume",
|
|
45
|
+
"Namespace",
|
|
46
|
+
"LimitRange",
|
|
47
|
+
"ResourceQuota",
|
|
48
|
+
"HorizontalPodAutoscaler",
|
|
49
|
+
"Job",
|
|
50
|
+
"CronJob",
|
|
51
|
+
"CustomResourceDefinition",
|
|
52
|
+
"MutatingWebhookConfiguration",
|
|
53
|
+
"ValidatingWebhookConfiguration",
|
|
54
|
+
"PodDisruptionBudget",
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
# CIS Benchmark patterns
|
|
58
|
+
CIS_PATTERNS = {
|
|
59
|
+
"anonymous_auth": {
|
|
60
|
+
"pattern": r"anonymous-auth:\s*true|anonymous.*auth.*true",
|
|
61
|
+
"control": "K8S-01",
|
|
62
|
+
"title": "Anonymous authentication enabled",
|
|
63
|
+
"severity": Severity.HIGH,
|
|
64
|
+
"description": "Anonymous authentication is enabled on the API server. Disable it.",
|
|
65
|
+
"suggestion": "Set --anonymous-auth=false on kube-apiserver",
|
|
66
|
+
},
|
|
67
|
+
"insecure_port": {
|
|
68
|
+
"pattern": r"insecure-port:\s*[1-9]\d*|insecure.bind-address",
|
|
69
|
+
"control": "K8S-02",
|
|
70
|
+
"title": "Insecure port enabled",
|
|
71
|
+
"severity": Severity.CRITICAL,
|
|
72
|
+
"description": "Insecure port is configured. API server traffic may be unencrypted.",
|
|
73
|
+
"suggestion": "Set --insecure-port=0 to disable insecure port",
|
|
74
|
+
},
|
|
75
|
+
"audit_log": {
|
|
76
|
+
"pattern": r"audit-log-path|audit-policy|audit.log|audit-file",
|
|
77
|
+
"control": "K8S-03",
|
|
78
|
+
"title": "Audit logging configured",
|
|
79
|
+
"severity": Severity.INFO,
|
|
80
|
+
"description": "Audit logging configuration detected.",
|
|
81
|
+
},
|
|
82
|
+
"tls_cert": {
|
|
83
|
+
"pattern": r"tls-cert-file|tls-private-key|cert-file|key-file",
|
|
84
|
+
"control": "K8S-04",
|
|
85
|
+
"title": "TLS configured",
|
|
86
|
+
"severity": Severity.INFO,
|
|
87
|
+
"description": "TLS certificate configuration detected.",
|
|
88
|
+
},
|
|
89
|
+
"etcd_encryption": {
|
|
90
|
+
"pattern": r"encryption-provider-config|encryption-provider|etcd-encryption",
|
|
91
|
+
"control": "K8S-05",
|
|
92
|
+
"title": "etcd encryption configured",
|
|
93
|
+
"severity": Severity.INFO,
|
|
94
|
+
"description": "etcd encryption at rest configuration detected.",
|
|
95
|
+
},
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
# Pod security patterns
|
|
99
|
+
POD_SECURITY_PATTERNS = {
|
|
100
|
+
"privileged": {
|
|
101
|
+
"pattern": r"privileged:\s*true",
|
|
102
|
+
"control": "K8S-06",
|
|
103
|
+
"title": "Privileged container",
|
|
104
|
+
"severity": Severity.CRITICAL,
|
|
105
|
+
"description": "Container runs in privileged mode with full host access.",
|
|
106
|
+
"suggestion": "Remove privileged: true and use specific capabilities instead",
|
|
107
|
+
"cwe": "CWE-250",
|
|
108
|
+
},
|
|
109
|
+
"host_path": {
|
|
110
|
+
"pattern": r"hostPath:",
|
|
111
|
+
"control": "K8S-07",
|
|
112
|
+
"title": "hostPath volume mounted",
|
|
113
|
+
"severity": Severity.HIGH,
|
|
114
|
+
"description": "Container mounts host filesystem via hostPath. May expose sensitive host data.",
|
|
115
|
+
"suggestion": "Use PersistentVolumeClaims or emptyDir instead of hostPath",
|
|
116
|
+
"cwe": "CWE-284",
|
|
117
|
+
},
|
|
118
|
+
"host_network": {
|
|
119
|
+
"pattern": r"hostNetwork:\s*true",
|
|
120
|
+
"control": "K8S-08",
|
|
121
|
+
"title": "Host network namespace",
|
|
122
|
+
"severity": Severity.HIGH,
|
|
123
|
+
"description": "Container uses host network namespace. May intercept host traffic.",
|
|
124
|
+
"suggestion": "Remove hostNetwork: true unless strictly required",
|
|
125
|
+
"cwe": "CWE-668",
|
|
126
|
+
},
|
|
127
|
+
"host_pid": {
|
|
128
|
+
"pattern": r"hostPID:\s*true",
|
|
129
|
+
"control": "K8S-09",
|
|
130
|
+
"title": "Host PID namespace",
|
|
131
|
+
"severity": Severity.HIGH,
|
|
132
|
+
"description": "Container shares host PID namespace. Can see all host processes.",
|
|
133
|
+
"suggestion": "Remove hostPID: true unless required for debugging",
|
|
134
|
+
"cwe": "CWE-693",
|
|
135
|
+
},
|
|
136
|
+
"run_as_root": {
|
|
137
|
+
"pattern": r"runAsNonRoot:\s*false|securityContext:\s*\n\s*runAsUser:\s*0",
|
|
138
|
+
"control": "K8S-10",
|
|
139
|
+
"title": "Container runs as root",
|
|
140
|
+
"severity": Severity.HIGH,
|
|
141
|
+
"description": "Container runs as root user. Compromise may lead to host root access.",
|
|
142
|
+
"suggestion": "Set runAsNonRoot: true and runAsUser: non-zero",
|
|
143
|
+
"cwe": "CWE-250",
|
|
144
|
+
},
|
|
145
|
+
"capabilities_add": {
|
|
146
|
+
"pattern": r"add:\s*\n\s*-\s*ALL|capabilities:\s*\n\s*add:",
|
|
147
|
+
"control": "K8S-11",
|
|
148
|
+
"title": "Excessive Linux capabilities",
|
|
149
|
+
"severity": Severity.HIGH,
|
|
150
|
+
"description": "Container adds Linux capabilities. ALL capabilities is equivalent to privileged.",
|
|
151
|
+
"suggestion": "Add only the minimum required capabilities and drop ALL by default",
|
|
152
|
+
"cwe": "CWE-250",
|
|
153
|
+
},
|
|
154
|
+
"read_only_rootfs": {
|
|
155
|
+
"pattern": r"readOnlyRootFilesystem:\s*false",
|
|
156
|
+
"control": "K8S-12",
|
|
157
|
+
"title": "Writable root filesystem",
|
|
158
|
+
"severity": Severity.MEDIUM,
|
|
159
|
+
"description": "Container root filesystem is writable. Malware may persist.",
|
|
160
|
+
"suggestion": "Set readOnlyRootFilesystem: true and use volumes for writable paths",
|
|
161
|
+
},
|
|
162
|
+
"default_namespace": {
|
|
163
|
+
"pattern": r"namespace:\s*default|metadata:\s*\n\s*name:\s*\w+",
|
|
164
|
+
"control": "K8S-13",
|
|
165
|
+
"title": "Default namespace usage",
|
|
166
|
+
"severity": Severity.LOW,
|
|
167
|
+
"description": "Resource uses the default namespace. Production workloads should use dedicated namespaces.",
|
|
168
|
+
"suggestion": "Use a dedicated namespace for production workloads",
|
|
169
|
+
},
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
# RBAC patterns
|
|
173
|
+
RBAC_PATTERNS = {
|
|
174
|
+
"wildcard_verbs": {
|
|
175
|
+
"pattern": r"verbs:\s*\n\s*-\s*\*|verbs:\s*\[\s*\"\*\"\s*\]",
|
|
176
|
+
"control": "K8S-14",
|
|
177
|
+
"title": "RBAC wildcard verbs",
|
|
178
|
+
"severity": Severity.HIGH,
|
|
179
|
+
"description": "RBAC rule grants wildcard verbs. Service account has excessive permissions.",
|
|
180
|
+
"suggestion": "Specify only required verbs (get, list, watch, create, update, patch, delete)",
|
|
181
|
+
"cwe": "CWE-269",
|
|
182
|
+
},
|
|
183
|
+
"wildcard_resources": {
|
|
184
|
+
"pattern": r"resources:\s*\n\s*-\s*\*|resources:\s*\[\s*\"\*\"\s*\]",
|
|
185
|
+
"control": "K8S-15",
|
|
186
|
+
"title": "RBAC wildcard resources",
|
|
187
|
+
"severity": Severity.HIGH,
|
|
188
|
+
"description": "RBAC rule grants access to all resources. Principle of least privilege violated.",
|
|
189
|
+
"suggestion": "Specify only the required resources",
|
|
190
|
+
"cwe": "CWE-269",
|
|
191
|
+
},
|
|
192
|
+
"cluster_admin": {
|
|
193
|
+
"pattern": r"cluster-admin|ClusterRoleBinding.*cluster-admin|roleRef:\s*\n\s*name:\s*cluster-admin",
|
|
194
|
+
"control": "K8S-16",
|
|
195
|
+
"title": "cluster-admin binding",
|
|
196
|
+
"severity": Severity.HIGH,
|
|
197
|
+
"description": "Binding to cluster-admin grants full cluster access.",
|
|
198
|
+
"suggestion": "Create a custom Role/ClusterRole with only required permissions",
|
|
199
|
+
"cwe": "CWE-269",
|
|
200
|
+
},
|
|
201
|
+
"service_account_token": {
|
|
202
|
+
"pattern": r"automountServiceAccountToken:\s*true|automount.*token.*true",
|
|
203
|
+
"control": "K8S-17",
|
|
204
|
+
"title": "Service account token auto-mounted",
|
|
205
|
+
"severity": Severity.MEDIUM,
|
|
206
|
+
"description": "Service account token is automatically mounted. Pods may access API server.",
|
|
207
|
+
"suggestion": "Set automountServiceAccountToken: false if API access is not needed",
|
|
208
|
+
"cwe": "CWE-200",
|
|
209
|
+
},
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
# Network policy patterns
|
|
213
|
+
NETWORK_POLICY_PATTERNS = {
|
|
214
|
+
"no_network_policy": {
|
|
215
|
+
"pattern": r"kind:\s*NetworkPolicy",
|
|
216
|
+
"control": "K8S-18",
|
|
217
|
+
"title": "NetworkPolicy found",
|
|
218
|
+
"severity": Severity.INFO,
|
|
219
|
+
"description": "NetworkPolicy resource detected.",
|
|
220
|
+
},
|
|
221
|
+
"ingress_allow_all": {
|
|
222
|
+
"pattern": r"ingress:\s*\n\s*-\s*\{\s*\}|ingress:\s*\n\s*-\s*from:\s*\n\s*-\s*\{\s*\}",
|
|
223
|
+
"control": "K8S-19",
|
|
224
|
+
"title": "NetworkPolicy allows all ingress",
|
|
225
|
+
"severity": Severity.MEDIUM,
|
|
226
|
+
"description": "NetworkPolicy allows ingress from all sources. Network segmentation not enforced.",
|
|
227
|
+
"suggestion": "Restrict ingress to specific pods/namespaces",
|
|
228
|
+
},
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
CONTROL_IDS = {
|
|
232
|
+
"K8S-01": ("Anonymous authentication enabled", Severity.HIGH),
|
|
233
|
+
"K8S-02": ("Insecure port enabled", Severity.CRITICAL),
|
|
234
|
+
"K8S-03": ("Audit logging", Severity.INFO),
|
|
235
|
+
"K8S-04": ("TLS configured", Severity.INFO),
|
|
236
|
+
"K8S-05": ("etcd encryption", Severity.INFO),
|
|
237
|
+
"K8S-06": ("Privileged container", Severity.CRITICAL),
|
|
238
|
+
"K8S-07": ("hostPath volume", Severity.HIGH),
|
|
239
|
+
"K8S-08": ("Host network", Severity.HIGH),
|
|
240
|
+
"K8S-09": ("Host PID", Severity.HIGH),
|
|
241
|
+
"K8S-10": ("Container runs as root", Severity.HIGH),
|
|
242
|
+
"K8S-11": ("Excessive capabilities", Severity.HIGH),
|
|
243
|
+
"K8S-12": ("Writable root filesystem", Severity.MEDIUM),
|
|
244
|
+
"K8S-13": ("Default namespace", Severity.LOW),
|
|
245
|
+
"K8S-14": ("RBAC wildcard verbs", Severity.HIGH),
|
|
246
|
+
"K8S-15": ("RBAC wildcard resources", Severity.HIGH),
|
|
247
|
+
"K8S-16": ("cluster-admin binding", Severity.HIGH),
|
|
248
|
+
"K8S-17": ("Service account token auto-mounted", Severity.MEDIUM),
|
|
249
|
+
"K8S-18": ("NetworkPolicy found", Severity.INFO),
|
|
250
|
+
"K8S-19": ("NetworkPolicy allows all ingress", Severity.MEDIUM),
|
|
251
|
+
"K8S-20": ("Missing network policy", Severity.MEDIUM),
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
_log = logging.getLogger("patchi.security.kubernetes_agent")
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
@register
|
|
259
|
+
class KubernetesAgent(BaseAgent):
|
|
260
|
+
name = "KubernetesAgent"
|
|
261
|
+
group = AgentGroup.SECURITY
|
|
262
|
+
domain = AgentDomain.SECURITY
|
|
263
|
+
description = (
|
|
264
|
+
"Kubernetes cluster hardening: CIS Benchmark controls, RBAC, pod security, network policies"
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
def _run(self, inp: AgentInput, result: AgentResult) -> None:
|
|
268
|
+
with trace_agent(self.name, inp.root) as trace:
|
|
269
|
+
findings: list[Finding] = []
|
|
270
|
+
files_scanned = 0
|
|
271
|
+
k8s_manifests = 0
|
|
272
|
+
namespaces_seen: set[str] = set()
|
|
273
|
+
has_network_policy = False
|
|
274
|
+
|
|
275
|
+
# Phase 1: Scan YAML files for Kubernetes manifests
|
|
276
|
+
for fpath in safe_rglob(inp.root, "*.yaml"):
|
|
277
|
+
files_scanned += 1
|
|
278
|
+
self._scan_yaml(fpath, inp.root, findings, namespaces_seen, "has_network_policy")
|
|
279
|
+
for fpath in safe_rglob(inp.root, "*.yml"):
|
|
280
|
+
files_scanned += 1
|
|
281
|
+
self._scan_yaml(fpath, inp.root, findings, namespaces_seen, "has_network_policy")
|
|
282
|
+
|
|
283
|
+
# Check if any NetworkPolicy was found
|
|
284
|
+
has_network_policy = any(f.message.startswith("K8S-18:") for f in findings)
|
|
285
|
+
|
|
286
|
+
# Phase 2: Scan Helm charts
|
|
287
|
+
for fpath in safe_rglob(inp.root, "Chart.yaml"):
|
|
288
|
+
files_scanned += 1
|
|
289
|
+
self._scan_helm_chart(fpath, inp.root, findings)
|
|
290
|
+
|
|
291
|
+
# Phase 3: Scan Dockerfiles for security issues
|
|
292
|
+
for fpath in safe_rglob(inp.root, "Dockerfile*"):
|
|
293
|
+
files_scanned += 1
|
|
294
|
+
self._scan_dockerfile(fpath, inp.root, findings)
|
|
295
|
+
|
|
296
|
+
# Phase 4: Aggregate analysis
|
|
297
|
+
# Check for missing network policies
|
|
298
|
+
if namespaces_seen and not has_network_policy:
|
|
299
|
+
findings.append(
|
|
300
|
+
make_finding(
|
|
301
|
+
Severity.MEDIUM,
|
|
302
|
+
"",
|
|
303
|
+
0,
|
|
304
|
+
"K8S-20: No NetworkPolicy found",
|
|
305
|
+
f"Found {len(namespaces_seen)} namespace(s) but no NetworkPolicy. Network segmentation not enforced.",
|
|
306
|
+
suggestion="Add NetworkPolicy to restrict pod-to-pod communication",
|
|
307
|
+
control_id="K8S-20",
|
|
308
|
+
)
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
# Phase 5: Try external tools
|
|
312
|
+
# Try kube-bench (CIS Benchmark scanner)
|
|
313
|
+
kube_bench_path = shutil.which("kube-bench")
|
|
314
|
+
if kube_bench_path:
|
|
315
|
+
try:
|
|
316
|
+
proc = subprocess.run(
|
|
317
|
+
[kube_bench_path, "run", "--json", "--targets", "master,node,policies"],
|
|
318
|
+
capture_output=True,
|
|
319
|
+
text=True,
|
|
320
|
+
timeout=120,
|
|
321
|
+
)
|
|
322
|
+
if proc.stdout:
|
|
323
|
+
import json
|
|
324
|
+
|
|
325
|
+
try:
|
|
326
|
+
data = json.loads(proc.stdout)
|
|
327
|
+
for check in data.get("tests", []):
|
|
328
|
+
for result_item in check.get("results", []):
|
|
329
|
+
if result_item.get("status") == "FAIL":
|
|
330
|
+
findings.append(
|
|
331
|
+
make_finding(
|
|
332
|
+
Severity.HIGH,
|
|
333
|
+
"",
|
|
334
|
+
0,
|
|
335
|
+
f"kube-bench: {result_item.get('test_desc', 'Unknown')}",
|
|
336
|
+
f"CIS Benchmark check failed: {result_item.get('test_desc', '')}",
|
|
337
|
+
suggestion="Fix kube-bench finding per CIS Kubernetes Benchmark",
|
|
338
|
+
)
|
|
339
|
+
)
|
|
340
|
+
except json.JSONDecodeError:
|
|
341
|
+
pass
|
|
342
|
+
except (subprocess.TimeoutExpired, Exception):
|
|
343
|
+
pass
|
|
344
|
+
|
|
345
|
+
# Try kubectl for cluster state
|
|
346
|
+
kubectl_path = shutil.which("kubectl")
|
|
347
|
+
if kubectl_path:
|
|
348
|
+
try:
|
|
349
|
+
# Check for RBAC
|
|
350
|
+
proc = subprocess.run(
|
|
351
|
+
[kubectl_path, "get", "clusterrolebindings", "-o", "json"],
|
|
352
|
+
capture_output=True,
|
|
353
|
+
text=True,
|
|
354
|
+
timeout=30,
|
|
355
|
+
)
|
|
356
|
+
if proc.returncode == 0 and proc.stdout:
|
|
357
|
+
import json
|
|
358
|
+
|
|
359
|
+
try:
|
|
360
|
+
data = json.loads(proc.stdout)
|
|
361
|
+
for item in data.get("items", []):
|
|
362
|
+
role_ref = item.get("roleRef", {})
|
|
363
|
+
if role_ref.get("name") == "cluster-admin":
|
|
364
|
+
subjects = item.get("subjects", [])
|
|
365
|
+
for subject in subjects:
|
|
366
|
+
findings.append(
|
|
367
|
+
make_finding(
|
|
368
|
+
Severity.HIGH,
|
|
369
|
+
"",
|
|
370
|
+
0,
|
|
371
|
+
"K8S-16: cluster-admin binding via kubectl",
|
|
372
|
+
f"cluster-admin bound to: {subject.get('name', 'unknown')} ({subject.get('kind', 'unknown')})",
|
|
373
|
+
suggestion="Review and remove unnecessary cluster-admin bindings",
|
|
374
|
+
cwe="CWE-269",
|
|
375
|
+
control_id="K8S-16",
|
|
376
|
+
)
|
|
377
|
+
)
|
|
378
|
+
except json.JSONDecodeError:
|
|
379
|
+
pass
|
|
380
|
+
except (subprocess.TimeoutExpired, Exception):
|
|
381
|
+
pass
|
|
382
|
+
|
|
383
|
+
# Try kube-linter
|
|
384
|
+
kube_linter_path = shutil.which("kube-linter")
|
|
385
|
+
if kube_linter_path:
|
|
386
|
+
try:
|
|
387
|
+
proc = subprocess.run(
|
|
388
|
+
[kube_linter_path, "lint", str(inp.root), "--format", "json"],
|
|
389
|
+
capture_output=True,
|
|
390
|
+
text=True,
|
|
391
|
+
timeout=60,
|
|
392
|
+
)
|
|
393
|
+
if proc.stdout:
|
|
394
|
+
import json
|
|
395
|
+
|
|
396
|
+
try:
|
|
397
|
+
data = json.loads(proc.stdout)
|
|
398
|
+
for item in data.get("objects", []):
|
|
399
|
+
for diag in item.get("diagnostics", []):
|
|
400
|
+
check = diag.get("check", "")
|
|
401
|
+
if any(
|
|
402
|
+
kw in check.lower()
|
|
403
|
+
for kw in [
|
|
404
|
+
"privileged",
|
|
405
|
+
"hostpath",
|
|
406
|
+
"network",
|
|
407
|
+
"rbac",
|
|
408
|
+
"capability",
|
|
409
|
+
]
|
|
410
|
+
):
|
|
411
|
+
findings.append(
|
|
412
|
+
make_finding(
|
|
413
|
+
Severity.MEDIUM,
|
|
414
|
+
item.get("metadata", {}).get("filePath", ""),
|
|
415
|
+
0,
|
|
416
|
+
f"kube-linter: {check}",
|
|
417
|
+
diag.get("message", ""),
|
|
418
|
+
suggestion="Review kube-linter finding for Kubernetes security impact",
|
|
419
|
+
)
|
|
420
|
+
)
|
|
421
|
+
except json.JSONDecodeError:
|
|
422
|
+
pass
|
|
423
|
+
except (subprocess.TimeoutExpired, Exception):
|
|
424
|
+
pass
|
|
425
|
+
|
|
426
|
+
result.findings = findings
|
|
427
|
+
result.files_scanned = files_scanned
|
|
428
|
+
result.data = {
|
|
429
|
+
"k8s_manifests": k8s_manifests,
|
|
430
|
+
"namespaces_seen": list(namespaces_seen),
|
|
431
|
+
"has_network_policy": has_network_policy,
|
|
432
|
+
}
|
|
433
|
+
result.status = AgentStatus.DONE
|
|
434
|
+
trace.findings = len(findings)
|
|
435
|
+
trace.files_scanned = files_scanned
|
|
436
|
+
|
|
437
|
+
return
|
|
438
|
+
|
|
439
|
+
def _scan_yaml(
|
|
440
|
+
self,
|
|
441
|
+
fpath: Path,
|
|
442
|
+
root: Path,
|
|
443
|
+
findings: list,
|
|
444
|
+
namespaces_seen: set,
|
|
445
|
+
network_policy_flag: str,
|
|
446
|
+
) -> None:
|
|
447
|
+
"""Scan a YAML/YML file for Kubernetes manifests."""
|
|
448
|
+
try:
|
|
449
|
+
content = fpath.read_text(encoding="utf-8", errors="replace")
|
|
450
|
+
except Exception as e:
|
|
451
|
+
_log.warning("KubernetesAgent._scan_yaml failed: %s", e)
|
|
452
|
+
return
|
|
453
|
+
|
|
454
|
+
# Quick check: is this a Kubernetes manifest?
|
|
455
|
+
if not re.search(r"apiVersion:\s*", content):
|
|
456
|
+
return
|
|
457
|
+
|
|
458
|
+
rel = str(fpath.relative_to(root))
|
|
459
|
+
kind_match = re.search(r"kind:\s*(\w+)", content)
|
|
460
|
+
if not kind_match:
|
|
461
|
+
return
|
|
462
|
+
|
|
463
|
+
kind = kind_match.group(1)
|
|
464
|
+
if kind not in K8S_KINDS:
|
|
465
|
+
return
|
|
466
|
+
|
|
467
|
+
# Extract namespace if present
|
|
468
|
+
ns_match = re.search(r"namespace:\s*(\S+)", content)
|
|
469
|
+
if ns_match:
|
|
470
|
+
namespaces_seen.add(ns_match.group(1))
|
|
471
|
+
|
|
472
|
+
# CIS Benchmark checks for kube-apiserver/kubelet configs
|
|
473
|
+
for _check_id, check_info in CIS_PATTERNS.items():
|
|
474
|
+
if re.search(check_info["pattern"], content, re.IGNORECASE):
|
|
475
|
+
findings.append(
|
|
476
|
+
make_finding(
|
|
477
|
+
check_info["severity"],
|
|
478
|
+
rel,
|
|
479
|
+
0,
|
|
480
|
+
f"{check_info['control']}: {check_info['title']}",
|
|
481
|
+
check_info["description"],
|
|
482
|
+
suggestion=check_info.get("suggestion", ""),
|
|
483
|
+
control_id=check_info["control"],
|
|
484
|
+
)
|
|
485
|
+
)
|
|
486
|
+
|
|
487
|
+
# Pod security checks
|
|
488
|
+
for _check_id, check_info in POD_SECURITY_PATTERNS.items():
|
|
489
|
+
if re.search(check_info["pattern"], content, re.IGNORECASE):
|
|
490
|
+
findings.append(
|
|
491
|
+
make_finding(
|
|
492
|
+
check_info["severity"],
|
|
493
|
+
rel,
|
|
494
|
+
0,
|
|
495
|
+
f"{check_info['control']}: {check_info['title']}",
|
|
496
|
+
check_info["description"],
|
|
497
|
+
suggestion=check_info.get("suggestion", ""),
|
|
498
|
+
cwe=check_info.get("cwe", ""),
|
|
499
|
+
control_id=check_info["control"],
|
|
500
|
+
)
|
|
501
|
+
)
|
|
502
|
+
|
|
503
|
+
# RBAC checks
|
|
504
|
+
for _check_id, check_info in RBAC_PATTERNS.items():
|
|
505
|
+
if re.search(check_info["pattern"], content, re.IGNORECASE):
|
|
506
|
+
findings.append(
|
|
507
|
+
make_finding(
|
|
508
|
+
check_info["severity"],
|
|
509
|
+
rel,
|
|
510
|
+
0,
|
|
511
|
+
f"{check_info['control']}: {check_info['title']}",
|
|
512
|
+
check_info["description"],
|
|
513
|
+
suggestion=check_info.get("suggestion", ""),
|
|
514
|
+
cwe=check_info.get("cwe", ""),
|
|
515
|
+
control_id=check_info["control"],
|
|
516
|
+
)
|
|
517
|
+
)
|
|
518
|
+
|
|
519
|
+
# Network policy checks
|
|
520
|
+
for _check_id, check_info in NETWORK_POLICY_PATTERNS.items():
|
|
521
|
+
if re.search(check_info["pattern"], content, re.IGNORECASE):
|
|
522
|
+
findings.append(
|
|
523
|
+
make_finding(
|
|
524
|
+
check_info["severity"],
|
|
525
|
+
rel,
|
|
526
|
+
0,
|
|
527
|
+
f"{check_info['control']}: {check_info['title']}",
|
|
528
|
+
check_info["description"],
|
|
529
|
+
suggestion=check_info.get("suggestion", ""),
|
|
530
|
+
control_id=check_info["control"],
|
|
531
|
+
)
|
|
532
|
+
)
|
|
533
|
+
|
|
534
|
+
# Check for missing securityContext
|
|
535
|
+
if kind in ("Pod", "Deployment", "StatefulSet", "DaemonSet", "Job", "CronJob"):
|
|
536
|
+
if "securityContext" not in content and kind in ("Pod",):
|
|
537
|
+
findings.append(
|
|
538
|
+
make_finding(
|
|
539
|
+
Severity.MEDIUM,
|
|
540
|
+
rel,
|
|
541
|
+
0,
|
|
542
|
+
"K8S-06: No securityContext defined",
|
|
543
|
+
"Pod does not define securityContext. Default settings may be permissive.",
|
|
544
|
+
suggestion="Add securityContext with runAsNonRoot, readOnlyRootFilesystem, and drop ALL capabilities",
|
|
545
|
+
control_id="K8S-06",
|
|
546
|
+
)
|
|
547
|
+
)
|
|
548
|
+
|
|
549
|
+
# Check for missing resource limits
|
|
550
|
+
if kind in ("Pod", "Deployment", "StatefulSet", "DaemonSet", "Job", "CronJob"):
|
|
551
|
+
if "resources:" not in content:
|
|
552
|
+
findings.append(
|
|
553
|
+
make_finding(
|
|
554
|
+
Severity.LOW,
|
|
555
|
+
rel,
|
|
556
|
+
0,
|
|
557
|
+
"K8S-12: No resource limits defined",
|
|
558
|
+
"Container does not define resource limits. May consume unlimited cluster resources.",
|
|
559
|
+
suggestion="Add resource limits (cpu, memory) to prevent resource exhaustion",
|
|
560
|
+
)
|
|
561
|
+
)
|
|
562
|
+
|
|
563
|
+
def _scan_helm_chart(self, fpath: Path, root: Path, findings: list) -> None:
|
|
564
|
+
"""Scan Helm chart metadata."""
|
|
565
|
+
try:
|
|
566
|
+
content = fpath.read_text(encoding="utf-8", errors="replace")
|
|
567
|
+
except Exception as e:
|
|
568
|
+
_log.warning("KubernetesAgent._scan_helm_chart failed: %s", e)
|
|
569
|
+
return
|
|
570
|
+
|
|
571
|
+
rel = str(fpath.relative_to(root))
|
|
572
|
+
|
|
573
|
+
# Check for deprecated API versions
|
|
574
|
+
api_version = re.search(r"apiVersion:\s*(\S+)", content)
|
|
575
|
+
if api_version and api_version.group(1) == "v1":
|
|
576
|
+
findings.append(
|
|
577
|
+
make_finding(
|
|
578
|
+
Severity.LOW,
|
|
579
|
+
rel,
|
|
580
|
+
0,
|
|
581
|
+
"K8S-13: Deprecated Helm chart API version",
|
|
582
|
+
"Helm chart uses apiVersion: v1 (deprecated). Upgrade to v2.",
|
|
583
|
+
suggestion="Migrate to Helm v3 (apiVersion: v2)",
|
|
584
|
+
)
|
|
585
|
+
)
|
|
586
|
+
|
|
587
|
+
def _scan_dockerfile(self, fpath: Path, root: Path, findings: list) -> None:
|
|
588
|
+
"""Scan Dockerfile for Kubernetes-relevant security issues."""
|
|
589
|
+
try:
|
|
590
|
+
content = fpath.read_text(encoding="utf-8", errors="replace")
|
|
591
|
+
except Exception as e:
|
|
592
|
+
_log.warning("KubernetesAgent._scan_dockerfile failed: %s", e)
|
|
593
|
+
return
|
|
594
|
+
|
|
595
|
+
rel = str(fpath.relative_to(root))
|
|
596
|
+
|
|
597
|
+
# Check for running as root
|
|
598
|
+
if not re.search(r"USER\s+\w+", content):
|
|
599
|
+
findings.append(
|
|
600
|
+
make_finding(
|
|
601
|
+
Severity.HIGH,
|
|
602
|
+
rel,
|
|
603
|
+
0,
|
|
604
|
+
"K8S-10: Dockerfile runs as root",
|
|
605
|
+
"Dockerfile does not set a non-root USER. Container will run as root.",
|
|
606
|
+
suggestion="Add 'USER nonroot' before the ENTRYPOINT/CMD",
|
|
607
|
+
cwe="CWE-250",
|
|
608
|
+
control_id="K8S-10",
|
|
609
|
+
)
|
|
610
|
+
)
|
|
611
|
+
|
|
612
|
+
# Check for ADD instead of COPY
|
|
613
|
+
if re.search(r"^ADD\s+", content, re.MULTILINE):
|
|
614
|
+
findings.append(
|
|
615
|
+
make_finding(
|
|
616
|
+
Severity.LOW,
|
|
617
|
+
rel,
|
|
618
|
+
0,
|
|
619
|
+
"K8S-12: Dockerfile uses ADD instead of COPY",
|
|
620
|
+
"ADD instruction may unpack archives or fetch URLs. COPY is more predictable.",
|
|
621
|
+
suggestion="Use COPY unless ADD functionality is specifically needed",
|
|
622
|
+
)
|
|
623
|
+
)
|
|
624
|
+
|
|
625
|
+
# Check for apt-get without cleanup
|
|
626
|
+
if re.search(r"apt-get\s+install", content) and not re.search(
|
|
627
|
+
r"apt-get\s+clean|rm\s+-rf\s+/var/lib/apt", content
|
|
628
|
+
):
|
|
629
|
+
findings.append(
|
|
630
|
+
make_finding(
|
|
631
|
+
Severity.LOW,
|
|
632
|
+
rel,
|
|
633
|
+
0,
|
|
634
|
+
"K8S-12: apt-get install without cleanup",
|
|
635
|
+
"Package manager cache not cleaned. Increases image size and may contain vulnerabilities.",
|
|
636
|
+
suggestion="Add 'RUN apt-get clean && rm -rf /var/lib/apt/lists/*' after install",
|
|
637
|
+
)
|
|
638
|
+
)
|