Permissions, sandboxes, and undo

A wooden backyard sandbox full of play sand, with a shovel and a couple of buckets sitting in one corner

An agent’s safety net runs on two different mechanisms at once: what it has to ask you before doing, and what the operating system blocks outright even after you already approved something. Recovery sits underneath both. It is the ability to undo what happened anyway once the first two missed something. This post covers all three across Claude Code, OpenCode, and Codex, plus a third-party sandbox and a few container-based options.

The previous post ended with a question: what happens once one of those commands does something you did not want? This post picks up that question directly. It is also the seventh in the series, and the one the first post promised: “the permission rules that decide what the agent may do without asking are a topic for a later post.”

Asking first

All three tools let you tell the agent which actions need your sign-off and which it can take on its own. The mechanics are close enough that the concepts transfer, even though the config syntax does not.

Claude Code calls these permission modes. Six exist in total, though only three are in the default Shift+Tab cycle: default (labeled “Manual” in the UI since v2.1.200, also accepted as manual on the CLI), acceptEdits, and plan. You cycle through them with Shift+Tab during a session, set one at startup with claude --permission-mode plan, or pin a default in settings.json under "defaultMode".

The modes differ in what runs without a prompt. Manual auto-approves reads only, and everything else pauses for your sign-off. acceptEdits goes further: it auto-approves reads and file edits, plus a specific curated set of filesystem Bash commands (mkdir, touch, rm, rmdir, mv, cp, sed) scoped to your working directory. Anything outside that set still prompts.

plan behaves like Manual, allowing reads only, but it exists specifically for exploring before you decide to edit, the read-only mode the planning post covered. Reach for it when you are not yet sure what the change should touch.

Claude Code also lets you write fine-grained rules in settings.json under allow, deny, and ask lists. The syntax is tool-specific:

{
  "permissions": {
    "allow": ["Bash(npm run test *)", "Read(./.env)"],
    "deny": ["WebFetch(domain:example.com)"],
    "ask": ["Bash(git push *)"]
  }
}

mcp__servername__toolname is the pattern for MCP tools. Deny rules win over ask rules, which win over allow rules, regardless of how specific each one is.

OpenCode expresses this with a permission key in opencode.json, with values "allow", "ask", or "deny", wildcard support via * and ?, and per-tool granular objects:

{
  "permission": {
    "bash": {
      "*": "ask",
      "git *": "allow",
      "rm *": "deny"
    }
  }
}

The keys include read, edit, bash, webfetch, and lsp, among others. The two built-in agents are Build and Plan. Build has full tool access by default. Plan defaults edit and bash to ask, making it the read-only starting point for exploration, and you switch between them with Tab in the TUI. The key list runs longer than Claude Code’s three-list setup, but it buys finer-grained control per tool.

Codex approaches approval through sandbox modes and approval policies set at startup. The sandbox mode (--sandbox) is one of read-only, workspace-write, or danger-full-access. The approval policy (--ask-for-approval) is one of untrusted, on-request, or never. The recommended daily-driver combination is --sandbox workspace-write --ask-for-approval on-request.

Mid-session, /permissions adjusts what Codex can do without asking, and /approve approves a single retry after an automatic-review denial. Two flags handle startup, and two commands cover the rest of the session, with nothing else to configure.

The OS says no

Approval prompts are a conversation you can override in the moment by clicking through. OS-level sandboxing goes to the kernel, and the kernel enforces the boundary directly. Once a process is sandboxed, nothing running inside it can widen the restrictions.

Claude Code and OpenCode both added native OS-level sandboxing recently. Previously, each relied on the approval layer alone for containment, with no kernel-level enforcement behind it.

Claude Code now ships native sandboxing for Bash commands. On macOS it uses Seatbelt. On Linux and WSL2 it uses bubblewrap. Native Windows and WSL1 are not supported.

You enable it with the /sandbox command or a sandbox block in settings.json. From there you can specify filesystem allow and deny lists, plus a network domain allowlist: nothing is allowed by default, and the first use of a new domain prompts you. Credential protection sits alongside both, able to deny or mask specific files and environment variables from sandboxed commands.

OpenCode’s sandboxing landed in April 2026 as an experimental, opt-in feature, macOS-only for now, using Seatbelt (sandbox-exec) to isolate bash commands. Linux and Windows are not yet covered. It does not sandbox MCP servers, and it does not intercept commands typed inside an already-running interactive shell (a PTY session). A bash:unsandboxed permission lets a denied command retry outside the sandbox when you need one.

OpenCode’s own SECURITY.md was candid about the previous state: “OpenCode does not sandbox the agent… it is not designed to provide security isolation.” It held up until April 2026, when the experimental sandbox became the first step past it.

