runtime command run¶
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¶
- A provider chose it.
runtime github pr listdispatches to the GitHub provider, which decided that operation is best served bygh. The provider supplies the binary and arguments. - 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:
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¶
Refused outright, independent of allowed_binaries — adding one to the
allow-list does not un-deny it.
3. Subcommand rules¶
| 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:
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_TOKENexists, Runtime validates it and brokers it to gh asGH_TOKENandGITHUB_TOKEN. - Otherwise, Runtime validates the existing native session with
gh auth statusand 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:
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:
Use raw when a downstream tool needs only the sanitized native payload:
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.
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:
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.