DEV Community

Cover image for Introducing Sandy: Kernel-Enforced Sandboxing with Behavioral Controls
Jens Ernstberger
Jens Ernstberger

Posted on

Introducing Sandy: Kernel-Enforced Sandboxing with Behavioral Controls

AI coding tools are most useful when they can work directly in a real repository. They inspect files, run builds, invoke package managers, and launch subprocesses. That access is also the source of their risk.

A process started from your terminal normally inherits much of your user account’s authority. A coding agent may need one project directory, but the operating system does not automatically stop it from probing unrelated files, local credentials, or configuration elsewhere on the machine. Prompt injection, an unsafe dependency, or a simple agent mistake can therefore have consequences far beyond the task you intended.

Sandy narrows that authority before the agent begins. It is an open source macOS process sandbox that compiles explicit capabilities into a Seatbelt policy and applies that policy to the entire process tree.

The practical goal is simple: keep the native local workflow, but replace ambient access with a boundary declared at launch.

Start an Agent Inside a Boundary

Install Sandy through Homebrew:

brew install kontext-security/tap/sandy
Enter fullscreen mode Exit fullscreen mode

Confirm that sandbox enforcement is available:

sandy doctor
Enter fullscreen mode Exit fullscreen mode

Then start an agent:

sandy run -- claude
Enter fullscreen mode Exit fullscreen mode

The command still runs in the foreground and uses the normal terminal. There is no container image to build and no modified version of Claude Code to install. The difference is that Sandy prepares and activates an operating-system sandbox before Claude Code executes.

The same wrapper works with Codex and OpenCode:

sandy run -- codex --sandbox danger-full-access
sandy run -- opencode
Enter fullscreen mode Exit fullscreen mode

For Codex, danger-full-access delegates containment to Sandy instead of placing a second sandbox inside the first one.

Sandy is not limited to AI agents. Any compatible command can be launched through it:

sandy run -- cargo test
sandy run -- python script.py
Enter fullscreen mode Exit fullscreen mode

Declare the Access a Task Actually Needs

Sandy treats filesystem and network access as launch-time capabilities. Additional directories are granted explicitly rather than inherited implicitly.

Give a process read access to a shared library:

sandy run --read ../shared-library -- claude
Enter fullscreen mode Exit fullscreen mode

Allow writes to a dedicated output directory:

sandy run --read-write ~/Downloads/output -- codex --sandbox danger-full-access
Enter fullscreen mode Exit fullscreen mode

Disable network access for a task that should remain local:

sandy run --block-net -- cargo test
Enter fullscreen mode Exit fullscreen mode

These options define the process boundary before any target code runs. The target cannot expand that boundary later, and its child processes receive the same restrictions.

To inspect the fully resolved configuration without starting the target, use:

sandy run --dry-run -- claude
Enter fullscreen mode Exit fullscreen mode

The result is a versioned JSON document. It shows the selected profile, normalized paths, grants, blocks, environment decisions, and other launch details. The dry_run_schema_version field makes the output suitable for review and automation without depending on an undocumented format.

A Launch Contract, Not a Runtime Suggestion

Sandy’s security model is built around decisions that are finalized before execution. It resolves the executable, canonicalizes paths, selects a profile, validates the environment, and compiles typed capabilities into the sandbox policy.

This design has several important consequences:

A failed sandbox means a failed launch

If validation fails or Seatbelt cannot be applied, Sandy does not start the target. It does not continue in an unrestricted mode or downgrade the failure to a warning.

The target never runs ahead of enforcement

Sandy uses a fresh bootstrap process to apply the sandbox before replacing itself with the requested command. The target does not receive an execution window outside the policy.

Policy input stays constrained

The policy compiler accepts supported, typed capabilities. It does not accept arbitrary Seatbelt source from the command line. This prevents a caller from smuggling unvalidated sandbox rules into the launch.

Restrictions follow subprocesses

Build tools, shell commands, language runtimes, and other descendants remain in the same sandbox. Spawning another process does not create a route back to the parent user account’s full authority.

Built-In Profiles for Coding Agents

Different agents need different local files to function. Sandy recognizes Claude Code, Codex, and OpenCode from the command being launched and selects an appropriate built-in profile.

Profiles grant supported application requirements while continuing to protect sensitive configuration. They are versioned documents included with Sandy, not editable bundles of raw sandbox language.

If an agent is launched through a wrapper or alias, select its profile directly:

sandy run --profile codex -- my-codex-wrapper
Enter fullscreen mode Exit fullscreen mode

