@jaguilar87/gaia-ops 4.4.0 → 4.7.2
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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +12 -3
- package/ARCHITECTURE.md +9 -8
- package/CHANGELOG.md +34 -0
- package/README.md +43 -11
- package/agents/terraform-architect.md +1 -1
- package/bin/README.md +2 -2
- package/bin/gaia-doctor.js +18 -5
- package/bin/gaia-history.js +0 -1
- package/bin/gaia-metrics.js +2 -2
- package/bin/gaia-scan.py +23 -1
- package/bin/gaia-update.js +346 -54
- package/bin/pre-publish-validate.js +33 -10
- package/commands/gaia.md +37 -0
- package/config/README.md +3 -9
- package/config/context-contracts.json +47 -15
- package/config/surface-routing.json +9 -1
- package/dist/gaia-ops/.claude-plugin/plugin.json +22 -0
- package/dist/gaia-ops/agents/cloud-troubleshooter.md +73 -0
- package/dist/gaia-ops/agents/devops-developer.md +57 -0
- package/dist/gaia-ops/agents/gaia-system.md +58 -0
- package/dist/gaia-ops/agents/gitops-operator.md +60 -0
- package/dist/gaia-ops/agents/speckit-planner.md +71 -0
- package/dist/gaia-ops/agents/terraform-architect.md +60 -0
- package/dist/gaia-ops/commands/gaia.md +37 -0
- package/dist/gaia-ops/config/README.md +58 -0
- package/dist/gaia-ops/config/cloud/aws.json +140 -0
- package/dist/gaia-ops/config/cloud/gcp.json +145 -0
- package/dist/gaia-ops/config/context-contracts.json +131 -0
- package/dist/gaia-ops/config/git_standards.json +72 -0
- package/dist/gaia-ops/config/surface-routing.json +197 -0
- package/dist/gaia-ops/config/universal-rules.json +10 -0
- package/dist/gaia-ops/hooks/adapters/__init__.py +52 -0
- package/dist/gaia-ops/hooks/adapters/base.py +219 -0
- package/dist/gaia-ops/hooks/adapters/channel.py +17 -0
- package/dist/gaia-ops/hooks/adapters/claude_code.py +1477 -0
- package/dist/gaia-ops/hooks/adapters/types.py +194 -0
- package/dist/gaia-ops/hooks/adapters/utils.py +25 -0
- package/dist/gaia-ops/hooks/hooks.json +126 -0
- package/dist/gaia-ops/hooks/modules/__init__.py +15 -0
- package/dist/gaia-ops/hooks/modules/agents/__init__.py +29 -0
- package/dist/gaia-ops/hooks/modules/agents/contract_validator.py +647 -0
- package/dist/gaia-ops/hooks/modules/agents/response_contract.py +496 -0
- package/dist/gaia-ops/hooks/modules/agents/skill_injection_verifier.py +124 -0
- package/dist/gaia-ops/hooks/modules/agents/task_info_builder.py +74 -0
- package/dist/gaia-ops/hooks/modules/agents/transcript_analyzer.py +458 -0
- package/dist/gaia-ops/hooks/modules/agents/transcript_reader.py +152 -0
- package/dist/gaia-ops/hooks/modules/audit/__init__.py +28 -0
- package/dist/gaia-ops/hooks/modules/audit/event_detector.py +168 -0
- package/dist/gaia-ops/hooks/modules/audit/logger.py +131 -0
- package/dist/gaia-ops/hooks/modules/audit/metrics.py +134 -0
- package/dist/gaia-ops/hooks/modules/audit/workflow_auditor.py +576 -0
- package/dist/gaia-ops/hooks/modules/audit/workflow_recorder.py +296 -0
- package/dist/gaia-ops/hooks/modules/context/__init__.py +11 -0
- package/dist/gaia-ops/hooks/modules/context/anchor_tracker.py +317 -0
- package/dist/gaia-ops/hooks/modules/context/compact_context_builder.py +215 -0
- package/dist/gaia-ops/hooks/modules/context/context_cache.py +129 -0
- package/dist/gaia-ops/hooks/modules/context/context_freshness.py +145 -0
- package/dist/gaia-ops/hooks/modules/context/context_injector.py +427 -0
- package/dist/gaia-ops/hooks/modules/context/context_writer.py +518 -0
- package/dist/gaia-ops/hooks/modules/context/contracts_loader.py +161 -0
- package/dist/gaia-ops/hooks/modules/core/__init__.py +40 -0
- package/dist/gaia-ops/hooks/modules/core/hook_entry.py +78 -0
- package/dist/gaia-ops/hooks/modules/core/paths.py +160 -0
- package/dist/gaia-ops/hooks/modules/core/plugin_mode.py +149 -0
- package/dist/gaia-ops/hooks/modules/core/plugin_setup.py +558 -0
- package/dist/gaia-ops/hooks/modules/core/state.py +179 -0
- package/dist/gaia-ops/hooks/modules/core/stdin.py +24 -0
- package/dist/gaia-ops/hooks/modules/events/__init__.py +1 -0
- package/dist/gaia-ops/hooks/modules/events/event_writer.py +210 -0
- package/dist/gaia-ops/hooks/modules/identity/__init__.py +0 -0
- package/dist/gaia-ops/hooks/modules/identity/identity_provider.py +21 -0
- package/dist/gaia-ops/hooks/modules/identity/ops_identity.py +34 -0
- package/dist/gaia-ops/hooks/modules/identity/security_identity.py +10 -0
- package/dist/gaia-ops/hooks/modules/memory/__init__.py +8 -0
- package/dist/gaia-ops/hooks/modules/memory/episode_writer.py +227 -0
- package/dist/gaia-ops/hooks/modules/orchestrator/__init__.py +1 -0
- package/dist/gaia-ops/hooks/modules/orchestrator/delegate_mode.py +128 -0
- package/dist/gaia-ops/hooks/modules/scanning/__init__.py +8 -0
- package/dist/gaia-ops/hooks/modules/scanning/scan_trigger.py +84 -0
- package/dist/gaia-ops/hooks/modules/security/__init__.py +89 -0
- package/dist/gaia-ops/hooks/modules/security/approval_cleanup.py +87 -0
- package/dist/gaia-ops/hooks/modules/security/approval_constants.py +23 -0
- package/dist/gaia-ops/hooks/modules/security/approval_grants.py +912 -0
- package/dist/gaia-ops/hooks/modules/security/approval_messages.py +71 -0
- package/dist/gaia-ops/hooks/modules/security/approval_scopes.py +153 -0
- package/dist/gaia-ops/hooks/modules/security/blocked_commands.py +584 -0
- package/dist/gaia-ops/hooks/modules/security/blocked_message_formatter.py +86 -0
- package/dist/gaia-ops/hooks/modules/security/command_semantics.py +130 -0
- package/dist/gaia-ops/hooks/modules/security/gitops_validator.py +179 -0
- package/dist/gaia-ops/hooks/modules/security/mutative_verbs.py +850 -0
- package/dist/gaia-ops/hooks/modules/security/prompt_validator.py +40 -0
- package/dist/gaia-ops/hooks/modules/security/tiers.py +196 -0
- package/dist/gaia-ops/hooks/modules/session/__init__.py +10 -0
- package/dist/gaia-ops/hooks/modules/session/session_context_writer.py +100 -0
- package/dist/gaia-ops/hooks/modules/session/session_event_injector.py +158 -0
- package/dist/gaia-ops/hooks/modules/session/session_manager.py +31 -0
- package/dist/gaia-ops/hooks/modules/tools/__init__.py +25 -0
- package/dist/gaia-ops/hooks/modules/tools/bash_validator.py +708 -0
- package/dist/gaia-ops/hooks/modules/tools/cloud_pipe_validator.py +181 -0
- package/dist/gaia-ops/hooks/modules/tools/hook_response.py +55 -0
- package/dist/gaia-ops/hooks/modules/tools/shell_parser.py +227 -0
- package/dist/gaia-ops/hooks/modules/tools/task_validator.py +283 -0
- package/dist/gaia-ops/hooks/modules/validation/__init__.py +23 -0
- package/dist/gaia-ops/hooks/modules/validation/commit_validator.py +380 -0
- package/dist/gaia-ops/hooks/post_compact.py +43 -0
- package/dist/gaia-ops/hooks/post_tool_use.py +54 -0
- package/dist/gaia-ops/hooks/pre_tool_use.py +383 -0
- package/dist/gaia-ops/hooks/session_start.py +69 -0
- package/dist/gaia-ops/hooks/stop_hook.py +69 -0
- package/dist/gaia-ops/hooks/subagent_start.py +71 -0
- package/dist/gaia-ops/hooks/subagent_stop.py +288 -0
- package/dist/gaia-ops/hooks/task_completed.py +70 -0
- package/dist/gaia-ops/hooks/user_prompt_submit.py +177 -0
- package/dist/gaia-ops/settings.json +72 -0
- package/dist/gaia-ops/skills/README.md +109 -0
- package/dist/gaia-ops/skills/agent-protocol/SKILL.md +105 -0
- package/dist/gaia-ops/skills/agent-protocol/examples.md +170 -0
- package/dist/gaia-ops/skills/agent-response/SKILL.md +53 -0
- package/dist/gaia-ops/skills/approval/SKILL.md +85 -0
- package/dist/gaia-ops/skills/approval/examples.md +140 -0
- package/dist/gaia-ops/skills/approval/reference.md +57 -0
- package/dist/gaia-ops/skills/command-execution/SKILL.md +64 -0
- package/dist/gaia-ops/skills/command-execution/reference.md +83 -0
- package/dist/gaia-ops/skills/context-updater/SKILL.md +76 -0
- package/dist/gaia-ops/skills/context-updater/examples.md +71 -0
- package/dist/gaia-ops/skills/developer-patterns/SKILL.md +93 -0
- package/dist/gaia-ops/skills/developer-patterns/reference.md +112 -0
- package/dist/gaia-ops/skills/execution/SKILL.md +66 -0
- package/dist/gaia-ops/skills/fast-queries/SKILL.md +47 -0
- package/dist/gaia-ops/skills/gaia-patterns/SKILL.md +92 -0
- package/dist/gaia-ops/skills/gaia-patterns/reference.md +22 -0
- package/dist/gaia-ops/skills/git-conventions/SKILL.md +48 -0
- package/dist/gaia-ops/skills/gitops-patterns/SKILL.md +73 -0
- package/dist/gaia-ops/skills/gitops-patterns/reference.md +183 -0
- package/dist/gaia-ops/skills/investigation/SKILL.md +77 -0
- package/dist/gaia-ops/skills/orchestrator-approval/SKILL.md +64 -0
- package/dist/gaia-ops/skills/reference.md +134 -0
- package/dist/gaia-ops/skills/security-tiers/SKILL.md +61 -0
- package/dist/gaia-ops/skills/security-tiers/destructive-commands-reference.md +623 -0
- package/dist/gaia-ops/skills/security-tiers/reference.md +39 -0
- package/dist/gaia-ops/skills/skill-creation/SKILL.md +119 -0
- package/dist/gaia-ops/skills/specification/SKILL.md +186 -0
- package/dist/gaia-ops/skills/speckit-workflow/SKILL.md +165 -0
- package/dist/gaia-ops/skills/speckit-workflow/reference.md +117 -0
- package/dist/gaia-ops/skills/terraform-patterns/SKILL.md +63 -0
- package/dist/gaia-ops/skills/terraform-patterns/reference.md +93 -0
- package/dist/gaia-ops/speckit/README.md +516 -0
- package/dist/gaia-ops/speckit/scripts/.gitkeep +0 -0
- package/dist/gaia-ops/speckit/templates/adr-template.md +118 -0
- package/dist/gaia-ops/speckit/templates/agent-file-template.md +23 -0
- package/dist/gaia-ops/speckit/templates/plan-template.md +227 -0
- package/dist/gaia-ops/speckit/templates/spec-template.md +140 -0
- package/dist/gaia-ops/speckit/templates/tasks-template.md +257 -0
- package/dist/gaia-ops/tools/context/README.md +132 -0
- package/dist/gaia-ops/tools/context/__init__.py +42 -0
- package/dist/gaia-ops/tools/context/_paths.py +20 -0
- package/dist/gaia-ops/tools/context/context_provider.py +476 -0
- package/dist/gaia-ops/tools/context/context_section_reader.py +330 -0
- package/dist/gaia-ops/tools/context/deep_merge.py +159 -0
- package/dist/gaia-ops/tools/context/pending_updates.py +760 -0
- package/dist/gaia-ops/tools/context/surface_router.py +278 -0
- package/dist/gaia-ops/tools/fast-queries/README.md +65 -0
- package/dist/gaia-ops/tools/fast-queries/__init__.py +30 -0
- package/dist/gaia-ops/tools/fast-queries/appservices/quicktriage_devops_developer.sh +75 -0
- package/dist/gaia-ops/tools/fast-queries/cloud/aws/quicktriage_aws_troubleshooter.sh +32 -0
- package/dist/gaia-ops/tools/fast-queries/cloud/gcp/quicktriage_gcp_troubleshooter.sh +88 -0
- package/dist/gaia-ops/tools/fast-queries/gitops/quicktriage_gitops_operator.sh +48 -0
- package/dist/gaia-ops/tools/fast-queries/run_triage.sh +59 -0
- package/dist/gaia-ops/tools/fast-queries/terraform/quicktriage_terraform_architect.sh +80 -0
- package/dist/gaia-ops/tools/gaia_simulator/__init__.py +33 -0
- package/dist/gaia-ops/tools/gaia_simulator/cli.py +354 -0
- package/dist/gaia-ops/tools/gaia_simulator/extractor.py +457 -0
- package/dist/gaia-ops/tools/gaia_simulator/reporter.py +258 -0
- package/dist/gaia-ops/tools/gaia_simulator/routing_simulator.py +334 -0
- package/dist/gaia-ops/tools/gaia_simulator/runner.py +539 -0
- package/dist/gaia-ops/tools/gaia_simulator/skills_mapper.py +262 -0
- package/dist/gaia-ops/tools/memory/README.md +0 -0
- package/dist/gaia-ops/tools/memory/__init__.py +20 -0
- package/dist/gaia-ops/tools/memory/episodic.py +1196 -0
- package/dist/gaia-ops/tools/persist_transcript_analysis.py +85 -0
- package/dist/gaia-ops/tools/review/__init__.py +1 -0
- package/dist/gaia-ops/tools/review/review_engine.py +157 -0
- package/dist/gaia-ops/tools/scan/__init__.py +35 -0
- package/dist/gaia-ops/tools/scan/config.py +247 -0
- package/dist/gaia-ops/tools/scan/merge.py +212 -0
- package/dist/gaia-ops/tools/scan/orchestrator.py +549 -0
- package/dist/gaia-ops/tools/scan/registry.py +127 -0
- package/dist/gaia-ops/tools/scan/scanners/__init__.py +18 -0
- package/dist/gaia-ops/tools/scan/scanners/base.py +137 -0
- package/dist/gaia-ops/tools/scan/scanners/environment.py +324 -0
- package/dist/gaia-ops/tools/scan/scanners/git.py +570 -0
- package/dist/gaia-ops/tools/scan/scanners/infrastructure.py +875 -0
- package/dist/gaia-ops/tools/scan/scanners/orchestration.py +600 -0
- package/dist/gaia-ops/tools/scan/scanners/stack.py +1085 -0
- package/dist/gaia-ops/tools/scan/scanners/tools.py +260 -0
- package/dist/gaia-ops/tools/scan/setup.py +753 -0
- package/dist/gaia-ops/tools/scan/tests/__init__.py +1 -0
- package/dist/gaia-ops/tools/scan/tests/conftest.py +796 -0
- package/dist/gaia-ops/tools/scan/tests/test_environment.py +323 -0
- package/dist/gaia-ops/tools/scan/tests/test_git.py +419 -0
- package/dist/gaia-ops/tools/scan/tests/test_infrastructure.py +382 -0
- package/dist/gaia-ops/tools/scan/tests/test_integration.py +920 -0
- package/dist/gaia-ops/tools/scan/tests/test_merge.py +269 -0
- package/dist/gaia-ops/tools/scan/tests/test_orchestration.py +304 -0
- package/dist/gaia-ops/tools/scan/tests/test_stack.py +604 -0
- package/dist/gaia-ops/tools/scan/tests/test_tools.py +349 -0
- package/dist/gaia-ops/tools/scan/ui.py +624 -0
- package/dist/gaia-ops/tools/scan/verify.py +266 -0
- package/dist/gaia-ops/tools/scan/walk.py +118 -0
- package/dist/gaia-ops/tools/scan/workspace.py +85 -0
- package/dist/gaia-ops/tools/validation/README.md +244 -0
- package/dist/gaia-ops/tools/validation/__init__.py +17 -0
- package/dist/gaia-ops/tools/validation/approval_gate.py +321 -0
- package/dist/gaia-ops/tools/validation/validate_skills.py +189 -0
- package/dist/gaia-security/.claude-plugin/plugin.json +22 -0
- package/dist/gaia-security/config/universal-rules.json +10 -0
- package/dist/gaia-security/hooks/adapters/__init__.py +52 -0
- package/dist/gaia-security/hooks/adapters/base.py +219 -0
- package/dist/gaia-security/hooks/adapters/channel.py +17 -0
- package/dist/gaia-security/hooks/adapters/claude_code.py +1477 -0
- package/dist/gaia-security/hooks/adapters/types.py +194 -0
- package/dist/gaia-security/hooks/adapters/utils.py +25 -0
- package/dist/gaia-security/hooks/hooks.json +57 -0
- package/dist/gaia-security/hooks/modules/__init__.py +15 -0
- package/dist/gaia-security/hooks/modules/agents/__init__.py +29 -0
- package/dist/gaia-security/hooks/modules/agents/contract_validator.py +647 -0
- package/dist/gaia-security/hooks/modules/agents/response_contract.py +496 -0
- package/dist/gaia-security/hooks/modules/agents/skill_injection_verifier.py +124 -0
- package/dist/gaia-security/hooks/modules/agents/task_info_builder.py +74 -0
- package/dist/gaia-security/hooks/modules/agents/transcript_analyzer.py +458 -0
- package/dist/gaia-security/hooks/modules/agents/transcript_reader.py +152 -0
- package/dist/gaia-security/hooks/modules/audit/__init__.py +28 -0
- package/dist/gaia-security/hooks/modules/audit/event_detector.py +168 -0
- package/dist/gaia-security/hooks/modules/audit/logger.py +131 -0
- package/dist/gaia-security/hooks/modules/audit/metrics.py +134 -0
- package/dist/gaia-security/hooks/modules/audit/workflow_auditor.py +576 -0
- package/dist/gaia-security/hooks/modules/audit/workflow_recorder.py +296 -0
- package/dist/gaia-security/hooks/modules/context/__init__.py +11 -0
- package/dist/gaia-security/hooks/modules/context/anchor_tracker.py +317 -0
- package/dist/gaia-security/hooks/modules/context/compact_context_builder.py +215 -0
- package/dist/gaia-security/hooks/modules/context/context_cache.py +129 -0
- package/dist/gaia-security/hooks/modules/context/context_freshness.py +145 -0
- package/dist/gaia-security/hooks/modules/context/context_injector.py +427 -0
- package/dist/gaia-security/hooks/modules/context/context_writer.py +518 -0
- package/dist/gaia-security/hooks/modules/context/contracts_loader.py +161 -0
- package/dist/gaia-security/hooks/modules/core/__init__.py +40 -0
- package/dist/gaia-security/hooks/modules/core/hook_entry.py +78 -0
- package/dist/gaia-security/hooks/modules/core/paths.py +160 -0
- package/dist/gaia-security/hooks/modules/core/plugin_mode.py +149 -0
- package/dist/gaia-security/hooks/modules/core/plugin_setup.py +558 -0
- package/dist/gaia-security/hooks/modules/core/state.py +179 -0
- package/dist/gaia-security/hooks/modules/core/stdin.py +24 -0
- package/dist/gaia-security/hooks/modules/events/__init__.py +1 -0
- package/dist/gaia-security/hooks/modules/events/event_writer.py +210 -0
- package/dist/gaia-security/hooks/modules/identity/__init__.py +0 -0
- package/dist/gaia-security/hooks/modules/identity/identity_provider.py +21 -0
- package/dist/gaia-security/hooks/modules/identity/ops_identity.py +34 -0
- package/dist/gaia-security/hooks/modules/identity/security_identity.py +10 -0
- package/dist/gaia-security/hooks/modules/memory/__init__.py +8 -0
- package/dist/gaia-security/hooks/modules/memory/episode_writer.py +227 -0
- package/dist/gaia-security/hooks/modules/orchestrator/__init__.py +1 -0
- package/dist/gaia-security/hooks/modules/orchestrator/delegate_mode.py +128 -0
- package/dist/gaia-security/hooks/modules/scanning/__init__.py +8 -0
- package/dist/gaia-security/hooks/modules/scanning/scan_trigger.py +84 -0
- package/dist/gaia-security/hooks/modules/security/__init__.py +89 -0
- package/dist/gaia-security/hooks/modules/security/approval_cleanup.py +87 -0
- package/dist/gaia-security/hooks/modules/security/approval_constants.py +23 -0
- package/dist/gaia-security/hooks/modules/security/approval_grants.py +912 -0
- package/dist/gaia-security/hooks/modules/security/approval_messages.py +71 -0
- package/dist/gaia-security/hooks/modules/security/approval_scopes.py +153 -0
- package/dist/gaia-security/hooks/modules/security/blocked_commands.py +584 -0
- package/dist/gaia-security/hooks/modules/security/blocked_message_formatter.py +86 -0
- package/dist/gaia-security/hooks/modules/security/command_semantics.py +130 -0
- package/dist/gaia-security/hooks/modules/security/gitops_validator.py +179 -0
- package/dist/gaia-security/hooks/modules/security/mutative_verbs.py +850 -0
- package/dist/gaia-security/hooks/modules/security/prompt_validator.py +40 -0
- package/dist/gaia-security/hooks/modules/security/tiers.py +196 -0
- package/dist/gaia-security/hooks/modules/session/__init__.py +10 -0
- package/dist/gaia-security/hooks/modules/session/session_context_writer.py +100 -0
- package/dist/gaia-security/hooks/modules/session/session_event_injector.py +158 -0
- package/dist/gaia-security/hooks/modules/session/session_manager.py +31 -0
- package/dist/gaia-security/hooks/modules/tools/__init__.py +25 -0
- package/dist/gaia-security/hooks/modules/tools/bash_validator.py +708 -0
- package/dist/gaia-security/hooks/modules/tools/cloud_pipe_validator.py +181 -0
- package/dist/gaia-security/hooks/modules/tools/hook_response.py +55 -0
- package/dist/gaia-security/hooks/modules/tools/shell_parser.py +227 -0
- package/dist/gaia-security/hooks/modules/tools/task_validator.py +283 -0
- package/dist/gaia-security/hooks/modules/validation/__init__.py +23 -0
- package/dist/gaia-security/hooks/modules/validation/commit_validator.py +380 -0
- package/dist/gaia-security/hooks/post_tool_use.py +54 -0
- package/dist/gaia-security/hooks/pre_tool_use.py +383 -0
- package/dist/gaia-security/hooks/session_start.py +69 -0
- package/dist/gaia-security/hooks/stop_hook.py +69 -0
- package/dist/gaia-security/hooks/user_prompt_submit.py +177 -0
- package/dist/gaia-security/settings.json +58 -0
- package/git-hooks/commit-msg +41 -0
- package/hooks/README.md +8 -6
- package/hooks/adapters/channel.py +0 -25
- package/hooks/adapters/claude_code.py +364 -125
- package/hooks/elicitation_result.py +132 -0
- package/hooks/hooks.json +10 -1
- package/hooks/modules/README.md +3 -2
- package/hooks/modules/agents/contract_validator.py +3 -51
- package/hooks/modules/agents/response_contract.py +4 -8
- package/hooks/modules/agents/transcript_reader.py +4 -5
- package/hooks/modules/audit/__init__.py +4 -6
- package/hooks/modules/audit/event_detector.py +0 -2
- package/hooks/modules/audit/metrics.py +108 -187
- package/hooks/modules/audit/workflow_auditor.py +0 -4
- package/hooks/modules/audit/workflow_recorder.py +0 -5
- package/hooks/modules/context/compact_context_builder.py +1 -0
- package/hooks/modules/context/context_cache.py +129 -0
- package/hooks/modules/context/context_injector.py +18 -40
- package/hooks/modules/context/context_writer.py +1 -25
- package/hooks/modules/context/contracts_loader.py +7 -10
- package/hooks/modules/core/hook_entry.py +1 -0
- package/hooks/modules/core/paths.py +12 -13
- package/hooks/modules/core/plugin_mode.py +74 -4
- package/hooks/modules/core/plugin_setup.py +395 -23
- package/hooks/modules/events/__init__.py +1 -0
- package/hooks/modules/events/event_writer.py +210 -0
- package/hooks/modules/identity/ops_identity.py +18 -27
- package/hooks/modules/memory/episode_writer.py +1 -6
- package/hooks/modules/orchestrator/__init__.py +1 -0
- package/hooks/modules/orchestrator/delegate_mode.py +128 -0
- package/hooks/modules/security/__init__.py +2 -4
- package/hooks/modules/security/approval_constants.py +5 -1
- package/hooks/modules/security/approval_grants.py +189 -6
- package/hooks/modules/security/approval_messages.py +9 -21
- package/hooks/modules/security/blocked_commands.py +98 -34
- package/hooks/modules/security/command_semantics.py +0 -4
- package/hooks/modules/security/gitops_validator.py +1 -11
- package/hooks/modules/security/mutative_verbs.py +179 -38
- package/hooks/modules/security/tiers.py +1 -19
- package/hooks/modules/session/session_event_injector.py +1 -25
- package/hooks/modules/tools/bash_validator.py +310 -94
- package/hooks/modules/tools/shell_parser.py +0 -1
- package/hooks/modules/tools/task_validator.py +9 -29
- package/hooks/post_tool_use.py +0 -72
- package/hooks/pre_tool_use.py +42 -102
- package/hooks/session_start.py +4 -2
- package/hooks/subagent_start.py +6 -2
- package/hooks/subagent_stop.py +1 -13
- package/hooks/user_prompt_submit.py +119 -37
- package/index.js +1 -1
- package/package.json +5 -3
- package/skills/README.md +3 -5
- package/skills/agent-protocol/SKILL.md +17 -16
- package/skills/agent-protocol/examples.md +6 -6
- package/skills/agent-response/SKILL.md +11 -14
- package/skills/approval/SKILL.md +28 -13
- package/skills/approval/reference.md +2 -2
- package/skills/execution/SKILL.md +1 -1
- package/skills/gaia-patterns/SKILL.md +2 -3
- package/skills/orchestrator-approval/SKILL.md +22 -50
- package/skills/security-tiers/SKILL.md +1 -1
- package/templates/README.md +9 -9
- package/templates/managed-settings.template.json +43 -0
- package/tools/gaia_simulator/runner.py +34 -1
- package/tools/scan/orchestrator.py +13 -0
- package/tools/scan/scanners/base.py +8 -0
- package/tools/scan/scanners/git.py +78 -0
- package/tools/scan/scanners/infrastructure.py +65 -0
- package/tools/scan/scanners/stack.py +110 -0
- package/tools/scan/setup.py +120 -13
- package/tools/scan/workspace.py +85 -0
- package/config/context-contracts.aws.json +0 -42
- package/config/context-contracts.gcp.json +0 -39
- package/skills/project-dispatch/SKILL.md +0 -34
- package/templates/settings.template.json +0 -226
|
@@ -46,7 +46,6 @@ from ..core.paths import find_claude_dir, get_plugin_data_dir
|
|
|
46
46
|
from ..core.state import get_session_id
|
|
47
47
|
from .approval_scopes import (
|
|
48
48
|
ApprovalSignature,
|
|
49
|
-
SCOPE_EXACT_COMMAND,
|
|
50
49
|
SCOPE_SEMANTIC_SIGNATURE,
|
|
51
50
|
SUPPORTED_SCOPE_TYPES,
|
|
52
51
|
build_approval_signature,
|
|
@@ -56,7 +55,7 @@ from .approval_scopes import (
|
|
|
56
55
|
logger = logging.getLogger(__name__)
|
|
57
56
|
|
|
58
57
|
# Default grant TTL in minutes
|
|
59
|
-
DEFAULT_GRANT_TTL_MINUTES =
|
|
58
|
+
DEFAULT_GRANT_TTL_MINUTES = 5
|
|
60
59
|
|
|
61
60
|
# Cleanup throttle: only run cleanup if 60+ seconds since last run
|
|
62
61
|
_last_cleanup_time: float = 0.0
|
|
@@ -524,7 +523,7 @@ def activate_pending_approval(
|
|
|
524
523
|
reason="Unexpected error while activating approval.",
|
|
525
524
|
)
|
|
526
525
|
|
|
527
|
-
def check_approval_grant(command: str) -> Optional[ApprovalGrant]:
|
|
526
|
+
def check_approval_grant(command: str, session_id: str = None) -> Optional[ApprovalGrant]:
|
|
528
527
|
"""Check if there is an active approval grant for a command.
|
|
529
528
|
|
|
530
529
|
Called by the bash_validator before blocking a dangerous command.
|
|
@@ -533,6 +532,7 @@ def check_approval_grant(command: str) -> Optional[ApprovalGrant]:
|
|
|
533
532
|
|
|
534
533
|
Args:
|
|
535
534
|
command: The shell command to check.
|
|
535
|
+
session_id: Session ID for grant scoping (defaults to env var).
|
|
536
536
|
|
|
537
537
|
Returns:
|
|
538
538
|
The matching ApprovalGrant if found and valid, None otherwise.
|
|
@@ -540,7 +540,8 @@ def check_approval_grant(command: str) -> Optional[ApprovalGrant]:
|
|
|
540
540
|
global _last_check_found_expired
|
|
541
541
|
_last_check_found_expired = False
|
|
542
542
|
|
|
543
|
-
|
|
543
|
+
if not session_id:
|
|
544
|
+
session_id = _get_session_id()
|
|
544
545
|
|
|
545
546
|
try:
|
|
546
547
|
grants_dir = _get_grants_dir()
|
|
@@ -587,7 +588,60 @@ def check_approval_grant(command: str) -> Optional[ApprovalGrant]:
|
|
|
587
588
|
return None
|
|
588
589
|
|
|
589
590
|
|
|
590
|
-
def
|
|
591
|
+
def consume_grant(command: str, session_id: str = None) -> bool:
|
|
592
|
+
"""Mark the first matching valid grant as used and persist to disk.
|
|
593
|
+
|
|
594
|
+
Called by bash_validator immediately after check_approval_grant() returns
|
|
595
|
+
a match, so that the grant can only be used once (single-use).
|
|
596
|
+
|
|
597
|
+
Args:
|
|
598
|
+
command: The shell command whose grant should be consumed.
|
|
599
|
+
session_id: Session ID for grant scoping (defaults to env var).
|
|
600
|
+
|
|
601
|
+
Returns:
|
|
602
|
+
True if a grant was found and consumed, False otherwise.
|
|
603
|
+
"""
|
|
604
|
+
if not session_id:
|
|
605
|
+
session_id = _get_session_id()
|
|
606
|
+
|
|
607
|
+
try:
|
|
608
|
+
grants_dir = _get_grants_dir()
|
|
609
|
+
if not grants_dir.exists():
|
|
610
|
+
return False
|
|
611
|
+
|
|
612
|
+
for grant_file in sorted(grants_dir.glob(f"grant-{session_id}-*.json")):
|
|
613
|
+
try:
|
|
614
|
+
data = json.loads(grant_file.read_text())
|
|
615
|
+
grant = ApprovalGrant(**data)
|
|
616
|
+
|
|
617
|
+
if not grant.is_valid():
|
|
618
|
+
if grant.is_expired():
|
|
619
|
+
_cleanup_grant(grant_file)
|
|
620
|
+
continue
|
|
621
|
+
|
|
622
|
+
signature = grant.get_signature()
|
|
623
|
+
if signature is None or signature.scope_type not in SUPPORTED_SCOPE_TYPES:
|
|
624
|
+
continue
|
|
625
|
+
|
|
626
|
+
if grant.matches_command(command):
|
|
627
|
+
data["used"] = True
|
|
628
|
+
grant_file.write_text(json.dumps(data, indent=2))
|
|
629
|
+
logger.info(
|
|
630
|
+
"Grant consumed (single-use): command='%s', grant=%s",
|
|
631
|
+
command[:80], grant_file.name,
|
|
632
|
+
)
|
|
633
|
+
return True
|
|
634
|
+
|
|
635
|
+
except (json.JSONDecodeError, TypeError):
|
|
636
|
+
continue
|
|
637
|
+
|
|
638
|
+
except Exception as e:
|
|
639
|
+
logger.error("Error consuming grant: %s", e)
|
|
640
|
+
|
|
641
|
+
return False
|
|
642
|
+
|
|
643
|
+
|
|
644
|
+
def confirm_grant(command: str, session_id: str = None) -> bool:
|
|
591
645
|
"""Mark the first unconfirmed grant matching command as confirmed.
|
|
592
646
|
|
|
593
647
|
Called after the native permission dialog accepts the first T3 execution.
|
|
@@ -596,11 +650,13 @@ def confirm_grant(command: str) -> bool:
|
|
|
596
650
|
|
|
597
651
|
Args:
|
|
598
652
|
command: The shell command whose grant should be confirmed.
|
|
653
|
+
session_id: Session ID for grant scoping (defaults to env var).
|
|
599
654
|
|
|
600
655
|
Returns:
|
|
601
656
|
True if a grant was found and confirmed, False otherwise.
|
|
602
657
|
"""
|
|
603
|
-
|
|
658
|
+
if not session_id:
|
|
659
|
+
session_id = _get_session_id()
|
|
604
660
|
|
|
605
661
|
try:
|
|
606
662
|
grants_dir = _get_grants_dir()
|
|
@@ -721,6 +777,133 @@ def cleanup_expired_grants() -> int:
|
|
|
721
777
|
return cleaned
|
|
722
778
|
|
|
723
779
|
|
|
780
|
+
def get_pending_approvals_for_session(
|
|
781
|
+
session_id: Optional[str] = None,
|
|
782
|
+
) -> List[Dict[str, Any]]:
|
|
783
|
+
"""Return all non-expired pending approvals for a session.
|
|
784
|
+
|
|
785
|
+
Args:
|
|
786
|
+
session_id: Session ID to filter by (defaults to current session).
|
|
787
|
+
|
|
788
|
+
Returns:
|
|
789
|
+
List of pending approval dicts, newest first.
|
|
790
|
+
"""
|
|
791
|
+
if session_id is None:
|
|
792
|
+
session_id = _get_session_id()
|
|
793
|
+
|
|
794
|
+
results: List[Dict[str, Any]] = []
|
|
795
|
+
try:
|
|
796
|
+
grants_dir = _get_grants_dir()
|
|
797
|
+
for pending_file in grants_dir.glob("pending-*.json"):
|
|
798
|
+
if pending_file.name.startswith("pending-index-"):
|
|
799
|
+
continue
|
|
800
|
+
data = _read_json_file(pending_file)
|
|
801
|
+
if not data or data.get("session_id") != session_id:
|
|
802
|
+
continue
|
|
803
|
+
timestamp = data.get("timestamp", 0)
|
|
804
|
+
ttl = data.get("ttl_minutes", DEFAULT_GRANT_TTL_MINUTES)
|
|
805
|
+
if _is_ttl_expired(float(timestamp), int(ttl)):
|
|
806
|
+
continue
|
|
807
|
+
results.append(data)
|
|
808
|
+
except Exception as e:
|
|
809
|
+
logger.error("Error listing pending approvals for session %s: %s", session_id, e)
|
|
810
|
+
|
|
811
|
+
results.sort(key=lambda d: d.get("timestamp", 0), reverse=True)
|
|
812
|
+
return results
|
|
813
|
+
|
|
814
|
+
|
|
815
|
+
def find_pending_for_command(
|
|
816
|
+
session_id: str,
|
|
817
|
+
command: str,
|
|
818
|
+
) -> Optional[str]:
|
|
819
|
+
"""Find an existing pending approval nonce for this command and session.
|
|
820
|
+
|
|
821
|
+
When a subagent retries a blocked T3 command, a pending approval may
|
|
822
|
+
already exist from the first attempt. Reusing the existing nonce
|
|
823
|
+
prevents the infinite-loop of generating a new approval_id on every
|
|
824
|
+
retry while the user is still reviewing the first one.
|
|
825
|
+
|
|
826
|
+
Args:
|
|
827
|
+
session_id: Session to search.
|
|
828
|
+
command: The command to match against pending approvals.
|
|
829
|
+
|
|
830
|
+
Returns:
|
|
831
|
+
The nonce (approval_id) if a matching pending approval exists, else None.
|
|
832
|
+
"""
|
|
833
|
+
pending_list = get_pending_approvals_for_session(session_id)
|
|
834
|
+
if not pending_list:
|
|
835
|
+
return None
|
|
836
|
+
|
|
837
|
+
# Build a signature for the incoming command to compare semantically
|
|
838
|
+
target_sig = build_approval_signature(
|
|
839
|
+
command,
|
|
840
|
+
scope_type=SCOPE_SEMANTIC_SIGNATURE,
|
|
841
|
+
)
|
|
842
|
+
if target_sig is None:
|
|
843
|
+
return None
|
|
844
|
+
|
|
845
|
+
for pending_data in pending_list:
|
|
846
|
+
pending_sig_data = pending_data.get("scope_signature")
|
|
847
|
+
if not pending_sig_data:
|
|
848
|
+
continue
|
|
849
|
+
try:
|
|
850
|
+
pending_sig = ApprovalSignature.from_dict(pending_sig_data)
|
|
851
|
+
if matches_approval_signature(pending_sig, command):
|
|
852
|
+
nonce = pending_data.get("nonce")
|
|
853
|
+
if nonce:
|
|
854
|
+
logger.info(
|
|
855
|
+
"Reusing existing pending approval nonce=%s for command: %s",
|
|
856
|
+
nonce, command[:80],
|
|
857
|
+
)
|
|
858
|
+
return nonce
|
|
859
|
+
except Exception:
|
|
860
|
+
continue
|
|
861
|
+
|
|
862
|
+
return None
|
|
863
|
+
|
|
864
|
+
|
|
865
|
+
def activate_grants_for_session(
|
|
866
|
+
session_id: Optional[str] = None,
|
|
867
|
+
ttl_minutes: int = DEFAULT_GRANT_TTL_MINUTES,
|
|
868
|
+
) -> List[ApprovalActivationResult]:
|
|
869
|
+
"""Activate ALL pending approvals for a session.
|
|
870
|
+
|
|
871
|
+
Called by the ElicitationResult hook when the user approves via
|
|
872
|
+
AskUserQuestion. Converts every non-expired pending approval for the
|
|
873
|
+
session into an active grant.
|
|
874
|
+
|
|
875
|
+
Args:
|
|
876
|
+
session_id: Session to activate for (defaults to current session).
|
|
877
|
+
ttl_minutes: TTL for the resulting active grants.
|
|
878
|
+
|
|
879
|
+
Returns:
|
|
880
|
+
List of activation results (one per pending approval).
|
|
881
|
+
"""
|
|
882
|
+
if session_id is None:
|
|
883
|
+
session_id = _get_session_id()
|
|
884
|
+
|
|
885
|
+
pending_list = get_pending_approvals_for_session(session_id)
|
|
886
|
+
results: List[ApprovalActivationResult] = []
|
|
887
|
+
|
|
888
|
+
for pending_data in pending_list:
|
|
889
|
+
nonce = pending_data.get("nonce", "")
|
|
890
|
+
if not nonce:
|
|
891
|
+
continue
|
|
892
|
+
result = activate_pending_approval(
|
|
893
|
+
nonce=nonce,
|
|
894
|
+
session_id=session_id,
|
|
895
|
+
ttl_minutes=ttl_minutes,
|
|
896
|
+
)
|
|
897
|
+
results.append(result)
|
|
898
|
+
logger.info(
|
|
899
|
+
"Session-wide activation: nonce=%s status=%s",
|
|
900
|
+
nonce,
|
|
901
|
+
getattr(result.status, "value", str(result.status)),
|
|
902
|
+
)
|
|
903
|
+
|
|
904
|
+
return results
|
|
905
|
+
|
|
906
|
+
|
|
724
907
|
def _cleanup_grant(grant_file: Path) -> None:
|
|
725
908
|
"""Remove a single grant or pending file."""
|
|
726
909
|
try:
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
"""Canonical approval/resume text used by hooks, skills, and tests."""
|
|
2
|
+
from __future__ import annotations
|
|
2
3
|
|
|
3
4
|
from .approval_constants import NONCE_APPROVAL_PREFIX
|
|
4
5
|
|
|
5
6
|
CANONICAL_APPROVAL_TOKEN = "APPROVE:<nonce>"
|
|
6
7
|
CANONICAL_APPROVAL_TOKEN_FORMAT = f"{NONCE_APPROVAL_PREFIX}<32-char-hex>"
|
|
7
8
|
LATEST_BLOCKED_COMMAND_PHRASE = "latest blocked command"
|
|
8
|
-
AWAITING_APPROVAL_STATUS = "AWAITING_APPROVAL"
|
|
9
|
-
|
|
10
9
|
CANONICAL_APPROVAL_TOKEN_GUIDANCE = (
|
|
11
10
|
f"Use only {CANONICAL_APPROVAL_TOKEN} from the {LATEST_BLOCKED_COMMAND_PHRASE}."
|
|
12
11
|
)
|
|
@@ -59,25 +58,14 @@ def build_pending_approval_unavailable_message() -> str:
|
|
|
59
58
|
|
|
60
59
|
|
|
61
60
|
def build_t3_approval_instructions(nonce: str | None = None) -> str:
|
|
62
|
-
"""Return
|
|
63
|
-
if nonce:
|
|
64
|
-
step_two = f"2. Include the approval code NONCE:{nonce} in your {AWAITING_APPROVAL_STATUS} output.\n"
|
|
65
|
-
step_four = (
|
|
66
|
-
f"4. Wait for explicit user approval. When resumed, expect APPROVE:{nonce} "
|
|
67
|
-
f"and then retry the command. {CANONICAL_APPROVAL_TOKEN_GUIDANCE}\n"
|
|
68
|
-
)
|
|
69
|
-
else:
|
|
70
|
-
step_two = f"2. Retry the blocked command if you need a fresh approval code for {AWAITING_APPROVAL_STATUS}.\n"
|
|
71
|
-
step_four = (
|
|
72
|
-
f"4. Wait for explicit user approval before executing. {CANONICAL_APPROVAL_TOKEN_GUIDANCE}\n"
|
|
73
|
-
)
|
|
61
|
+
"""Return T3 approval block data.
|
|
74
62
|
|
|
63
|
+
Kept minimal: just the facts (tier, nonce). Workflow instructions
|
|
64
|
+
live in skills (approval, orchestrator-approval, security-tiers) so
|
|
65
|
+
the hook doesn't duplicate or conflict with them.
|
|
66
|
+
"""
|
|
67
|
+
nonce_line = f"NONCE:{nonce}" if nonce else "NONCE:unavailable (retry command to generate)"
|
|
75
68
|
return (
|
|
76
|
-
"
|
|
77
|
-
"
|
|
78
|
-
f"{step_two}"
|
|
79
|
-
f"3. Set PLAN_STATUS: {AWAITING_APPROVAL_STATUS}.\n"
|
|
80
|
-
f"{step_four}"
|
|
81
|
-
"5. Include an `approval_request` object in your json:contract with:\n"
|
|
82
|
-
" operation, exact_content, scope, risk_level, rollback, verification\n"
|
|
69
|
+
f"[T3_APPROVAL_REQUIRED] {nonce_line}\n"
|
|
70
|
+
"Load the approval skill for next steps."
|
|
83
71
|
)
|
|
@@ -2,14 +2,15 @@
|
|
|
2
2
|
Blocked command patterns - PERMANENTLY BLOCKED operations (exit 2, never approvable).
|
|
3
3
|
|
|
4
4
|
This is the single source of truth for DESTRUCTIVE commands. Commands matched here
|
|
5
|
-
are blocked with exit 2 and
|
|
5
|
+
are blocked with exit 2 and cannot be approved.
|
|
6
6
|
|
|
7
7
|
All other state-modifying commands are detected by the universal verb detector
|
|
8
|
-
(mutative_verbs.py) as MUTATIVE and routed through the
|
|
8
|
+
(mutative_verbs.py) as MUTATIVE and routed through the user approval workflow.
|
|
9
9
|
|
|
10
10
|
Categories:
|
|
11
11
|
- AWS networking/data infrastructure delete operations
|
|
12
12
|
- AWS KMS/Route53/Organizations operations
|
|
13
|
+
- Azure resource group/networking/data/AKS/Key Vault delete operations
|
|
13
14
|
- GCP project/cluster/database delete operations
|
|
14
15
|
- Kubernetes critical delete operations (cluster, namespace, pv, node, CRD, webhooks)
|
|
15
16
|
- Kubernetes bulk delete operations (--all flag)
|
|
@@ -127,6 +128,43 @@ BLOCKED_PATTERNS: Dict[str, List[re.Pattern]] = {
|
|
|
127
128
|
re.compile(r"aws\s+route53\s+delete-hosted-zone\b", re.IGNORECASE),
|
|
128
129
|
],
|
|
129
130
|
|
|
131
|
+
# Azure - Resource group, networking, data infrastructure (irreversible)
|
|
132
|
+
"azure_critical": [
|
|
133
|
+
re.compile(r"az\s+group\s+delete\b", re.IGNORECASE),
|
|
134
|
+
re.compile(r"az\s+network\s+vnet\s+delete\b", re.IGNORECASE),
|
|
135
|
+
re.compile(r"az\s+network\s+vnet\s+subnet\s+delete\b", re.IGNORECASE),
|
|
136
|
+
re.compile(r"az\s+network\s+nsg\s+delete\b", re.IGNORECASE),
|
|
137
|
+
re.compile(r"az\s+network\s+public-ip\s+delete\b", re.IGNORECASE),
|
|
138
|
+
re.compile(r"az\s+network\s+application-gateway\s+delete\b", re.IGNORECASE),
|
|
139
|
+
re.compile(r"az\s+network\s+lb\s+delete\b", re.IGNORECASE),
|
|
140
|
+
re.compile(r"az\s+network\s+dns\s+zone\s+delete\b", re.IGNORECASE),
|
|
141
|
+
re.compile(r"az\s+network\s+private-dns\s+zone\s+delete\b", re.IGNORECASE),
|
|
142
|
+
re.compile(r"az\s+vm\s+delete\b", re.IGNORECASE),
|
|
143
|
+
re.compile(r"az\s+vmss\s+delete\b", re.IGNORECASE),
|
|
144
|
+
re.compile(r"az\s+disk\s+delete\b", re.IGNORECASE),
|
|
145
|
+
re.compile(r"az\s+snapshot\s+delete\b", re.IGNORECASE),
|
|
146
|
+
re.compile(r"az\s+image\s+delete\b", re.IGNORECASE),
|
|
147
|
+
re.compile(r"az\s+sql\s+server\s+delete\b", re.IGNORECASE),
|
|
148
|
+
re.compile(r"az\s+sql\s+db\s+delete\b", re.IGNORECASE),
|
|
149
|
+
re.compile(r"az\s+cosmosdb\s+delete\b", re.IGNORECASE),
|
|
150
|
+
re.compile(r"az\s+redis\s+delete\b", re.IGNORECASE),
|
|
151
|
+
re.compile(r"az\s+storage\s+account\s+delete\b", re.IGNORECASE),
|
|
152
|
+
re.compile(r"az\s+storage\s+container\s+delete\b", re.IGNORECASE),
|
|
153
|
+
re.compile(r"az\s+storage\s+blob\s+delete-batch\b", re.IGNORECASE),
|
|
154
|
+
re.compile(r"az\s+aks\s+delete\b", re.IGNORECASE),
|
|
155
|
+
re.compile(r"az\s+aks\s+nodepool\s+delete\b", re.IGNORECASE),
|
|
156
|
+
re.compile(r"az\s+acr\s+delete\b", re.IGNORECASE),
|
|
157
|
+
re.compile(r"az\s+keyvault\s+delete\b", re.IGNORECASE),
|
|
158
|
+
re.compile(r"az\s+keyvault\s+key\s+delete\b", re.IGNORECASE),
|
|
159
|
+
re.compile(r"az\s+keyvault\s+secret\s+delete\b", re.IGNORECASE),
|
|
160
|
+
re.compile(r"az\s+functionapp\s+delete\b", re.IGNORECASE),
|
|
161
|
+
re.compile(r"az\s+webapp\s+delete\b", re.IGNORECASE),
|
|
162
|
+
re.compile(r"az\s+ad\s+app\s+delete\b", re.IGNORECASE),
|
|
163
|
+
re.compile(r"az\s+ad\s+sp\s+delete\b", re.IGNORECASE),
|
|
164
|
+
re.compile(r"az\s+servicebus\s+namespace\s+delete\b", re.IGNORECASE),
|
|
165
|
+
re.compile(r"az\s+eventhubs\s+namespace\s+delete\b", re.IGNORECASE),
|
|
166
|
+
],
|
|
167
|
+
|
|
130
168
|
# GCP - Project, cluster, and database operations (irreversible)
|
|
131
169
|
"gcp_critical": [
|
|
132
170
|
re.compile(r"gcloud\s+projects\s+delete\b", re.IGNORECASE),
|
|
@@ -240,6 +278,29 @@ BLOCKED_COMMAND_SUGGESTIONS = {
|
|
|
240
278
|
"aws organizations delete-organization": "[BLOCKED] Organization deletion is irreversible",
|
|
241
279
|
"aws route53 delete-hosted-zone": "[BLOCKED] DNS zone deletion causes widespread outage",
|
|
242
280
|
|
|
281
|
+
# Azure suggestions
|
|
282
|
+
"az group delete": "[BLOCKED] Resource group deletion destroys all contained resources - use Terraform/Terragrunt",
|
|
283
|
+
"az network vnet delete": "[BLOCKED] VNet deletion is irreversible - use Terraform/Terragrunt",
|
|
284
|
+
"az network vnet subnet delete": "[BLOCKED] Subnet deletion is irreversible - use Terraform/Terragrunt",
|
|
285
|
+
"az network nsg delete": "[BLOCKED] NSG deletion removes all security rules - use Terraform/Terragrunt",
|
|
286
|
+
"az vm delete": "[BLOCKED] VM deletion is irreversible - use Terraform/Terragrunt",
|
|
287
|
+
"az vmss delete": "[BLOCKED] Scale set deletion is irreversible - use Terraform/Terragrunt",
|
|
288
|
+
"az disk delete": "[BLOCKED] Disk deletion loses all data - use Terraform/Terragrunt",
|
|
289
|
+
"az sql server delete": "[BLOCKED] SQL Server deletion destroys all databases - use Terraform/Terragrunt",
|
|
290
|
+
"az sql db delete": "[BLOCKED] Database deletion loses all data - use Terraform/Terragrunt",
|
|
291
|
+
"az cosmosdb delete": "[BLOCKED] CosmosDB deletion is irreversible - use Terraform/Terragrunt",
|
|
292
|
+
"az redis delete": "[BLOCKED] Redis deletion loses all cached data - use Terraform/Terragrunt",
|
|
293
|
+
"az storage account delete": "[BLOCKED] Storage account deletion destroys all data - use Terraform/Terragrunt",
|
|
294
|
+
"az aks delete": "[BLOCKED] AKS cluster deletion is irreversible - use Terraform/Terragrunt",
|
|
295
|
+
"az acr delete": "[BLOCKED] Container registry deletion destroys all images - use Terraform/Terragrunt",
|
|
296
|
+
"az keyvault delete": "[BLOCKED] Key Vault deletion can render encrypted data unrecoverable",
|
|
297
|
+
"az functionapp delete": "[BLOCKED] Function App deletion is irreversible - use Terraform/Terragrunt",
|
|
298
|
+
"az webapp delete": "[BLOCKED] Web App deletion is irreversible - use Terraform/Terragrunt",
|
|
299
|
+
"az ad app delete": "[BLOCKED] App registration deletion breaks all dependent services",
|
|
300
|
+
"az ad sp delete": "[BLOCKED] Service principal deletion breaks authentication for dependent services",
|
|
301
|
+
"az servicebus namespace delete": "[BLOCKED] Service Bus namespace deletion is irreversible - use Terraform/Terragrunt",
|
|
302
|
+
"az eventhubs namespace delete": "[BLOCKED] Event Hubs namespace deletion is irreversible - use Terraform/Terragrunt",
|
|
303
|
+
|
|
243
304
|
# GCP suggestions
|
|
244
305
|
"gcloud projects delete": "[BLOCKED] Project deletion is irreversible - must be done via Cloud Console",
|
|
245
306
|
"gcloud container clusters delete": "[BLOCKED] Use Terraform/Terragrunt for GKE management",
|
|
@@ -335,6 +396,41 @@ SEMANTIC_BLOCKED_RULES = (
|
|
|
335
396
|
SemanticBlockedRule("aws_critical", ("aws", "organizations", "delete-organization"), "aws organizations delete-organization"),
|
|
336
397
|
SemanticBlockedRule("aws_critical", ("aws", "route53", "delete-hosted-zone"), "aws route53 delete-hosted-zone"),
|
|
337
398
|
|
|
399
|
+
# Azure
|
|
400
|
+
SemanticBlockedRule("azure_critical", ("az", "group", "delete"), "az group delete"),
|
|
401
|
+
SemanticBlockedRule("azure_critical", ("az", "network", "vnet", "delete"), "az network vnet delete"),
|
|
402
|
+
SemanticBlockedRule("azure_critical", ("az", "network", "vnet", "subnet", "delete"), "az network vnet subnet delete"),
|
|
403
|
+
SemanticBlockedRule("azure_critical", ("az", "network", "nsg", "delete"), "az network nsg delete"),
|
|
404
|
+
SemanticBlockedRule("azure_critical", ("az", "network", "public-ip", "delete"), "az network vnet delete"),
|
|
405
|
+
SemanticBlockedRule("azure_critical", ("az", "network", "application-gateway", "delete"), "az network vnet delete"),
|
|
406
|
+
SemanticBlockedRule("azure_critical", ("az", "network", "lb", "delete"), "az network vnet delete"),
|
|
407
|
+
SemanticBlockedRule("azure_critical", ("az", "network", "dns", "zone", "delete"), "az network vnet delete"),
|
|
408
|
+
SemanticBlockedRule("azure_critical", ("az", "network", "private-dns", "zone", "delete"), "az network vnet delete"),
|
|
409
|
+
SemanticBlockedRule("azure_critical", ("az", "vm", "delete"), "az vm delete"),
|
|
410
|
+
SemanticBlockedRule("azure_critical", ("az", "vmss", "delete"), "az vmss delete"),
|
|
411
|
+
SemanticBlockedRule("azure_critical", ("az", "disk", "delete"), "az disk delete"),
|
|
412
|
+
SemanticBlockedRule("azure_critical", ("az", "snapshot", "delete"), "az disk delete"),
|
|
413
|
+
SemanticBlockedRule("azure_critical", ("az", "image", "delete"), "az disk delete"),
|
|
414
|
+
SemanticBlockedRule("azure_critical", ("az", "sql", "server", "delete"), "az sql server delete"),
|
|
415
|
+
SemanticBlockedRule("azure_critical", ("az", "sql", "db", "delete"), "az sql db delete"),
|
|
416
|
+
SemanticBlockedRule("azure_critical", ("az", "cosmosdb", "delete"), "az cosmosdb delete"),
|
|
417
|
+
SemanticBlockedRule("azure_critical", ("az", "redis", "delete"), "az redis delete"),
|
|
418
|
+
SemanticBlockedRule("azure_critical", ("az", "storage", "account", "delete"), "az storage account delete"),
|
|
419
|
+
SemanticBlockedRule("azure_critical", ("az", "storage", "container", "delete"), "az storage account delete"),
|
|
420
|
+
SemanticBlockedRule("azure_critical", ("az", "storage", "blob", "delete-batch"), "az storage account delete"),
|
|
421
|
+
SemanticBlockedRule("azure_critical", ("az", "aks", "delete"), "az aks delete"),
|
|
422
|
+
SemanticBlockedRule("azure_critical", ("az", "aks", "nodepool", "delete"), "az aks delete"),
|
|
423
|
+
SemanticBlockedRule("azure_critical", ("az", "acr", "delete"), "az acr delete"),
|
|
424
|
+
SemanticBlockedRule("azure_critical", ("az", "keyvault", "delete"), "az keyvault delete"),
|
|
425
|
+
SemanticBlockedRule("azure_critical", ("az", "keyvault", "key", "delete"), "az keyvault delete"),
|
|
426
|
+
SemanticBlockedRule("azure_critical", ("az", "keyvault", "secret", "delete"), "az keyvault delete"),
|
|
427
|
+
SemanticBlockedRule("azure_critical", ("az", "functionapp", "delete"), "az functionapp delete"),
|
|
428
|
+
SemanticBlockedRule("azure_critical", ("az", "webapp", "delete"), "az webapp delete"),
|
|
429
|
+
SemanticBlockedRule("azure_critical", ("az", "ad", "app", "delete"), "az ad app delete"),
|
|
430
|
+
SemanticBlockedRule("azure_critical", ("az", "ad", "sp", "delete"), "az ad sp delete"),
|
|
431
|
+
SemanticBlockedRule("azure_critical", ("az", "servicebus", "namespace", "delete"), "az servicebus namespace delete"),
|
|
432
|
+
SemanticBlockedRule("azure_critical", ("az", "eventhubs", "namespace", "delete"), "az eventhubs namespace delete"),
|
|
433
|
+
|
|
338
434
|
# GCP
|
|
339
435
|
SemanticBlockedRule("gcp_critical", ("gcloud", "projects", "delete"), "gcloud projects delete"),
|
|
340
436
|
SemanticBlockedRule(
|
|
@@ -432,18 +528,6 @@ def get_blocked_patterns() -> List[re.Pattern]:
|
|
|
432
528
|
return patterns
|
|
433
529
|
|
|
434
530
|
|
|
435
|
-
def get_blocked_patterns_by_category(category: str) -> List[re.Pattern]:
|
|
436
|
-
"""
|
|
437
|
-
Get blocked patterns for a specific category.
|
|
438
|
-
|
|
439
|
-
Args:
|
|
440
|
-
category: Category name (aws_critical, kubernetes_critical, etc.)
|
|
441
|
-
|
|
442
|
-
Returns:
|
|
443
|
-
List of compiled regex patterns for that category
|
|
444
|
-
"""
|
|
445
|
-
return BLOCKED_PATTERNS.get(category, [])
|
|
446
|
-
|
|
447
531
|
|
|
448
532
|
def is_blocked_command(command: str) -> BlockedCommandResult:
|
|
449
533
|
"""
|
|
@@ -490,26 +574,6 @@ def is_blocked_command(command: str) -> BlockedCommandResult:
|
|
|
490
574
|
return BlockedCommandResult(is_blocked=False)
|
|
491
575
|
|
|
492
576
|
|
|
493
|
-
def get_suggestion_for_blocked(command: str) -> Optional[str]:
|
|
494
|
-
"""
|
|
495
|
-
Get a safe alternative suggestion for a blocked command.
|
|
496
|
-
|
|
497
|
-
Args:
|
|
498
|
-
command: The blocked command
|
|
499
|
-
|
|
500
|
-
Returns:
|
|
501
|
-
Suggestion string or None
|
|
502
|
-
"""
|
|
503
|
-
semantic_rule = _match_semantic_block_rule(command)
|
|
504
|
-
if semantic_rule is not None:
|
|
505
|
-
return BLOCKED_COMMAND_SUGGESTIONS.get(semantic_rule.suggestion_key)
|
|
506
|
-
|
|
507
|
-
command_lower = command.lower()
|
|
508
|
-
for cmd_prefix, suggestion in BLOCKED_COMMAND_SUGGESTIONS.items():
|
|
509
|
-
if cmd_prefix in command_lower:
|
|
510
|
-
return suggestion
|
|
511
|
-
return None
|
|
512
|
-
|
|
513
577
|
|
|
514
578
|
def _match_semantic_block_rule(command: str) -> Optional[SemanticBlockedRule]:
|
|
515
579
|
"""Return the first semantic deny rule that matches a command."""
|
|
@@ -38,10 +38,6 @@ class CommandSemantics:
|
|
|
38
38
|
"""Return the canonical analysis form of the command."""
|
|
39
39
|
return " ".join(self.semantic_tokens)
|
|
40
40
|
|
|
41
|
-
def has_flag(self, flag: str) -> bool:
|
|
42
|
-
"""Check whether a normalized flag is present."""
|
|
43
|
-
return flag.lower() in self.flag_tokens
|
|
44
|
-
|
|
45
41
|
|
|
46
42
|
def tokenize_command(command: str) -> Tuple[str, ...]:
|
|
47
43
|
"""Tokenize a shell command safely, preserving quoted substrings."""
|
|
@@ -9,7 +9,7 @@ Ensures commands follow GitOps principles:
|
|
|
9
9
|
|
|
10
10
|
import re
|
|
11
11
|
import logging
|
|
12
|
-
from typing import
|
|
12
|
+
from typing import List, Optional
|
|
13
13
|
from dataclasses import dataclass, field
|
|
14
14
|
|
|
15
15
|
logger = logging.getLogger(__name__)
|
|
@@ -177,13 +177,3 @@ def validate_gitops_workflow(
|
|
|
177
177
|
severity="warning",
|
|
178
178
|
suggestions=["Verify command follows GitOps principles"],
|
|
179
179
|
)
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
def to_dict(result: GitOpsValidationResult) -> Dict[str, Any]:
|
|
183
|
-
"""Convert GitOpsValidationResult to dictionary for backward compatibility."""
|
|
184
|
-
return {
|
|
185
|
-
"allowed": result.allowed,
|
|
186
|
-
"reason": result.reason,
|
|
187
|
-
"severity": result.severity,
|
|
188
|
-
"suggestions": result.suggestions,
|
|
189
|
-
}
|