Access Control

Dawn has three independent layers that each answer a different question about a tool call. They compose — none of them substitutes for another, and most production apps that touch anything sensitive (shell, network, prod data) end up using all three together.

LayerQuestion it answersDocs
Tool scopingWhich tools can the model call?Tools
Permissions / HITLWhether a given call runsPermissions
Execution sandboxWhat an allowed, approved call can actually touchSandbox

Tool scoping — which tools

agent({ tools: { allow, deny, approve, constrain } }) controls the surface offered to the model. deny revokes a tool so it's never wired into the generated entry; allow grants back a withheld capability tool (subagents start least-privilege, so this is how you hand one readFile or runBash); deny wins when a name appears in both. This is enforced at composition time — a denied tool is never wired in, so the model has no way to call it, valid or not.

src/app/ops/index.ts
export default agent({ model: "gpt-5", systemPrompt: "…", tools: { deny: ["runBash"] } })

Scoping controls the surface, not what a granted tool does once invoked — a granted writeFile can still write anywhere its implementation permits. See Tools.

Permissions — whether a call runs

Two gates are on by default for the built-in workspace tools: runBash commands are matched against allow/deny patterns, and filesystem paths outside workspace/ are permission-gated. An unmatched ("unknown") call pauses the run and asks a human, unless permissions.mode is set to non-interactive (fail-closed) or bypass (dev/test only).

Two more gates ride the same interrupt machinery: tools: { approve: [...] } requires human approval before any named tool call, and memory: { writes: "ask" } gates belief-contradiction memory writes. See Permissions for the interrupt payloads and resume flow.

Execution sandbox — what a call can touch

Even a tool that's allowed and approved still runs somewhere. By default that's the local workspace/ directory on the host. Adding a sandbox key to dawn.config.ts routes every readFile, writeFile, listDir, and runBash call for a thread into an isolated environment instead — filesystem, shell, and (optionally) network. Dawn ships a Docker reference provider and a Kubernetes provider behind the same SandboxProvider contract. See Sandbox.

How they compose

A route that lets the model run shell commands, gated by approval, inside an isolated environment stacks all three — sandbox in dawn.config.ts decides what runBash can touch, and the route's tools block decides which tools are offered and which need approval:

dawn.config.ts
import { config } from "@dawn-ai/cli"
import { dockerSandbox } from "@dawn-ai/sandbox"
 
export default config({
  sandbox: { provider: dockerSandbox({ image: "node:22-slim" }) }, // what runBash can touch
})
src/app/ops/index.ts
export default agent({
  model: "gpt-5",
  systemPrompt: "…",
  tools: { allow: ["runBash"], approve: ["runBash"] }, // which tools + whether each call runs
})

Related