Skip to content

Policy

Policy decides what may execute. It is evaluated before every operation, before authentication, and it is never optional and never skippable.

Location: <Runtime Home>/policy-config.yaml, or $RUNTIME_POLICY_FILE. Ownership: yours. Seeded once, never overwritten by an upgrade.

Policy has no environment-variable overrides. An env var must never be able to widen governance — point RUNTIME_POLICY_FILE at a different file instead.

Two governance surfaces

Surface Governs Block
Provider Governance runtime <provider> <operation> providers:
Binary Governance runtime command run <binary> allowed_binaries: + command_policy:

When a provider chooses the CLI transport for an operation, both apply — the provider rule first, then allowed_binaries and command_policy on top.

Provider Governance

enabled: true

providers:
  github:
    enabled: true
    denied:
      - api DELETE
  files:
    enabled: true
    denied:
      - delete

How rules match

Operations are discovered from the Provider that owns them, not from a list here. The consequences:

  • A provider with no entry is fully allowed. runtime github repo list works without a policy edit, and a new operation in a later runtime version never requires one.
  • denied prefixes match against "<operation> <args...>". A rule can deny a whole operation (delete) or one narrow case (workflow run deploy-prod.yml, api DELETE).
  • allowed, when non-empty, restricts the provider to those prefixes.
  • enabled: false disables the provider entirely.
  • Rules are evaluated on the operation, never the transport. A denial cannot be sidestepped by a provider moving an operation from REST to the CLI.

Governance is evaluated on what will actually execute

If a provider normalizes an argument, policy matches the canonical form. This closed a real bypass: the github provider upper-cases an api method, so api delete once slipped past the api DELETE denial and sent a genuine DELETE. Any normalization performed after the policy check is a bypass by definition.

A rule must name an operation that exists

The seeded policy denies api DELETE rather than repo delete — because there is no repo delete operation. That rule would read like governance while enforcing nothing, and the destructive path is actually the api escape hatch.

runtime config validate

The report ends with policy warnings for any rule that can never match: a providers.<name> rule naming an operation the provider doesn't expose, a block for an unregistered provider, or a command_policy.rules.<binary> entry for a binary absent from allowed_binaries.

A denial that matches nothing is worse than no denial — it produces false confidence. Check the warnings after every policy edit.

Locking a provider down to an allow-list

providers:
  github:
    enabled: true
    allowed:
      - repo list
      - repo view
      - user get

Anything not prefixed by one of those is refused.

Allow-list → deny-list was a deliberate change

Earlier versions used a global allowed_commands list, which no longer exists. Installations that need an explicit allow-list must set providers.<name>.allowed. See Migration Guides.

Binary Governance

allowed_binaries:
  - gh
  - git
  - terraform
  - gcloud
  - kubectl
  - oc
  - aws
  - az
  - vault
  - helm
  - flux
  - gsutil
  - bq
  - docker
  - podman
  - kustomize
  - argocd
  - istioctl
  - pulumi
  - packer
  - sops

Only these binaries may execute at all through runtime command run. A binary absent from this list is refused, whatever the subcommand rules say.

Subcommand rules

command_policy:
  # Refused outright even if they were ever added to allowed_binaries
  denied_binaries:
    - rm
    - sudo
    - chmod
    - curl
    - wget
    - ssh

  rules:
    git:
      denied:
        - push --force
        - push -f
        - reset --hard
        - clean -fd
        - filter-branch
        - update-ref -d
    terraform:
      denied:
        - destroy
    kubectl:
      denied:
        - delete namespace
    helm:
      denied:
        - uninstall

Matching semantics, which differ between the two lists:

List Matching
denied A contiguous run of argument tokens anywhere in the command
allowed Prefix match

Denials always win.

Because denied matches anywhere, a leading global flag cannot bypass a rule: git -C /some/dir push --force is caught exactly like a bare git push --force.

Subcommand rules are evaluated before Runtime Context injection, so an injected flag (--project=, -n <namespace>) can never be used to slip past a denial.

What the seeded rules protect

The shipped defaults target one thing consistently: irreversible actions.

Binary Denied Why
git force-push, hard reset, clean, filter-branch, ref deletion Rewrites or discards history
terraform, pulumi destroy Tears down real infrastructure
helm, istioctl uninstall Removes a release or the mesh control plane
flux delete, uninstall Removes GitOps-managed resources or Flux itself
kubectl / oc delete namespace / delete project Bulk resource destruction
gcloud project and service-account deletion Identity and account loss
aws s3 rb --force, ec2 terminate-instances, iam delete-user, rds delete-db-instance Bulk data and identity deletion
az vm delete, group delete, ad user delete, keyvault delete Same profile as aws
vault kv delete, kv metadata delete, secrets disable Permanent secret removal
gsutil rm -r, rb Recursive object and bucket deletion
bq rm -f, rm -r -f, rm -f -r -f skips the confirmation prompt
docker, podman system prune -a, volume rm, run --privileged Bulk pruning; privileged run is a container-escape risk
argocd app delete, cluster rm, repo rm Detaches or deletes GitOps state

kustomize, packer and sops carry no denial rules — none of them has a subcommand that destroys existing infrastructure. kustomize only renders YAML to stdout, packer only builds new images, and sops encrypts and decrypts files in place.

Denials are audited

A denial is recorded exactly like a success or a failure. That is the point: the audit log answers "was this attempted and refused", not just "what happened".

runtime command run git push --force
runtime audit tail -n 1 --output json

The runtime tells you when it is the one refusing:

policy denied github <operation>: ...

Common tasks

providers:
  files:
    enabled: true
    denied: []          # remove the `delete` rule
providers:
  github:
    enabled: true
    denied:
      - api DELETE
      - workflow run deploy-prod.yml
allowed_binaries:
  - gh
  - git
  - my-internal-cli

command_policy:
  rules:
    my-internal-cli:
      denied:
        - teardown
providers:
  github:
    enabled: false

After any edit:

runtime config validate     # confirms the rule can actually match something

New policy defaults never reach an existing install

policy-config.yaml is user-owned, so a release adding allowed_binaries entries or new deny rules changes nothing for a machine that already has the file. See the upgrade trap.