Security model
Cusp's trust boundaries and the controls at each one — the permission gate, the OS sandbox and network allowlist, secrets, the local daemon, the desktop app, and cloud.
This page describes how Cusp keeps an agent within the limits you set: who and what Cusp trusts, where the boundaries are, and which control enforces each one. For what data leaves your machine, see Security and privacy.
Principles
- One gate. Every side effect from every kind of run (your session, a subagent, a race lane, a review, a crew member, an SDK script) goes through the same permission engine. Crew policies, CLI flags and SDK callbacks are inputs to it, not alternatives.
- Content is data. Text the agent reads (files, web pages, issues, CI logs, tool output, messages from other agents) never carries your authority.
- Fail closed. A check that can't run results in a prompt or a refusal, never a silent allow.
- Least privilege by default. Shell commands run sandboxed, with no network, and without your credentials in their environment.
Trust boundaries
| Boundary | What's on the other side | Main controls |
|---|---|---|
| The model | Untrusted output: any tool call it proposes might be wrong or induced by injected text | Permission engine, protected paths, sandbox |
| The repository | Code and configuration you may not have written | Workspace trust; repositories can't weaken the sandbox |
| Shell commands | Arbitrary programs | OS sandbox, network proxy, scrubbed environment |
| The network | Any host | Sandbox network policy, domain allowlist |
| Other local programs | Anything running as you on the same machine | Daemon bearer token, loopback-only binding, origin checks |
| Remote devices | Your other devices, through Cusp's cloud | Scoped tokens that can't escalate privileges |
| Extensions | Skills, hooks, MCP servers, plugins | Same gate; hooks and MCP servers don't receive the daemon token |
The permission gate
Each tool call is checked against hooks, your rules and the session's mode before it runs. See Permissions for modes and rule syntax. On top of your rules:
- Protected paths always need a person, in every mode:
.git, agent configuration (.cusp,.claude,.codex, …), shell startup files and configuration that executes code. - Secret stores (
~/.ssh,~/.aws, keychains, CLI tokens, browser profiles, Cusp's own data directory and others) can't be read by the agent's file tools without your approval. The same list is denied to sandboxed commands. - Shell parsing. Compound commands, substitutions,
sh -c,evaland wrappers such assudoorenvare parsed, so a dangerous command can't ride on an allow rule for a safe one. - Auto mode's classifier sees your messages and the proposed action, never tool output, so injected text can't argue it into approving. After 3 blocks in a row or 20 in total, auto mode stops and asks you.
- Agent messages carry no authority. A message from another agent arrives as information. A message to a session running with more autonomy than the default waits for your approval.
- Administrators can turn off bypass and auto modes (
disableBypassPermissionsMode,disableAutoMode) and block reads outside the working directories (blockReadsOutsideWorkingDirectories).
The sandbox
Shell commands the agent runs (Bash, Process, Terminal, and helpers such as type checkers and language servers) run inside the operating system's sandbox: Seatbelt on macOS and bubblewrap on Linux. It's on by default. See Sandbox for settings.
By default a sandboxed command:
- Can write only to the workspace and temp directories. Git configuration and hooks, and agent and editor configuration inside the workspace, stay read-only.
- Can read everything except secret stores.
- Has no network access, including localhost. Open specific local ports with
sandbox.network.allowLocalPorts. The daemon's own port stays blocked (on Linux, only while the network is fully blocked; otherwise the daemon relies on its token). - Runs with provider keys and Cusp's credentials removed from its environment.
Fail closed. If the sandbox can't be applied (bubblewrap isn't installed, or the platform has none, as on Windows today), shell commands are refused. You can approve a single unsandboxed command, or set sandbox.failIfUnavailable: false in your user settings to run unsandboxed.
Repositories can't weaken it. Settings that turn the sandbox off or widen it (enabled: false, failIfUnavailable: false, unsandboxed or excluded commands, localhost ports, Unix sockets, extra readable or writable paths) are honored only from your user settings, managed settings or --settings, never from a project's own files.
Network allowlist
With sandbox.network.allowedDomains set, sandboxed commands reach the network only through Cusp's filtering proxy:
- Entries can be exact hosts (
registry.example.org), subdomain wildcards (*.example.com, which doesn't matchexample.comitself),*, or IP addresses, each with an optional:port. deniedDomainswins overallowedDomains.- A host on neither list prompts you: allow once, allow for the session, or deny.
- Private and local addresses are reachable only through an explicit host or IP entry, and DNS is resolved once, in the proxy.
- A refused request gets a 403 with an
X-Cusp-Blockedheader and a body starting "Blocked by Cusp network policy:", so the agent can tell you which host it needs.
On Linux the allowlist is advisory for now. Traffic is pointed at the proxy through HTTP_PROXY and related variables, but there's no network namespace isolation yet, so a program that ignores proxy settings can reach the network directly. On macOS the sandbox profile enforces it.
Audit log
Every permission verdict, every gate decision and every proxied network connection is appended to <data directory>/audit/<session>.jsonl, readable only by you.
Secrets
- Desktop app. Provider keys are stored in the OS credential store under the service
run.cusp.desktop. Cusp's database holds only a reference. Values are capped at 16 KiB and never appear in logs or error messages. - Daemon. Keys are resolved from a reference (
env:NAMEor a keychain entry) when needed, held only in memory, and never logged, stored or returned by any API. - Child processes. Shell commands, hooks and MCP servers don't inherit provider keys or the daemon's token.
The local daemon
The daemon is the agent runtime. The desktop app, CLI, VS Code extension and SDKs are all clients of it.
- Loopback only. It binds
127.0.0.1. Listening on another interface requires an explicit--allow-remoteflag, meant for cloud sandboxes. - Bearer token. A new random token is generated on every launch and checked in constant time on every request. The exceptions are a health check and the endpoint crew members use for their team tools, which takes its own per-session token instead.
- Discovery file. While running, the daemon writes its address and token to
daemon.jsonin its data directory, with permissions that let only you read it (file0600, directory0700), and removes it on exit. That's how the CLI and editor extensions find a running daemon. - Origins. Browser requests are accepted only from the desktop app's own origins, on both HTTP and WebSocket connections.
- Scoped access. Requests relayed from your other devices through Cusp's cloud use a separate token with a narrower scope. They can drive sessions and answer prompts, but can't switch a session to bypass or auto mode, change permission rules, settings or providers, or save "always allow" rules. Terminals are reachable remotely only if you enable
remote.terminallocally.
The desktop app
- Loads only its bundled interface, never remote content.
- A strict content security policy limits it to itself and the loopback daemon (
object-src,base-uriandform-actionare'none'). - Every call from the interface to the native shell passes through an isolation layer in a sandboxed frame, encrypted and checked against an allowlist. Each native command needs an explicit permission; unused ones are stripped at build time.
- The daemon token is redacted from debug output and never written to disk or logs by the app.
- Files open in your editor through fixed URL schemes or the OS default handler; the app never spawns a process for this.
GitHub automation
When Cusp acts on GitHub (the GitHub Action, pull request monitoring, auto-fix):
- Only people with write access can trigger or steer it with a mention.
- Pull request text and CI logs are passed to the agent as untrusted data.
- Turning on auto-merge needs your approval.
- Pull requests from forks run read-only in the Action.
Cloud
Cusp's cloud is coming soon. Its security design: device sign-in with a one-time code, API keys with narrow scopes, keys and tokens stored only as hashes, provider credentials encrypted with envelope encryption, and cloud sandboxes whose provider and GitHub credentials are added by an egress proxy and never enter the sandbox. This section will be expanded when the cloud is available.
Reporting a vulnerability
See Security and privacy.