Sandboxes
A sandbox is a Linux virtual machine of its own, with its own kernel, memory and disk. It runs Debian with Python 3, Node.js 24, git, build tools, curl, jq, ripgrep and Chromium. There is no systemd.
- Commands run as the user
agentin/workspaceby default, with a clean environment and no standard input.agentcan usesudowithout a password, soapt-get installandpip installwork. - Files persist across pauses and restarts until you delete the sandbox.
- Docker can be installed and run inside a sandbox:
sudo apt-get install -y docker.io, then startdockerdyourself. - A sandbox has an ID like
sb-4f1c9e27a0b3d5c86e12. It stays on the host it was created on.
Sandbox states
| Status | Meaning |
|---|---|
creating | Being placed on a host and booted. |
running | Running. Billed as compute. |
pausing | Its memory is being written to disk. |
paused | Memory and files are saved on its host; nothing runs. Billed as storage. The next use resumes it. |
resuming | Its memory is being loaded back. |
stopped | Its machine stopped without being deleted, for example when its host restarted. Billed as storage. The next use boots it fresh from its files. |
deleting | Being deleted. If the host does not confirm at once, we keep trying. |
deleted | Gone, with its files, memory and snapshots. |
failed | Creation failed. Nothing runs and nothing is billed. |
Sizes
Pick a size when you create a sandbox. It cannot be changed later.
| Size | vCPUs | Memory | Disk |
|---|---|---|---|
small | 1 | 2 GiB | 10 GiB |
medium (default) | 2 | 4 GiB | 20 GiB |
large | 4 | 8 GiB | 40 GiB |
vCPUs are shared with other sandboxes on the host, in proportion to size. A sandbox that keeps its vCPUs busy for minutes, such as a long build, gets a smaller share until it calms down, so shells and short tasks stay quick. Disk and network throughput are limited per vCPU; see Limits.
Pause, resume and the idle policy
Pausing writes a sandbox's memory to its host's disk and stops it. Nothing runs and it uses no memory or CPU. Resuming loads the memory back: processes, open files and everything in memory continue where they were. The clock is corrected. Outside servers may have closed connections during a long pause, so programs should be ready to reconnect.
You never have to resume by hand. Running a command, moving a file, opening a shell, the browser or a preview, or reading the credential inventory resumes a paused sandbox first. Reading its status never does.
When a sandbox counts as idle
A running sandbox is active while any of these is true:
- a command's output is being streamed, a file is moving, or a shell, browser or preview connection is open;
- it sent or received network traffic recently;
- it used CPU recently;
- a program in it is waiting for a reply through our network proxy, such as an agent waiting on its model (for up to 30 minutes).
When none of these has been true for its idle timeout, it is paused. The timeout is 10 minutes by default, and 1 to 1440 minutes when you set it:
mat create --idle-minutes 30mat set $ID --idle-minutes 5A command left running with no client attached keeps running while it uses CPU or the network. If it goes quiet, the sandbox may pause; the command continues when it resumes.
Pausing to make room
A host may pause sandboxes before their timeout when it needs room for another sandbox: only ones idle for at least 30 seconds, and first those of accounts using more than their share of the host. When a host runs short of memory, it may pause active sandboxes too, because a paused sandbox loses nothing. Kept-awake sandboxes are never paused this way.
Restarts and cold boots
If a host restarts or a sandbox's machine crashes, the sandbox shows stopped. Its next use boots it fresh from its files: files are kept, running processes are not. The same happens if a saved memory snapshot cannot be restored. cold_booted_at in the API, and restarted in mat ls, say when it happened.
Keep-awake
A kept-awake sandbox is never paused automatically: not when idle and not to make room. It is billed as running the whole time. You can still pause it yourself.
mat create --keep-awakemat set $ID --keep-awake truemat set $ID --keep-awake falseNetwork rules (egress)
Every connection a sandbox opens goes through a proxy on its host. Each sandbox has one of three presets:
| Preset | What the sandbox can reach |
|---|---|
open (default) | The internet. |
restricted | HTTPS and HTTP to the domains you allow, plus the essentials unless you turn them off, and DNS. Nothing else. |
none | Nothing. |
mat create --egress restricted --allow example.com --allow '*.example.org'mat set $ID --egress restricted --no-essentials --allow api.anthropic.commat set $ID --egress openexample.comallows exactly that name.*.example.comallows its subdomains at any depth, but notexample.comitself.- Under
restricted, the proxy reads the name from the TLS server name or the HTTPHostheader, without decrypting. It looks the name up itself and connects to its own answer, so an allowed name cannot be paired with another address. - Protocols that carry no name, such as SSH or database connections, need
open. - Refused connections are rejected at once, so programs fail fast instead of hanging.
- A change applies to new connections; open ones are not cut.
egress_pendingis true until the host confirms it enforces the new rules.mat set --egress restrictedreplaces the whole list, so name every domain each time.
The essentials
Under restricted, these are allowed unless you pass --no-essentials (or "essentials": false):
| For | Domains |
|---|---|
| Debian packages | deb.debian.orgsecurity.debian.org |
| Python | pypi.orgfiles.pythonhosted.org |
| JavaScript | registry.npmjs.orgregistry.yarnpkg.com |
| Rust | crates.ioindex.crates.iostatic.crates.iostatic.rust-lang.org |
| Go | proxy.golang.orgsum.golang.org |
| GitHub | github.com*.github.com*.githubusercontent.comghcr.io |
| Model APIs | api.anthropic.comapi.openai.comgenerativelanguage.googleapis.com |
Always blocked
Whatever the preset, a sandbox can never reach private networks, link-local and cloud metadata addresses, loopback, other sandboxes or our hosts. This is a security boundary, not a setting.
Abuse controls
Outbound mail on port 25 is blocked; send through a mail provider on port 465 or 587. Each sandbox has a limit on new connections per second and on open connections, set well above what installs, git, model APIs and browsing need: 20 per second sustained, with bursts of 400. Accounts without an active subscription get a quarter of that. A sandbox whose connections fail to 200 different destinations within a minute looks like a port scan: its new connections are refused, and it is paused and held for review. A held sandbox keeps its files and memory but cannot run until we clear it; we email you when it happens, and its status shows held.
The egress log
mat egress shows recent connections, oldest first: the name asked for, the address, whether it was allowed, and why not. Repeated entries are folded with a count. The host keeps the last 1000 per sandbox in memory, so the log is lost if the host agent restarts.
Brokered secrets
A brokered secret is an API key or token that never enters the sandbox. Inside, the variable holds a worthless placeholder. When a program sends an HTTPS request to a domain the secret is bound to, the proxy on the host replaces the placeholder with the real value in the request headers.
mat secret set ANTHROPIC_API_KEY --domain api.anthropic.comPaste the secret value and press Enter:mat create --secret ANTHROPIC_API_KEYmat exec $ID -- sh -c 'echo $ANTHROPIC_API_KEY'The last command prints the placeholder, not your key.
- The value is swapped in only in request headers, and only when both the TLS server name and the
Hostheader match a bound domain. The proxy verifies the real server's certificate before sending anything. - To read those requests, the proxy decrypts HTTPS to bound domains, and only to them. The sandbox trusts a certificate authority of its own for this; its key stays on the host.
SSL_CERT_FILE,REQUESTS_CA_BUNDLEandNODE_EXTRA_CA_CERTSpoint to it, so curl, Python and Node trust it. - Under
restricted, the secret's domain must also be allowed. Model APIs are in the essentials. - Secrets belong to your account. Replacing a value updates every sandbox it is attached to. New commands and shells get newly attached secrets; running ones keep the environment they started with.
- Values can be written and replaced, never read back.
- Not supported yet: secrets in request bodies or URLs, and WebSocket connections to bound domains. Bound domains are offered HTTP/1.1 only.
Credential inventory
Agents leave credentials behind: a gh login, an .env file, browser cookies. mat credentials lists what a sandbox holds and where, never the values:
mat credentials $IDaws_profile default - /home/agent/.aws/credentialsgithub_cli octocat github.com /home/agent/.config/gh/hosts.ymlIt looks in well-known places: tool credential files in /home/agent and /root (AWS, gh, git, netrc, npm, PyPI, Docker, SSH keys, kubeconfig, gcloud, Claude Code and Codex logins), .env files in /workspace, running programs' environments, and Chromium cookie stores (sites and counts). It is a best-effort scan, not a guarantee. Scanning runs inside the sandbox, so it resumes a paused one.
Shell and browser
mat shell opens an interactive login shell. mat browser prints a single-use URL for the sandbox's Chromium over the Chrome DevTools Protocol, valid for 60 seconds. Pass it to a CDP client:
from playwright.sync_api import sync_playwright url = "wss://…" # from mat browserwith sync_playwright() as p: browser = p.chromium.connect_over_cdp(url) page = browser.contexts[0].new_page() page.goto("https://example.com")Chromium in the sandbox runs with Google's background services turned off, so it does not call out on its own. Your API key never appears in these URLs. See Connecting a shell or browser.
Previews
A preview shows a web server running in a sandbox, at https://PORT-SANDBOX_ID. followed by our preview domain. Open one from the sandbox's page in the dashboard. Previews are never public: only you, signed in to the dashboard, can open them. An API key or a mat session cannot. Signing out ends access. HTTP and WebSockets both work, and a paused sandbox resumes when its preview is opened.
Previews need our preview domain; where it is not set up yet, the dashboard cannot open one.