Codex uses Seatbelt via sandbox-exec on macOS, with a generated profile. On Linux and WSL2 it uses bwrap (bubblewrap) plus seccomp. Native Windows has an experimental restricted-token and AppContainer approach. The first post described the Linux mechanism as “Landlock and seccomp,” but the official security docs give the correct name as bwrap plus seccomp, a correction I’m making here.

On both platforms, the agent can edit inside your workspace and needs you to explicitly widen the sandbox to reach the network or write elsewhere. The sandbox is always active, and the approval policy sits on top of it as a separate, layered control that decides when to interrupt you.

nono

nono is a third-party CLI that wraps any agent process in an OS-level sandbox: Landlock on Linux and Windows (via WSL2), Seatbelt on macOS. The quickstart shows the basic pattern:

nono run --profile nolabs-ai/claude -- claude
nono run --profile nolabs-ai/opencode -- opencode
nono run --profile nolabs-ai/codex -- codex

Once applied, the kernel enforces the profile from outside the process. An agent that decides it needs broader access has no mechanism to get it, regardless of what you approved at the prompt layer.

Credential handling gets its own dedicated mechanism, separate from the sandbox’s own deny or mask rules. The default path is proxy injection: the agent talks to a local proxy over plain HTTP, and the proxy attaches the real credential as an HTTP header before forwarding the request over TLS, so the agent process never sees the key, not in its environment and not in memory.

A simpler environment-variable mode exists too, where nono pulls the secret from your system keystore (Keychain on macOS, Secret Service on Linux) right before it execs the process and zeroes the value out afterward, trading some of that guarantee for less setup. In both modes, the real credential lives in your keystore or a manager like 1Password, never in the agent’s own process.

nono also adds a network allowlist by domain with built-in profiles for common LLM providers, and a rollback feature that snapshots the filesystem before a session so you can review changes and accept or revert to the pre-session state.

None of the three agents’ native sandboxing extends to the tools the agent calls as separate child processes. A nono profile can let the agent invoke git while git itself only gets access to the repo and the git object store, or let it invoke gh (GitHub’s CLI client) while that token only works against specific GitHub API endpoints.

The agent’s own sandbox stops at the agent process, and nono’s per-tool policies add a separate restriction on top of each child process it spawns.

Profiles are distributed through a registry called nono-packs, which ships packs for claude, opencode, codex, and others. Some older documentation pages still reference an always-further namespace in examples, from before the registry migrated to nolabs-ai. I am actively contributing to the opencode pack there.

nono earns its place on top of what Claude Code, OpenCode, and Codex already ship. If you are on OpenCode, its own sandbox is still macOS-only and experimental, so nono is the only way to get kernel enforcement on Linux today.

Across all three, the per-tool child-process policies and the proxy-based credential handling, where the real key never enters the agent’s process, are not something any of the native sandboxes replicate. The added security and flexibility are worth the cost of a separate tool, especially since a properly configured profile stays transparent once set up, to the point where you stop noticing it’s there.

At Anaconda we lean on this enough to have built autobox, an internal tool that manages and abstracts nono profiles across the company, including one that fully sandboxes OpenCode while it is connected to Sesame, another tool I wrote. I covered Sesame in an earlier post: it is our MCP server for company context.

Our private nono registry carries the pack that puts this into practice. Sesame itself runs outside the sandbox, holding the real Jira, Slack, GitHub, and other service credentials it needs to gather context. The sandboxed OpenCode process only ever gets scoped access to Sesame’s own local endpoint, never to the credentials behind it, so a misbehaving agent or compromised package has nothing to steal and exfiltrate.

Containers

Before kernel-level sandboxing existed in any of these tools, the standard answer was a container. A container stops filesystem and process escape at the host boundary. A kernel sandbox restricts what happens inside the container. Running both adds genuine depth to the isolation story.

Claude Code has a full devcontainer setup with a Dev Container Feature you drop into any devcontainer.json, persistent volume mounts for auth state across rebuilds, an optional egress firewall script, and a reference configuration in the anthropics/claude-code repository that combines all of it. As the first post in this series mentioned, OpenCode also publishes a container image for that purpose.

agentbox is a third-party tool built specifically around this use case. It runs Claude Code, OpenCode, Pi, and Codex in isolated Docker or Podman containers, mounts a single host folder as the agent’s only filesystem view, and applies a default-deny egress allowlist that passes only the configured provider API hostname.

Two sync modes cover different workflows: mount gives the agent a live bind-mount of your directory, while snapshot copies the directory in, lets the agent work, diffs the result, and lets you review before copying changes back out. It ships as CLI, TUI, and GUI frontends on one shared engine.

