Execution Admission
Use admission policies to reject YAML function execution and CLI commands, or attach command restrictions to component instances using literal tags and labels.
Input kinds
| Kind | Gate | Input fields | Instance selectors |
|---|---|---|---|
function | Before YAML function execution and Go-template calls, including configuration loading | name: canonical YAML name without ! or template function/namespace; engine: yaml or go-template | No; instance metadata may not exist yet |
command | Before a runnable CLI command's argument validator and pre-run hooks | command: command path string; path: array of command names, excluding atmos; destroy: boolean, see Destroy detection | No |
component.command | During shared stack resolution, before component template/function evaluation and tool execution | command, component_type, component, stack, dry_run, destroy (see Destroy detection), capabilities | Yes; literal inherited metadata |
These gates do not require --affected. Function arguments and CLI argument/flag values are not
exposed: they can contain secrets. The one exception is destroy, a boolean Atmos derives from the arguments.
All three kinds support CEL and restricted Starlark, denial,
and GitHub approval requirements.
Disable !exec
apiVersion: atmos/v1
kind: Policy
metadata:
name: no-exec
description: Prevent configuration from executing shell commands through !exec.
spec:
policies:
disable:
on: function
action: deny
reason: |-
**`!exec` is disabled** because stack configuration must not run shell commands.
**Instead:** Pass precomputed values through environment variables and read them with `!env`.
rule: input.name == 'exec'
The optional reason appears in the CLI error when this rule blocks !exec. Use it to explain
the restriction and suggest an alternative. Omitting it does not change enforcement.
Every denial names the rule that blocked it, such as function-disable-exec/guard, whether or not
the rule has a reason, and exits with code 77. Scripts and CI jobs can use that code to tell a
policy denial apart from a usage error or a failed command. Evaluation errors and pending approvals
do not use this code.
The check applies to atmos.yaml mappings and sequences, stack YAML functions, and registry-based
lazy evaluation. Skipped functions are not executed. Literal text containing !exec is not shell
execution. This rule does not disable shell hooks, automation scripts, or external tools. Go-template calls have their own function names and engine, described below.
Block a command
apiVersion: atmos/v1
kind: Policy
metadata:
name: no-destroy
spec:
policies:
direct-destroy:
on: command
action: deny
rule: input.command == 'terraform destroy' || input.destroy
CLI admission covers registered and custom runnable commands, including commands without components or stacks. Native aliases use the canonical command path. Built-in commands are also checked before project initialization and configuration loading. Configuration is loaded to register custom commands before their CLI admission; use function policies to control YAML execution during that loading. Help rendering and external programs invoked without Atmos are not command admission targets.
Admission of built-in commands runs before atmos.yaml loads, so a denial here cannot post commit statuses or job
summaries. When a CI provider is detected, such as GitHub Actions, Atmos reports one error annotation per denying
rule instead. It needs no CI configuration. Custom-command denials happen after configuration loads, so they
follow ci.enabled and ci.annotations.enabled:
::error title=Policy no-destroy/direct-destroy denied execution::Denied by no-destroy/direct-destroy.
The annotation message is the rule's reason as plain text, or Denied by <rule>. when the rule has
none. These annotations have no file location, because the command is denied before any stack is read.
See failure annotations for how other failures are reported.
Destroy detection
Matching input.command == 'terraform destroy' alone misses
atmos terraform apply vpc -s prod -destroy -auto-approve. Both command and component.command
inputs therefore carry destroy, a normalized boolean that Atmos computes from the arguments. Rules do
not parse flags, and no other argument value is exposed. destroy is always present and is true when:
- the command is
terraform destroy(including thetfalias) orhelmfile destroy, or - a
terraformcommand (OpenTofu included), such asplan,apply, ordeploy, receives the-destroyor--destroyflag, including after the--separator.
A true value (-destroy=true, --destroy=1, -destroy=T) counts, and a false value
(-destroy=false, -destroy=0) does not. An unparseable value counts as true, and any true
occurrence wins over a later false. Every other command, such as helm delete, kubernetes delete, or
any non-Terraform command with a -destroy argument, leaves destroy false; match those by command or
path.
destroy cannot see a decision made earlier: atmos terraform apply <planfile> is not recognized as a destroy
when the saved plan came from plan -destroy. Govern the plan itself with
Terraform plan policies.
Attach to instances
apiVersion: atmos/v1
kind: Policy
metadata:
name: production
spec:
policies:
no-delete:
on: component.command
match:
labels: {stage: prod}
action: deny
rule: input.command in ['delete', 'destroy'] || input.destroy
Shared stack resolution applies this gate to Terraform, Helmfile, Helm, Kubernetes, Packer, Ansible, container, emulator, and custom component integrations using that resolver. Commands without an instance still receive CLI admission, not fabricated component metadata. An integration bypassing shared stack resolution must add instance admission before advertising that capability.
Metadata used by selectors must be literal: unresolved templates/functions or malformed metadata
produce an error, not a non-match. capabilities always includes component.command, function, and source;
Terraform also supplies terraform.plan and operation, while Helmfile, Helm, and Kubernetes supply
operation. A capability name does not manufacture its input at other gates.
Enforcement configuration
The CLI loads and pins policy definitions after applying --chdir, before configuration loading.
Later configuration changes to the environment or working directory do not replace that in-memory
bundle. Missing default policy.d/ remains opt-in behavior; protect an explicit ATMOS_POLICY_DIR
and the governing files in CI. Pinning does not establish trust in files selected from the PR itself.
Denials, evaluation failures, missing required inputs, and outstanding approvals return errors. Dry-run does not bypass admission. See enforcement for repository protection requirements and affected policies for semantic detachment checks.
Go-template functions and provenance
on: function also covers Go-template calls. input.engine is yaml or go-template;
input.name retains the existing YAML name or identifies the template function/namespace.
For example:
apiVersion: atmos/v1
kind: Policy
metadata:
name: rendering
spec:
policies:
environment:
on: function
action: deny
rule: >-
input.engine == 'go-template' &&
input.name in ['env', 'getenv', 'expandenv', 'env.Getenv', 'env.ExpandEnv']
remote-exec:
on: function
action: deny
rule: input.engine == 'yaml' && input.name == 'exec' && input.provenance != 'local'
Functions are checked when called, so an unused branch does not trigger admission. Namespace
constructors such as net and aws are gates; file methods, DNS lookup methods, environment
methods, and atmos.Component, atmos.Store, atmos.Resolve, and atmos.GomplateDatasource
have additional method gates. atmos.Resolve retains the YAML handler check too.
provenance is local, remote, or unknown. Remote cache files retain remote attribution.
Lazy YAML values retain conservative parse-time attribution across merges: identical values with
multiple origins are treated as remote if any recorded origin is remote. Synthetic or unattributed
execution contexts remain unknown; use != 'local' when missing attribution must block.
This is execution provenance, not proof of a trusted author or signed source.
See source and filesystem policies for destination restrictions and include
confinement, and policy commands for offline checks and --explain diagnostics.