Security model and limits
This page says what Barry protects, what it doesn’t, and where each guarantee stops. Read it before you hand an agent a credential, expose a service beyond your Mac, or rely on a bound to stop a determined adversary. Every limit here comes from the code or from Barry’s own design records, and the page names them plainly so you can decide which risks to accept.
The short version: Barry draws one hard boundary, between your user account and the vault daemon’s separate account. Everything else that runs as you, including your agent sessions, shares one level of trust. Inside that level, Barry narrows what a session can see and call, and those controls stop ordinary mistakes well. They don’t make a hostile process running as you safe.
One local secret authorizes local HTTP
Section titled “One local secret authorizes local HTTP”Barry’s services talk over local HTTP, and a single instance secret,
BARRY_SECRET, authorizes a call. A caller presents it as a bearer token or as
an x-barry-secret header. Barry compares the two values in constant time and
refuses a length mismatch, and it rejects a request that repeats either header,
so a duplicate field can’t slip a second value past the parser. The services
read the secret on every request, so a rotation takes effect without a restart.
See
internal.ts
for the check.
No trusted-address shortcut exists. A request from 127.0.0.1 gets no special
treatment, because every hosting provider, whether a local name, a private
tunnel, or a public tunnel, connects from loopback. Trusting loopback would
trust the internet the moment a tunnel pointed at a service. A service that
starts without a secret to compare against answers 500 rather than accepting
anything, and an authorization check that times out answers 503.
Browser-facing services, such as the memory browser and the metrics dashboard,
can’t ask a page to carry the secret. You type it once on a sign-in page, and
the service sets an HttpOnly, SameSite=Strict cookie that holds a signed value
derived from the secret with its own 30-day expiry. Rotating BARRY_SECRET
signs every browser out.
The cost of one secret: every process running as your user can read it. The MCP configuration Barry writes for a harness carries the secret as a bearer header, so the harness process holds it too. Treat any process under your account as able to call Barry’s local services with full owner authority.
Device credentials carry scopes
Section titled “Device credentials carry scopes”A phone or another client doesn’t need the full secret. barry device pair
reviews a pending pairing request and approves it with a set of scopes, a list
of identity IDs, and an optional expiry. barry device list shows each
credential’s grants and usage, and barry device revoke withdraws one. The
device command reference lists the
flags.
A device credential resolves to a principal that names its user, its identity grants, and its scopes. The scope vocabulary covers sessions, events, actions, tools, identities, and artifacts, but the routes that accept devices today open reads: listing and reading sessions and their messages, subscribing to a session’s events, reading an identity, and reading artifacts. A device may read sessions but not start or control them, and the MCP server refuses device credentials outright. Barry checks a device credential against the identities store on each request, so a revocation takes effect on the next one.
Keep “owner” in its two senses apart. barry owner shows or binds the user who
owns this installation; the instance secret resolves to that user. A workflow
owner is the machine that holds a workflow’s state and hands steps to workers.
The vault runs as its own user
Section titled “The vault runs as its own user”The vault daemon runs as a separate operating-system user, _barryvault, and
listens only on a unix socket that the kernel guards at connect(). It ignores
BARRY_SECRET on purpose: every process running as you can read that secret, so
a vault that accepted it would open to exactly the processes the separate
account exists to exclude. The vault issues its own tokens instead, and keeps
the bearer token for its MCP endpoint apart from its signing key.
sudo barry setup --system creates the account and installs the daemon’s code
root-owned, so an ordinary user can’t rewrite what runs as _barryvault.
That separation protects the store’s integrity, the daemon’s memory, and the server code. It doesn’t keep secrets from an agent running as you:
- Each identity’s vault master password and client credentials live in your login Keychain without an access-control list, so any process running as you can read them, and the master password alone can log in and decrypt.
- Your user belongs to the
_barryvaultgroup by design, so your own processes, agent sessions included, can reach the vault’s API over the socket. - Anything that reaches root crosses the boundary.
The vault README walks through each of these, and ADR 0042 records that moving key custody into the daemon’s account remains a design direction, not finished work.
Treat a declared secret as readable
Section titled “Treat a declared secret as readable”When a session starts, Barry resolves the secrets an identity declares and hands each tool the values that tool names. That routing keeps a value out of tools that never asked for it, but the session’s own process sits under your user. In the words of the secrets guide: treat a declared secret as readable by the session you hand it to. A bag service that opens a vault can also decrypt every item in that vault, so give a service its own vault when that matters. The secrets walkthrough covers declaring and scoping them.
Three things never reach the agent through resolution: the vault master password, the vault client credentials, and any secret the identity didn’t declare. Barry also strips every declared credential name from the environment it passes the harness, so an ambient key in your shell doesn’t leak into a session.
Tool-scoped secrets and re-authorization
Section titled “Tool-scoped secrets and re-authorization”In the MCP server, each tool handler receives only the secrets its definition
lists, and a call fails with a missing-secrets error when one of them didn’t
resolve. The SDK’s scoped tool invoker, which workflow tool activities build
on, goes further.
scoped-invocation.ts
authorizes the call, fetches only the credentials the tool declares, and then
authorizes it again. If the policy fingerprint changed while it waited, it
refuses with “execution policy changed during invocation admission.” It also
refuses when the run’s approved fingerprint doesn’t match, when the tool’s
read or write classification or its contract hash changed, or when the
destination can’t enforce the run’s resource restrictions. A stored receipt
never bypasses a revocation: the invoker checks current authorization even
before it replays a finished result.
Workers open one route
Section titled “Workers open one route”A worker machine authenticates with its own credential, not the instance
secret. That credential opens only POST /api/worker. It opens no console, run
inspection, start, session, identity, or store API. The owner machine stores a
hash of it and never sends a worker its instance secret, an identity’s whole
environment, or the contents of a vault.
A task runs on a worker only when that worker can enforce everything the task requires: its credentials, workspace, runtime, and bounds. Versioned Node bag jobs that use no files and declare no secrets qualify as they are. Eligible scripts and agent actions run remotely with credentials provisioned for that worker or a narrowly scoped delegated credential, and they declare their workspace up front. Anything else stays on the owner machine, or stays visibly blocked.
Scopes narrow what a worker credential can claim. They don’t verify results or
sandbox a hostile worker operator. A stolen credential can claim any job in its
scope, read those jobs’ inputs, and report fabricated results, so invite only
machines you operate. barry workers invite --jobs sets the scope, and
barry workers revoke refuses the worker’s next call.
Distributed worker trust
covers the full threat model, and
Distributed nodes
explains the protocol.
Enforcement depends on the harness and mode
Section titled “Enforcement depends on the harness and mode”Barry enforces a bound through more than one layer, and the harness and its launch mode decide which layers apply. The bounds reference lists the fields; this section covers how far each one reaches.
- Barry’s own tools. The MCP server filters tools by trait and bound and checks every call, whatever the harness.
- Other MCP servers. A session starts with a strict MCP configuration that
loads only the servers Barry configured.
<identity> session start --relaxmerges your user-level MCP servers in, and their tools bypass Barry’s tool filter and call-time guards. - Native tools. Native tools stay off unless a trait grants them, and a
bound takes them back. A mode declares which native tools it can control.
Cursor leaves some tools uncontrolled, and in the guest and instrumented
modes of Antigravity and Codex an operator can lift the hook gate with
/hooks. A bound that would need to take back an uncontrolled tool refuses the launch. - Per-call gating. Claude Code’s guest and instrumented modes run the
vendor’s own interface with no Barry
PreToolUsehook, so their native tool settings act as static restrictions, not per-call authorization. The Claude Code modes record this beside the evidence for each mode. - Guards. File, shell, and network guards inspect structured tool parameters and recognizable shell commands. Interpreter code, arbitrary scripts, and URLs built at runtime fall outside them. The SDK bounds notes call them best effort against ordinary mistakes.
- Egress sandbox.
network.enforce: sandboxruns the agent behind a proxy that filters outbound traffic by hostname, and only Claude Code’s hosted mode declares one. The proxy can’t tell an HTTP read from a write to the same host, so partial action denials still rely on guards.
Adversarial protection needs the sandbox, and even the sandbox governs only egress. Bag tools that make requests from their own process answer to their trait and tool grants, not to the agent’s network bound.
Rules that fail closed
Section titled “Rules that fail closed”Where Barry can’t prove something is safe, it refuses rather than guesses:
- A session the MCP server can’t resolve gets no tools, and the server retries on a backoff. A connection that names no session gets only the default-trait grant, never the full toolset, and one that names an unknown session gets a 403. ADR 0018 records why.
- Bag health reports distinguish “nothing needs attention” from “Barry couldn’t find out,” so an outage never reads as a clean bill of health (ADR 0013).
- A session whose required credentials have no source, or won’t read, doesn’t start.
<identity> session start --boundstops when Barry can’t record the session with its bound, instead of running unbounded.- A requested egress sandbox that the harness or model endpoint can’t enforce refuses the session, and the sandbox fails rather than runs unsandboxed when it can’t start.
- In Codex, a missing, failing, slow, or malformed policy script produces an explicit deny. ADR 0029 also records the edge: deleting the launcher or disabling hooks can still let a call through.
- A store stamped by a newer build refuses to open, so an older build never reads a schema it doesn’t fully understand.
For the processes and stores these rules protect, see Services and stores.