Skip to content

runtime command run

runtime command run <binary> [args...]

The Command Engine wraps approved CLI binaries behind one deterministic execution boundary. For a reviewed semantic mode it preserves the native CLI's authentication, context and output behaviour while Runtime supplies policy, bounded execution and audit.

runtime command run git status
runtime command run terraform plan
runtime command run kubectl get pods
runtime command run gh repo list --limit 5

Two ways the Command Engine is reached

  1. A provider chose it. runtime github pr list dispatches to the GitHub provider, which decided that operation is best served by gh. The provider supplies the binary and arguments.
  2. Directly, via command run — the governed native CLI path for modes a provider does not expose.

Both paths are gated by the same policy. When a provider picks the CLI transport, allowed_binaries and command_policy are evaluated on top of the provider's own rule. A provider can only reach binaries the installation already permits, and cannot launder a denied binary through an operation.

Prefer a provider operation

command run is an escape hatch, not the default style. When a provider already exposes what you need, use it:

runtime github repo list             # preferred
runtime command run gh repo list     # works, but bypasses the curated surface

A provider operation is a stable contract — the transport can change beneath it without breaking you. A command run invocation is pinned to one tool's exact CLI forever.

Allowed binaries

Only binaries in allowed_binaries may execute at all. The compiled default policy permits 21:

Category Binaries
VCS / GitHub git, gh
IaC terraform, pulumi, packer
GCP gcloud, gsutil, bq
AWS / Azure aws, az
Kubernetes kubectl, oc, helm, kustomize, flux, istioctl, argocd
Containers docker, podman
Secrets vault, sops

Check what your install actually allows — new entries in a release do not reach an existing policy-config.yaml:

runtime config validate

That report also tells you which allowed binaries are actually installed on the machine.

See Command Engine Binaries for per-binary detail, and Policy to change the list.

Governance layers

1. Binary allow-list

A binary not in allowed_binaries can never run. Full stop.

2. Hard-denied binaries

command_policy:
  denied_binaries:
    - rm
    - sudo
    - chmod
    - curl
    - wget
    - ssh

Refused outright, independent of allowed_binaries — adding one to the allow-list does not un-deny it.

3. Subcommand rules

command_policy:
  rules:
    terraform:
      denied: ["destroy"]
    git:
      denied: ["push --force", "reset --hard"]
List Matching
denied A contiguous run of argument tokens anywhere in the command
allowed Prefix match; when present, only matching prefixes may run

Denials always win.

4. Semantic context support

An installed binary that passes policy still needs a known context contract. Runtime classifies an invocation as context-aware, explicitly context-free, or unsupported. When behaviour differs by subcommand, Runtime admits an exact semantic mode instead of trusting the whole binary.

gh repo list (gh.repo.list) is the first admitted direct gh mode. gh pr list, gh auth, extensions, and every other direct gh mode remain context-unsupported until each has a reviewed contract. Provider operations such as runtime github pr list are unaffected because the GitHub provider owns their operation semantics.

Because denied matches anywhere rather than as a prefix, leading flags cannot bypass a rule:

runtime command run terraform destroy -auto-approve   # denied
runtime command run git -C /tmp/wd push --force       # denied

5. The compiled floor

Some refusals are not in your document and cannot be removed from it. They are compiled into the binary, and a policy that explicitly allows the operation does not override them — a denial a document can remove is a suggestion.

The one you are most likely to meet is credential export: an operation whose output is a secret.

$ runtime command run gcloud auth print-access-token
compiled safety profile (compiled-safety-profile/v3) denied:
secret-export-operation: `gcloud auth print-access-token` is a
credential-export operation: its output is an access token. An approved tool
may use a credential internally — that is the whole native-credential model —
but it may not turn one into Runtime output

The distinction is where the bytes go, not whether a secret is involved. kubectl apply authenticates with your kubeconfig and is fine. kubectl get secret -o yaml returns the secret itself, and from there it is in the result, the next capability step, an AI's context and the audit record.

Also denied by the floor: reading a secret out of a manager (gcloud secrets versions access, aws secretsmanager get-secret-value, az keyvault secret show, vault kv get), printing a session token (oc whoami -t, gh auth token), decrypting to stdout (sops -d), and git credential fill.

