- MCP Server ToolsUpdated
- Managed MCP with chat()Updated
- Manual MCPUpdated
- MCP Type GenerationUpdated
- MCP Client InputUpdated
- MCP Server Tools: AdvancedNew
- MCP EventsNew
- MCP AppsUpdated
- Serve Tools over HTTPUpdated
- Serve Resources and PromptsUpdated
- Ask for InputUpdated
- Draft Text from a ToolUpdated
- MCP Server TasksUpdated
- MCP Server AuthUpdated
- MCP Server SessionsUpdated
- MCP Server on stdioUpdated
Persistence (Advanced)
- GenerationsUpdated
- Realtime Voice ChatUpdated
- Text-to-SpeechUpdated
- TranscriptionUpdated
- Transcription: AdvancedNew
- Voice CreationUpdated
- Audio RecordingUpdated
- Audio GenerationUpdated
- Image GenerationUpdated
- Image Generation: AdvancedNew
- Video GenerationUpdated
- Video Generation: AdvancedNew
- Live GenerationUpdated
- World GenerationUpdated
- Generation HooksUpdated
- OverviewUpdated
- Quick StartUpdated
- ProvidersUpdated
- HarnessesUpdated
- Harness AuthUpdated
- WorkspaceUpdated
- ToolsUpdated
- PolicyUpdated
- Lifecycle & SnapshotsUpdated
- Portable SnapshotsUpdated
- Keep Files After ReloadUpdated
- Save a Named VersionUpdated
- Branch From a VersionUpdated
- Send a Frozen FileUpdated
- Let the Agent Save and ForkUpdated
- Pick Which Files to KeepUpdated
- What a Snapshot StoresUpdated
- Instance DurabilityUpdated
- Durable RunsUpdated
- EventsUpdated
Sandboxes (Advanced)
- Runtime ContextUpdated
- Bring Your Own KeyUpdated
- Debug LoggingUpdated
- Multimodal ContentUpdated
- Files APIUpdated
- Per-Model Type SafetyUpdated
- Runtime Adapter SwitchingUpdated
- Tree-ShakingUpdated
- Extend AdapterUpdated
- Extend Adapter: AdvancedNew
- Typed Pre-Configured OptionsUpdated
- Test with a Fake ModelUpdated
- Prompt CachingUpdated
- Mid-Conversation ChangesUpdated
- Approval Flow ProcessingUpdated
Policy
A policy allows a command, asks for approval, or blocks it. Use it to stop sudo, a repo delete, or a call to an unknown host.
A policy holds those rules. defineSandboxPolicy() writes them once, and each harness adapter maps them to its own permission system.
Add a policy#
- Write the rules.
- Pass them as policy to defineSandbox.
import {
defineSandbox,
defineSandboxPolicy,
defineWorkspace,
githubRepo,
} from '@tanstack/ai-sandbox'
import { dockerSandbox } from '@tanstack/ai-sandbox-docker'
const policy = defineSandboxPolicy({
commands: {
allow: ['pnpm test', 'pnpm typecheck', 'git diff'],
ask: ['pnpm install', 'curl *'],
deny: ['sudo *', 'rm -rf *'],
},
capabilities: { fileWrite: 'allow', network: 'ask' },
default: 'ask',
})
export const sandbox = defineSandbox({
id: 'repo-agent',
provider: dockerSandbox({ image: 'node:22' }),
workspace: defineWorkspace({
source: githubRepo({ repo: 'owner/app' }),
setup: ['corepack enable', 'pnpm install'],
}),
policy,
})Every run that uses this sandbox now follows the policy, including the bridged tools. The policy does not check the workspace setup commands. They come from your own config and run as written.
Decisions#
Each command or capability gets one of three decisions:
| Decision | What happens |
|---|---|
| allow | The agent runs it. |
| ask | The agent pauses. The harness sends an approval request, and the client answers it before the action continues. |
| deny | The action is blocked. The agent cannot run it. |
Commands#
commands has three lists of patterns: allow, ask, and deny. A pattern matches the command that the agent is about to run.
- * is a glob. curl * matches every curl command. sudo * matches everything run through sudo.
- A string with no *, such as pnpm test, matches only that command.
Put your named workspace scripts (pnpm test, pnpm build) in allow. They give the policy stable names to match.
Capabilities#
capabilities uses the same decisions for broad abilities:
import { defineSandboxPolicy } from '@tanstack/ai-sandbox'
const policy = defineSandboxPolicy({
capabilities: {
fileWrite: 'allow', // the agent can edit the working tree
network: 'ask', // pause for approval before any outbound request
},
})This is the backstop. A network command that is not in your commands lists still needs approval under network: 'ask'.
If you omit a capability, it uses the policy default. Some providers also enforce network: 'deny' when they create the sandbox. See Providers (Advanced).
Precedence: deny > ask > allow#
When more than one rule matches, the strictest one wins:
- If a matching rule says deny, the action is blocked.
- Else, if a matching rule says ask, the action needs approval.
- Else, if a rule says allow, the action runs.
- If nothing matches, the default applies.
So you can allow a broad family and block one case:
import { defineSandboxPolicy } from '@tanstack/ai-sandbox'
const policy = defineSandboxPolicy({
commands: {
// Every `curl` is allowed...
allow: ['curl *'],
// ...but `deny` wins, so this host is always blocked.
deny: ['curl * internal.example.com*'],
},
default: 'deny',
})The default#
default is the decision for anything that no rule matches. If you omit it, it is 'ask'.
- 'allow': only what you ask about or deny is gated. Good for trusted dev loops.
- 'ask': unknown actions pause for approval. A safe middle.
- 'deny': the agent runs only what you allow. The strictest choice, for untrusted or production runs.
How ask reaches the user#
When the agent tries an ask action, the harness pauses it and sends an approval request into the run stream. Your client approves or rejects it. Until the client answers, the action waits.
Use ask for actions that are usually fine but sometimes dangerous, such as pnpm install with a new dependency, or an outbound curl. The human stays in the loop, and you do not have to list every safe command.
Advanced#
How adapters map a policy#
Each harness adapter translates the same policy into its own permissions:
- Grok Build: the permission flags of the grok CLI.
- Claude Code: its permission rules (allowed, ask, and denied tool and command rules).
- Codex: its approval and sandbox settings.
- Other harnesses: whatever native gate they have.
If a harness cannot express a rule, it skips that rule with a warning. The run does not fail.
A deny-only list with default: 'allow' stays permissive on Grok Build and Codex. Those harnesses keep auto-approve and do not enforce commands.deny. The outer sandbox is the isolation there. If you need a deny rule for each command, use Claude Code.
Some providers run as a non-root user, so package installs in setup need sudo. Do not deny sudo * on those providers. See Providers (Advanced) for the list, and Workspace for how to write setup commands.
Network policy on Docker Sandboxes#
Most providers leave capabilities.network to the harness. sbxSandbox() also enforces it on the host HTTP and HTTPS proxy (networkPolicy: true).
sbx policy can allow or deny hosts. It has no ask. Deny wins over allow, so network: 'deny' becomes an allowlist, not a full deny.
| TanStack capabilities.network | What sbxSandbox() writes |
|---|---|
| No policy, and no allowNetwork or denyNetwork | If the policy list is empty, sbxSandbox() runs sbx policy init deny-all. A known adapter (grok-build, claude-code, codex) then allows its model API host and localhost for this sandbox. Unknown adapters stay on the machine preset. |
| No policy, and allowNetwork | Allow the model API host (for a known adapter), localhost, and allowNetwork for this sandbox. Then apply denyNetwork. |
| No policy, and denyNetwork only | Deny those hosts for this sandbox. Allow stays empty: no automatic hosts and no localhost. This adds deny rules on top of the machine preset. |
| allow | Allow **, then apply denyNetwork. |
| deny | Allow the model API host, localhost, and allowNetwork. Then apply denyNetwork. |
| ask (or the policy default when network is unset) | The same allowlist as deny. The harness still asks for tools and commands. |
Model hosts that are allowed automatically:
- grok-build: api.x.ai
- claude-code: api.anthropic.com
- codex: api.openai.com
- opencode or an unknown adapter: none
If the allowlist is empty under deny or ask, create throws. Pass allowNetwork, or use grokBuildText, claudeCodeText, or codexText.
The guest calls host.docker.internal for the tool bridge. The sbx proxy changes that host to localhost before the policy match. sbxSandbox() adds localhost when it writes a real allowlist. It does not add it for denyNetwork only.
import { sbxSandbox } from '@tanstack/ai-sandbox-docker'
import { defineSandbox, defineSandboxPolicy } from '@tanstack/ai-sandbox'
const sandbox = defineSandbox({
id: 'repo-agent',
provider: sbxSandbox({
allowNetwork: ['*.npmjs.org', 'registry.npmjs.org'],
}),
policy: defineSandboxPolicy({
capabilities: { network: 'deny' },
commands: { allow: ['pnpm test'], deny: ['sudo *'] },
}),
})Command rules and fileWrite stay with the harness, because sbx does not see command lines.