Commands without a recognized agent identity use the generic profile.

By default, unrelated sensitive locations remain outside the grant. This includes SSH material, cloud credentials, Keychains, and shell configuration. An agent working in a repository should not gain access to those files merely because it was started by the same user who owns them.

Containment and Behavioral Authorization Are Separate Controls

Filesystem containment and action authorization solve different problems.

Sandy answers the operating-system question: which resources can this process tree reach?

A behavioral control answers a different question: should this particular action be approved in the current context?

Keeping those responsibilities separate makes each layer easier to reason about. Sandy does not become a credential store, a tool-call classifier, an approval service, or an audit database. Instead, it can preserve the registrations and configuration required by systems that provide those functions.

Sandy includes first-class integration paths for Kontext and Numbat. Their relevant hooks can remain available to the agent while the files that configure them stay protected from modification.

For example:

sandy integrations setup kontext --agent claude
sandy run --kontext -- claude
Enter fullscreen mode Exit fullscreen mode

With --kontext, a missing or invalid required integration prevents launch. This turns behavioral authorization from an optional convenience into part of the declared execution contract.

The same composition is useful with network restrictions:

sandy run --block-net --kontext -- claude
Enter fullscreen mode Exit fullscreen mode

The agent receives a network block while Sandy allows the exact local path needed for the Kontext integration.

What Happens Between run and the Target

The launch path is intentionally short:

Terminal
  |
  |  sandy run --read-write ~/work -- claude
  v
Sandy parent
  |
  |  Resolve executable, profile, paths, environment, and capabilities
  |  Validate and compile the launch contract
  v
Fresh bootstrap process
  |
  |  Remove the launch manifest
  |  Apply the macOS Seatbelt policy
  |  Execute the requested target
  v
Claude Code and its child processes
  |
  |  Access granted project resources
  |  Receive denials for resources outside the policy
  |  Remain subject to the same restrictions as subprocesses are created
  v
Exit status returned by the parent
Enter fullscreen mode Exit fullscreen mode

The trusted parent remains outside the sandbox and waits for completion. If preparation or policy application fails at any point, the target is not executed. Otherwise, the parent returns the target’s exit status to the calling shell.

Why Use a Native Process Sandbox?

Containers remain a strong choice when a task needs an isolated filesystem image, a reproducible runtime, service orchestration, or deployment parity. Sandy targets a different workflow: running a local command against a local checkout with less setup.

There is no image lifecycle, volume mapping, daemon, or guest environment. The agent continues to use the tools and project files already present on the Mac, subject to the capabilities Sandy grants.

This makes Sandy useful when a container would add more operational machinery than the task requires. It is not a universal replacement for containers or virtual machines. It is a focused boundary for local process execution.

Practical Patterns

Keep an agent inside one project

sandy run --read-write ~/Documents/my-project -- claude
Enter fullscreen mode Exit fullscreen mode

Compile with separate source and output permissions

sandy run --read src --read-write target -- cargo build
Enter fullscreen mode Exit fullscreen mode

Test a package without external network access

sandy run --block-net -- npm test
Enter fullscreen mode Exit fullscreen mode

Review the resolved sandbox before execution

sandy run --dry-run -- claude
Enter fullscreen mode Exit fullscreen mode

These examples use the same underlying model: describe the minimum useful authority, inspect it when needed, and launch the process only after the contract is valid.

Current Platform Scope

Platform Enforcement mechanism Availability
macOS Native Seatbelt sandbox Filesystem and network controls available
Linux Not yet supported Planned
Windows Not yet supported Planned

Sandy currently relies on Apple’s sandbox(7) mechanism. The project’s assumptions, protected assets, trust boundaries, and known limitations are documented in THREAT_MODEL.md.

Project Status

Sandy is available under the MIT license. The 0.1.x releases are experimental and have not undergone an independent security audit, so the threat model and implementation should be reviewed before using Sandy for sensitive workloads.

Project links:

Give Local Agents Less Ambient Authority

The question is not whether a coding agent intends to stay inside your project. The stronger question is whether the operating system will stop it when it does not.

Sandy lets you answer that question before launch. It converts a local command into a process tree with explicit filesystem and network capabilities, protects the configuration of complementary runtime controls, and refuses to proceed when the boundary cannot be established.

Try it on a non-sensitive repository, inspect the dry-run output, and test the denials you expect to hold. If you find a bug or have a use case the current profiles do not cover, open an issue on GitHub.

Top comments (0)