Bounds
A bound restricts a session. It removes tools, write access, file paths, shell commands, and network destinations from what the session’s traits granted, and it never grants anything. A session with no bound has everything its traits grant.
The schema is AgentBoundSchema in
sdk/lib/src/bounds/types.ts.
It rejects unknown keys at every level, so a misspelled field fails instead of
denying nothing.
Fields
Section titled “Fields”| Field | Type | Restricts |
|---|---|---|
deniedTools |
list of strings | Tools. Each entry is a namespace or a tool name |
deniedAccess |
list of strings | Write access: write for every tool, or a namespace or tool name |
files.deny |
list of patterns | Paths that match any pattern |
files.allowOnly |
list of patterns | Every path that matches no pattern. deny wins when a path matches both |
bash.deny |
list of strings | Shell commands that contain the string. Prefer denyPrograms |
bash.denyPrograms |
list of strings | Shell commands that run the program, or program:subcommand |
network.actions |
list of action tags | Network operations. See network actions |
network.domains |
list of host patterns | Destinations to deny |
network.allowDomains |
list of host patterns | The only destinations the egress sandbox lets through. Needs enforce: sandbox |
network.enforce |
guard or sandbox |
Enforcement level. Default guard. See enforcement levels |
For example, this bound denies writes and pushes, and blocks secret files:
deniedAccess: [write]files: deny: ["*.env", "~/.ssh/**"]network: actions: [git:push]Patterns
Section titled “Patterns”| Where | Pattern | Matches |
|---|---|---|
files |
*.ext |
Any path that ends in .ext |
files |
dir/** or dir/* |
Any path that contains dir/. A leading **/ means any depth |
files |
Another pattern with * |
* matches any characters, anywhere in the path |
files |
No * |
Any path that contains the text |
files |
Leading ~ |
Your home directory |
domains |
example.com or *.example.com |
example.com and every subdomain |
allowDomains |
example.com |
Only example.com |
allowDomains |
*.example.com |
Every subdomain, not example.com itself |
denyPrograms |
git |
Every git command |
denyPrograms |
gh:pr:create |
Only that subcommand |
denyPrograms |
gh:api:write |
gh api with -X POST, PUT, PATCH, or DELETE, or with -f or -F fields |
Barry checks file patterns against both the path as written and its absolute form.
denyPrograms matches every command in a line, across &&, ||, ;, |,
subshells, and command substitutions. It looks through wrappers such as env,
sudo, xargs, timeout, sh -c, npx, and uvx. Barry denies a command
it cannot parse. It has no exception syntax: to deny most of a program, list each
subcommand.
Network actions
Section titled “Network actions”A parent tag denies all of its children.
| Tag | Parent | Denies |
|---|---|---|
all |
None | Every tag in this table |
write |
all |
git:push, http:write, and ssh:write |
read |
all |
git:fetch, http:read, ssh:read, and dns |
git:push |
write |
git push, and the git bag’s push tool |
http:write |
write |
curl, wget, and httpie with a write method or a body, and mutating gh api calls |
ssh:write |
write |
scp, rsync, and sftp, in either direction |
git:fetch |
read |
git fetch, pull, clone, ls-remote, and submodule, and the git bag’s pull, fetch, and remotes tools |
http:read |
read |
curl, wget, and httpie reads, read-only gh api calls, WebFetch, and WebSearch |
ssh:read |
read |
ssh |
dns |
read |
dig, nslookup, and host |
Barry denies a command it cannot parse. A program that none of these tags
names, such as nc or python, runs.
Enforcement levels
Section titled “Enforcement levels”enforce |
What enforces the bound | Strength |
|---|---|---|
guard (default) |
Barry filters its tool list and checks each call to its tools before the tool runs | Cooperative. A script written to disk, or a program the classifier does not know, gets past it |
sandbox |
The guards, plus the agent runtime’s egress proxy, which filters every outbound connection from the agent’s process tree by hostname | The agent cannot lift it. The proxy knows hosts, not HTTP methods, so a partial actions list such as [write] still relies on the guards |
Under sandbox, an absent allowDomains means an empty allowlist: no egress
except to localhost. Under guard, allowDomains does nothing, and Barry logs
a warning when the list is not empty.
How bounds merge
Section titled “How bounds merge”A session’s bound merges every source:
- The bounds its traits carry, by name (
bounds:) or inline (bound:). - The session’s named bound, from
--bound. - The identity’s default bound.
- An inline bound on the session record.
Merging only moves toward more restriction:
| Field | Merge rule |
|---|---|
| Every list except the allowlists | Union |
files.allowOnly, network.allowDomains |
Intersection of identical strings. An absent list has no effect; an empty list permits nothing |
network.enforce |
sandbox wins over guard |
Apply a bound
Section titled “Apply a bound”| Command | Effect |
|---|---|
barry -r |
Starts a session with the read trait instead of the trait picker. The trait grants Barry’s builtin namespaces and carries the readonly bound, which also restricts the default traits |
barry --bound <name> |
Starts a session with a named bound. An unknown name exits with the list of bounds instead of starting |
barry identity bound set <name> |
Sets the identity’s default bound. For an identity directory, this copies the bound’s rules into identity.yaml, so later edits to the named bound do not follow |
barry identity bound clear |
Removes the identity’s default bound |
barry bound list |
Lists every named bound and what it restricts |
For example:
barry bound listbarry --bound no-network-write --traits codingbarry identity bound set no-secretsA bag’s trait carries a bound with bounds: or bound:; see
Packing bags. An identity carries one inline
under bound: in identity.yaml. To add named bounds, list them in
bounds.yaml in your config repository and run barry config import.
Builtin bounds
Section titled “Builtin bounds”Barry ships these in
sdk/cli/seed/bounds.builtin.yaml:
| Name | Rules |
|---|---|
readonly |
deniedTools: [Bash, Write, Edit], deniedAccess: [write], network.actions: [write] |
no-bash |
deniedTools: [Bash] |
no-secrets |
files.deny: *.env, .env.*, .ssh/**, *.pem, *.key, *.p12, *.pfx |
no-network |
network.actions: [all] |
no-network-write |
network.actions: [write] |
sandboxed-offline |
network.actions: [all], enforce: sandbox, allowDomains: [] |
slack-self-dm-only |
Denies the Slack tools that can choose a recipient |
Enforcement coverage
Section titled “Enforcement coverage”Barry enforces a bound on the tools it serves. The agent’s own native tools,
such as Claude Code’s Bash, are a separate route, and whether a bound
reaches them depends on the agent and on how the session started.
| Rule | Barry’s tools | Agent’s native tools |
|---|---|---|
deniedTools |
Removed from the tool list | Naming Bash, WebFetch, WebSearch, or AskUserQuestion requests denial of native Bash, WebFetch, and WebSearch together. Other names do not reach native tools |
deniedAccess |
Write tools removed from the tool list | Not applied. Barry denies native Write and Edit separately, as described after this table, but native Bash can still write files |
files.deny |
Checked on Read, Write, Edit, MultiEdit, Glob, Grep, and LS, not on Bash |
Not applied. cat .env in either shell gets past it |
files.allowOnly |
Checked on the same tools | Requests denial of native Bash, WebFetch, WebSearch, Read, Glob, and Grep |
bash.deny, bash.denyPrograms |
Checked on Barry’s Bash |
Requests denial of native Bash, WebFetch, and WebSearch |
network.actions |
Network tools removed from the list, and Bash commands classified |
Requests denial of native Bash, WebFetch, and WebSearch |
network.domains |
URLs checked in Bash commands and in WebFetch |
Requests denial of native Bash, WebFetch, and WebSearch |
network.allowDomains |
Not applied | Enforced only by the egress sandbox |
enforce: sandbox |
Not applicable | Egress proxy, only where the next table says so |
Where a native denial request takes effect:
| Session | Claude Code, on any endpoint | Cursor | Codex or OpenCode |
|---|---|---|---|
Terminal session that barry starts |
Only for the bound named with --bound, or a database identity’s default bound. A bound from a trait, including -r, or from identity.yaml restricts Barry’s tools only |
Same as Claude Code, except Glob and LS, which Cursor cannot deny |
Never. Barry prints a warning and the native tools stay available |
| Session that Barry’s session API runs | The bound does not reach native tools. Native Bash is always denied. Outside development, native Read, Write, Edit, Glob, and Grep are too, unless the identity sets allow_native_tools. Native WebFetch and WebSearch stay available |
Same as Claude Code, except Glob, which Cursor cannot deny |
Never. Barry logs a warning |
Barry denies native Write and Edit apart from any bound: in every Claude Code
session, even with allow_native_tools, and in every terminal Cursor session.
Codex and OpenCode never deny them.
Where enforce: sandbox takes effect:
| Session | Claude Code on its own endpoint or z.ai | Claude Code on Ollama | Codex, Cursor, or OpenCode |
|---|---|---|---|
Terminal session that barry starts |
Not applied: the guards only | Not applied | Not applied |
| Session that Barry’s session API runs | Egress proxy | Guards only, with a logged warning | Guards only, with a logged warning |
Other limits:
- Tools that bags serve make their own network requests in Barry’s process.
Traits,
deniedTools, anddeniedAccessgovern them;networkrules do not. network.domainsreads URLs as written. It misses URLs built at runtime and hosts reached by IP address.- A program the classifier does not know runs under
guard. Only the sandbox covers it.
For the agent capabilities behind these tables, see Adapters.