code-container takes a rootless Podman approach (Docker as a less-tested fallback), with a default-deny iptables egress firewall that whitelists the Anthropic API, GitHub, npm, PyPI, and similar common targets. It supports Claude Code, OpenCode, Codex, and Gemini.

Its own README points to NVIDIA OpenShell for teams that need declarative security policies, a privacy-aware LLM proxy, and Kubernetes orchestration for multi-agent fleets. The pointer comes from code-container’s own comparison, and I have not independently verified the claims it makes about OpenShell.

My own tool in this space is padded-cell. It manages dev containers with a persistent per-directory home on macOS and Linux with either Docker or Podman, so a directory’s container and private home survive every rebuild and get reused on the next run. Its focus is keeping a dev environment around and reusable. It skips capability dropping, read-only filesystems, and network allowlisting entirely, so for isolation specifically, agentbox, code-container, or nono are the better fit.

Getting back

Approval prompts and sandboxes both have gaps. Recovery covers what slips through either one.

Claude Code keeps automatic checkpoints before each user prompt, up to 100 per session. You invoke the rewind menu with /rewind, or by pressing Esc twice when the prompt input is empty. The menu offers four options: restore both code and conversation, just the conversation, just the code, or summarize from or to a checkpoint.

Checkpoints persist across resumed sessions, are cleaned up after 30 days (configurable), and no other tool in this series matches that granularity.

OpenCode’s undo is built on git. /undo removes the most recent user message and all subsequent responses, along with any file changes the agent made, and /redo restores them, on keybinds ctrl+x u and ctrl+x r. Because the implementation uses git to revert file changes, the project has to be a git repository for this to work, and the reversal only covers file changes and conversation state, not arbitrary shell command side effects: if the agent ran a migration or pushed a branch, /undo does not touch those.

Codex ships no app-level undo, checkpoint, or rollback feature at all, a gap the first post already noted, and skips this layer entirely. The official position is that the OS sandbox plus git already cover this ground: the sandbox limits what can go wrong up front, and git recovers what happens anyway. Run it outside a git repo, and there is no recovery path at all.

How the three compare

Claude Code and OpenCode now both have native OS-level sandboxing, which changes the picture from what you might have read about either tool six months ago. Claude Code shipped its version first, and it already reaches Linux as well as macOS. OpenCode’s is newer, arriving in April 2026 as an experimental feature that covers macOS only so far. Both kept their undo stories intact while adding this.

Codex puts the most weight on the containment layer. That sandbox is enforced before any approval prompt even fires, with the approval policy layered on top as a separate check. Codex ships no first-party undo because the sandbox is the primary protection and git is the recovery path.

nono applies uniformly across all three from a single profile registry, and that shared mechanism keeps a multi-agent setup simple. One profile format and one credential-injection path cover Claude Code, OpenCode, and Codex alike, so a team running more than one of them only has to maintain a single boundary system.

Its per-tool child-process policies and credential injection are also more powerful and more flexible than the native sandboxing any of the three agents ship. A profile is a file you can version, diff, and script into a CI job or an unattended run. Secret injection is the clearest example of a capability the three agents lack entirely: nono’s proxy injection keeps the real API key out of the agent process, something none of Claude Code’s, OpenCode’s, or Codex’s own approval or sandbox layers attempt.

Practical defaults

Claude Code gets acceptEdits mode with a deny rule for anything you would not want touched without a prompt, plus the sandbox enabled with a conservative filesystem allowlist. For OpenCode, run the plan agent to explore and build to make changes, with bash: { "*": "ask", "git *": "allow", "rm *": "deny" } in opencode.json and the experimental sandbox on. Codex gets --sandbox workspace-write --ask-for-approval on-request, as the docs recommend.

On top of any of those, nono is a good addition for anything running unattended or touching credentials. The phantom credential injection alone is enough reason on a machine where the agent has access to real API keys.

Across all three tools, one habit matters most: stay in a git repo and commit before handing the agent anything substantial. Approval prompts can be clicked through and sandboxes have gaps, so a clean commit is the recovery path that works regardless of which tool you are on.

Play nice in the sandbox

Each layer does a different job. Approval prompts are the most visible control, and their effectiveness depends on you actually reading and declining them. OS-level sandboxing is the hardest boundary, newest for Claude Code and OpenCode and still limited to macOS on OpenCode’s side. What slips through both of those falls to recovery, though Codex skips it since its own sandbox already does that job by default. nono sits across all three layers at once: a single profile enforces the sandbox and handles credentials for whichever agent you’re running, which matters most once more than one of them is in play.

Containers are a coarser, longer-standing option that remains useful, especially for unattended runs or team setups where a consistent, reproducible environment matters more than per-developer configuration.

The next post covers MCP and custom tools, extending the agent’s reach past the filesystem and shell into your own services and data sources.