If you genuinely need to move a secret, use the tooling built for that. A governed runtime is not it.

The second floor denial is tool reconfiguration: an operation that changes the tool rather than the system you are operating on.

$ runtime command run gcloud components install gke-gcloud-auth-plugin
compiled safety profile (compiled-safety-profile/v3) denied:
tool-lifecycle-mutation: `gcloud components install` changes gcloud itself,
not the system you are operating on: it adds executables to the SDK install
root, which policy pinned and digested. Runtime pins the artifact and reads
where your tool is pointed; an operation that rewrites either one is not the
operation policy thought it was approving. Do it deliberately in your own
shell, outside Runtime

Also denied: gcloud config set, gcloud auth login, kubectl config use-context, kubectl config set-credentials, oc login / oc logout, git config --global, helm plugin install, docker login, vault login, argocd login, terraform login, gh extension install, gh alias set, aws configure set and az account set.

Reading is untouched. kubectl config view, kubectl config current-context, gcloud config get-value project, gcloud auth list and git config --get all run. The rule is about writes to the tool's own state.

Why it is a floor and not a policy rule: Runtime pins the exact binary that runs and reads where your tool is pointed to decide whether an operation is allowed. A command that installs a component into that install root, or switches the current context, changes what every later governed operation does — through a command whose name says nothing about it.

Do these in your own shell, deliberately. The two Runtime already owns are named verbs with an audit record: runtime auth logout gcp and runtime auth logout openshift.

Evaluation order

Policy is evaluated before the Command Engine runs, and against the final arguments — selectors from your native tool are already resolved into them at that point.

context resolution → artifact pinning → compiled floor → allow-list →
denied_binaries → subcommand rules → auth → execute

Two parts of that ordering are load-bearing. Selectors are applied before the policy check rather than after, so the command policy judged is the command that runs — the engine adds nothing afterwards. And the binary is resolved to one exact file before the decision, so the artifact policy reviewed is the artifact that executes rather than whatever the name resolves to a moment later.

What the process receives

The Command Engine builds an environment rather than passing on its own.

Before 0.6.0 an approved binary inherited every variable the calling shell held — every unrelated token, every session credential, and the loader variables (LD_PRELOAD, NODE_OPTIONS, GIT_SSH_COMMAND) that turn any approved binary into a launcher for unreviewed code.

Now a variable reaches a tool only when the tool's built-in contract declares it and your policy permits it for that exact binary:

command_policy:
  rules:
    vault:
      environment:
        - VAULT_ADDR
        - VAULT_TOKEN

Omit environment and the tool's own contract decides. An empty list is different from an absent one and means no variable at all. The loader variables are refused for everyone, including enterprise policy.

Output is bounded (1 MiB by default) with truncation stated in the message, and passes a redaction pass for recognisable credential shapes on both the success and failure paths. That is defence in depth, not data-loss prevention.

Three more things about the process, because they change what some commands can do:

Input What the process gets
Working directory A private, empty per-operation directory — never the directory you ran runtime from. A target-sensitive tool must therefore be told its target on the command line, and a command that depends on the current repository or Terraform project is not supported through command run in this release
HOME An isolated per-operation home, removed afterwards — except for tools whose contract declares they need the real one because their credentials live there (kubectl, oc, gcloud, git, and the rest of the native-credential set)
PATH, stdin, pager, editor A fixed minimal PATH; stdin closed, so a tool that decides to prompt fails instead of hanging; no pager and no editor

The empty working directory is also what contains a tool's own helpers. Repository hooks, .terraform/ provider plugins and local provisioners have nothing to load from it. On top of that, git hooks are switched off explicitly, helm plugins are pointed at an empty directory, and the Cloud SDK's component update check is disabled. Credential helpers — git's, docker's, and the user.exec plugin in your kubeconfig — are deliberately kept: they are how native authentication works, and refusing them would push you toward a long-lived token in a file instead.

Upgrading from 0.5.x

A command that quietly relied on an ambient variable will now fail. That is the intended breaking change — the alternative is every approved binary keeping access to every secret in your shell. Add the name to that binary's environment list.

Authentication

Auth is resolved from config.yaml's command_providers map:

