Policies
The agent asks. The policy decides. Every file, Git, SSH and network action an agent takes is checked against a policy it cannot see, edit or turn off.
The permission gateway
Each agent runs in its own microVM. Everything outside that VM is reached through a gateway on the host:
- Files: your directories are served into the guest by a host process that applies the filesystem policy on every open, read and write.
- Git:
gitandghin the guest talk to a Git gateway on the host, which holds the token and checks every request. - SSH:
sshto one of your nodes goes through the host with a short-lived certificate, and the node enforces its own policy. - Network: egress rules are compiled into the sandbox's firewall, outside the guest kernel.
The policy lives on the host. Nothing the agent sends can change it, and a refusal reaches the agent as an ordinary error: Permission denied, a rejected push, exit code 126.
Two policies, two refusals
Branch-level Git permissions with the token on the host, and your directory mounted in with secrets kept out:
{"op":"read","path":"/workspace/app/.env","reason":"denied by fs policy"}
smx agent logs · every refusal is recorded
Filesystem policy
A sandbox config is one YAML file. Its fs: section decides which host paths the guest sees and how:
allowtakesro:PATHorrw:PATH=GUEST.denybeats any allow, and a denied path is invisible in the guest.defaultisnone,roorrwfor paths no rule names.mountsattach other backends the same way: a host directory (including an NFS or SMB mount), S3 or SFTP. Credentials in mount options are references (env:orfile:); a literal secret fails the create.- The state root and
/proc,/sys,/dev,/runare always closed, whatever the rules say. - Edits under an
rwpath land on the host as they happen.
Flags replace the file's lists for one create: --fs-allow, --fs-deny, --fs-default and --fs-mount TYPE:SOURCE=AT[,k=v].
Git policy
A Git policy says, per repository, what the sandbox may do: read, push (by branch and path), open change requests. With no policy, nothing is allowed.
- Plain
gitand the realghwork unchanged in the guest. The gateway inspects every push for its branches, force updates and changed files before forwarding it. - Some rules hold whatever the policy says: only branches can be pushed, never a protected or default branch; an agent never approves or merges into the default branch; CI and ownership files (
.github/**,CODEOWNERS) need an explicit allow. - On the API, pull requests can be listed, read and created from branches this sandbox pushed. Merges, comments and account lookups are refused.
- A sandbox with a Git policy has no direct route to the provider: it must use the
offlineormodel-onlyprofile.
SSH policy
An agent can open a shell, run commands and mount directories on one of your nodes. A small agent on the node (its own port, your sshd untouched) enforces one signed policy per profile:
The sandbox config grants the node; inside the guest, stock OpenSSH tools just work:
- Commands are checked when they execute, not as typed text, so
bash -c, scripts,busyboxapplets and renamed copies hit the same rule. - The command list gives clear refusals; the real boundary is session confinement on the node: an unprivileged uid, no new privileges, a filesystem view built from
fs, cgroup limits and per-session egress. - Certificates live at most 10 minutes and name one sandbox, one node and the hash of one policy. A node holding no verified policy refuses every session.
Egress
Every sandbox is created under a named security profile. Five ship built in:
| Profile | The guest reaches |
|---|---|
offline | nothing (with --allow, only those destinations) |
model-only | the node's model endpoints, plus DNS |
internet | the public internet; every private range denied |
internet-lan | the internet plus the subnets the operator made grantable |
unrestricted | everything except the always-closed floor |
A deny always beats an allow, and the floor beats both. A profile cannot widen what the operator made grantable, and a hostname never resolves into private space. Applying a profile to a running sandbox swaps its rules in one step, probes them, and rolls back if the probe fails.
Credentials stay on the host
The agent never holds the keys it acts with:
- Git: the gateway uses the host's token.
ghin the guest gets a placeholder login. - SSH: the private key is generated on the sandbox host and never leaves it; the guest needs no key.
- Storage: mount credentials are references resolved on the host.
A prompt-injected agent has no token to leak, and every packet it sends to an allowed host still goes through the policy.
Agent policy
On top of each sandbox's own policy, agent-policy.yaml on the host says which sandboxes, images, harnesses, security profiles and host paths each named agent may use:
An agent the file doesn't name may do nothing. The check reads what the sandbox actually has (its recorded image, profile and mounts), not what the command line claims. While a run is live, a Git request needs both the sandbox's policy and the agent's git: section to allow it.
Denials and the log
Every refusal is recorded as a policy.denied event in the run's log, tagged with the layer that refused it: filesystem, egress, node or permission prompt. Git operations and refusals are listed by smx vc log. Filesystem refusals are also appended to denials.jsonl, one JSON object per line.
Read a run's events, refusals included:
See the security model for the threat model, or the docs to run your first sandboxed agent.