command_providers:
  gh: github
  gcloud: gcp
  gsutil: gcp
  bq: gcp
  kubectl: kubernetes
  oc: openshift
  helm: kubernetes
  flux: kubernetes
  istioctl: kubernetes

A binary with no entry skips the Auth Engine entirely. There is no silent fallback to a default provider — omission means "manages its own credentials" (terraform's provider blocks, Docker's registry logins) or "no Auth Engine provider exists for that platform yet" (aws, az, vault).

For gh.repo.list, Runtime uses one of two platform-native paths:

  • If the configured RUNTIME_GITHUB_TOKEN exists, Runtime validates it and brokers it to gh as GH_TOKEN and GITHUB_TOKEN.
  • Otherwise, Runtime validates the existing native session with gh auth status and gh reads the same home, config directory, host and credential store as a local invocation.

Runtime never runs gh auth login during an operation. Run it yourself only when you want to create or change gh's native session.

Where an operation lands

Runtime keeps no context of its own. context.yaml and $RUNTIME_CONTEXT_FILE are retired: where an operation lands comes from the tool that will run it, or from the arguments you passed. See Runtime Context.

Selectors already on your command are authoritative. When one is missing, Runtime reads it from the tool's own state — your kubeconfig's current context, gcloud's active configuration — and resolves it into the argument list before policy evaluates the command:

Binary Resolved when absent Read from
gcloud --project=<project> gcloud's own active configuration
kubectl, oc -n <namespace> the current kubeconfig context
gh repo list explicit owner, otherwise authenticated account and host gh arguments, GH_HOST and native auth configuration

Nothing is added when the flag is already present. Registered context-free binaries receive their arguments unchanged; an unsupported binary or mode is refused rather than treated as context-free by omission.

Output modes

Use the same native table you expect from gh for interactive work:

runtime command run gh repo list --limit 5

Use Runtime JSON for automation. When the CLI explicitly declares JSON, the parsed value is available at .data; stdout and stderr stay separate under .result:

runtime --output json command run gh repo list --limit 5 \
  --json nameWithOwner,visibility,url

Use raw when a downstream tool needs only the sanitized native payload:

runtime --output raw command run gh repo list --limit 5

Raw is available for one direct provider or Command Engine operation, not for a multi-step capability. Runtime does not guess JSON from bytes; the semantic adapter recognizes the CLI's explicit --json flag.

Keeping this surface small keeps it auditable: a new binary does not silently gain selector resolution.

helm, flux and istioctl accept -n but don't receive it

They are context-aware for observation but have no selector case here. Pass -n <namespace> yourself where those tools need it.

Binding strength limits what may change

How firmly Runtime holds a target decides whether it may back a change:

Strength Meaning Reads Changes
strong the target cannot be reinterpreted by the tool afterwards
context_free the operation carries its own coordinates
selector_bound Runtime pinned the selectors, but the tool re-reads a mutable mapping to interpret them

So today kubectl get pods runs while kubectl delete namespace prod is refused with unpinned-side-effect. That is deliberate: a change approved against selectors the tool re-resolves afterwards can land somewhere policy never saw. Naming the target explicitly on the command is how you get a change through.

Auditing

Every invocation — allowed, denied, or failed — is recorded. Nothing executes without a trace.

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

The transport field is empty for command run records, which is how you tell a direct escape-hatch invocation apart from a provider operation that happened to choose the CLI.

Deliberately excluded

ansible and ansible-playbook are not on the allow-list and should not be added by the same pattern as everything else.

Their ad-hoc and playbook modules (ansible all -m shell -a '<opaque payload>') are arbitrary remote command execution — the same risk category as ssh, which is refused outright. The dangerous part is an opaque payload argument, not a recognizable subcommand, so a denied: rule cannot meaningfully cap it the way terraform destroy can be capped.

Revisit deliberately if a real need arises.

In capabilities

A binary: step is the same escape hatch inside a capability:

workflow:
  - binary: gh
    args: [repo, list, --limit, "10", --json, nameWithOwner]

The binary and semantic mode must pass all admission gates or validation fails. Note that runtime itself is not an allowed binary — a capability cannot invoke another capability. See Composing capabilities.

Operational examples on this site were verified against Runtime 0.9.8. After bootstrap, the version-exact files in Runtime Home win.