Skip to content

Sonny: sandbox leads and tasks by default - #285

Merged
lydakis merged 31 commits into
mainfrom
claude/project-thread-6zwinw
Oct 28, 2026
Merged

lydakis merged 31 commits into
mainfrom
claude/project-thread-6zwinw

Conversation

@lydakis

@lydakis lydakis commented Oct 28, 2026

Copy link
Copy Markdown
Owner

With #265 and #276 merged, sandboxing becomes Sonny's default, as Codex's modes do it and as decided for this project: leads and tasks run workspace-write with the network on, and their repository's git folder is writable so commits don't ask.

What changes

Sonny

  • Leads: the /approvals mode sets the lead's sandbox along with its gate.
    • In auto (a new lead's mode) and ask, the lead runs workspace-write with the network on. Its repository's git folder (git rev-parse --git-common-dir) is passed as writable.
    • In full, the lead runs danger-full-access, as Codex's full access does.
    • When Sonny opens a lead whose sandbox isn't its mode's, such as one made before this change, it sets the sandbox again with the same configure.
  • Tasks: start_task always starts the task with --sandbox workspace-write --network, plus the task folder's repository as --writable, whatever the lead's mode.
    • A worktree's git folder lives in the lead's repository, so the task can commit on its branch.

Daemon

  • configure takes sandbox, writable and network. These are the names create's settings use, and the request replaces the bot's sandbox.
    • A bot that another bot made keeps no weaker a sandbox than its creator's, applied through the same Settings::under used when it was made.
    • A bot's own socket still can't call configure at all, as before.
  • A git folder given as a writable root keeps what git runs read-only, as .git is kept: hooks, config and config.worktree. A repository's missing hooks folder may not be created. Without this, a bot that may commit could write a hook that git later runs outside any sandbox.

Notes

  • Tasks of a full lead: they are sandboxed and take the lead's empty gate, so any call that leaves their sandbox is refused rather than asked. This matches the design ("tasks always workspace-write").
  • Not included: the lead can't write in its tasks' worktrees. Tasks commit on their branches and the lead merges in its own repository.
  • Linux without bubblewrap: creating a sandboxed lead fails with sandbox_unavailable, which is the daemon's existing rule that a sandbox is never silently skipped.
  • Performance: each confined call now checks each root for a HEAD file, one stat. Nothing changes on the request path.

Tests

  • Runtime (test_approvals), on a repository with a linked worktree:
    • configure sandboxes an unsandboxed bot.
    • A child configured to danger-full-access stays workspace-write.
    • network without sandbox is refused.
    • In the sandbox, git commit in the worktree works.
    • Writing .git/hooks/pre-commit and appending to .git/config both fail.
    • The test fails without the daemon change.
  • Runtime (test_served): a task started by start_task has workspace-write, the network on, and the repository's .git as writable.
  • Unit:
    • A git-folder root allows objects and lock files, and refuses config and new hooks.
    • Sonny's mode-to-sandbox mapping, including full for leads and outside a repository.
  • Locally:
    • cargo test --workspace with AGENT_TEST_SANDBOX=1 passes.
    • Clippy and fmt pass.
    • The test_approvals, test_served, test_answered, test_lookout and test_trigger modules pass.

macOS isn't run here. The Seatbelt profile uses the same roots and read-only lists, so it gets the git rules without its own change.


Generated by Claude Code

claude added 23 commits October 28, 2026 03:11
… a process slot, a child's roots keep what its creator's keep read-only

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jkdGHSSeQctmSznfdNuRg
… pass on no root the creator keeps read-only

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jkdGHSSeQctmSznfdNuRg
…s; children inherit the sandbox without ceilings; take placeholders before a process slot

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jkdGHSSeQctmSznfdNuRg
…amed or linked; each Seatbelt rule names only its own paths, once

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jkdGHSSeQctmSznfdNuRg
… the socket

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jkdGHSSeQctmSznfdNuRg
…-6zwinw

# Conflicts:
#	docs/DAEMON_MEASUREMENTS.md
…apped to what it may do

Each sandboxed bot gets a socket of its own beside the daemon's, bound over
the daemon's path in bwrap and allowed by Seatbelt, and named by AGENT_SOCKET,
so the agent CLI works in its shell. Requests on it answer as that bot: no
configure, answers, tool results, serving approvals or shutdown; what it makes
is its own and inherits its sandbox inside its folders; it removes only its
own bots; what it sends reads as its own.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jkdGHSSeQctmSznfdNuRg
… only the answered tools its maker passes on

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jkdGHSSeQctmSznfdNuRg
…to claude/project-thread-6zwinw

# Conflicts:
#	docs/RUST_PROTOTYPE.md
#	src/sandbox.rs
#	src/server/mod.rs
#	src/server/turn.rs
#	src/tools.rs
#	tests/test_approvals.py
#	tests/tool_contract.rs
The test's own socket sat beside the daemon's, so hiding the folder of
bots' sockets hid the daemon's path too; the daemon keeps them in
SOCKET.d/.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jkdGHSSeQctmSznfdNuRg
A folder a bot was given can sit in one its creator may write, and the
creator can swap a link in for it later; bound by its path, bubblewrap
would follow that link. Each root is now opened folder by folder without
following links, refused if one was swapped in, and bound from that
descriptor.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jkdGHSSeQctmSznfdNuRg
…lose a bot's socket with it

A fork keeps its source's declared tools, so forking a bot whose tools a
client answers is held to what the caller may pass on, as create is. A
turn's folder becomes the bot's, so one given to any sandboxed bot must
be the caller's to write. A deleted or expired bot's own socket is
closed and removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jkdGHSSeQctmSznfdNuRg
… sockets off the runtime thread

Pinned descriptors outlived bubblewrap into the command itself, which
could reach the host's folders through them. Each confined call builds
its policy anew, off the runtime thread, so a root that a link was
swapped in for since is refused there instead. Closing a deleted bot's
own socket takes a lock and unlinks a file, so it runs on a blocking
worker.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jkdGHSSeQctmSznfdNuRg
…-6zwinw

# Conflicts:
#	docs/DAEMON_MEASUREMENTS.md
…-6zwinw

# Conflicts:
#	docs/DAEMON_MEASUREMENTS.md
…rred attribution stable across retries

A caller whose allowed list leaves out a client-answered tool can't
declare it for a bot it makes, nor have a new bot inherit it. A submit on
a bot's own socket without from is named by the caller's name as origin,
never by whatever turn it runs then, so a retry is the same request.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jkdGHSSeQctmSznfdNuRg
…ts source's allowed list

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jkdGHSSeQctmSznfdNuRg
…-6zwinw

# Conflicts:
#	docs/DAEMON_MEASUREMENTS.md
The store answers a resent fork before it looks up the source, so a
client recovering a lost response still gets the fork it made.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jkdGHSSeQctmSznfdNuRg
…ndbox

A lead in auto or ask mode is made, or set again when Sonny opens it,
with Codex's workspace-write sandbox, the network on and its
repository's git folder writable, so a commit doesn't ask. In full mode
it runs unconfined. Every task is confined the same way in its own
folder, whatever its lead's mode.

The daemon's configure takes sandbox, writable and network, held no
weaker than the creator's for a bot another bot made. A git folder
given as a writable folder keeps its hooks and config read-only, as
.git is kept, and its missing hooks may not be made.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jkdGHSSeQctmSznfdNuRg
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-28T10:38:11.875605Z 1c74670 New commits
🔒 Security Review ✅ Completed 2026-10-28T10:44:54.352264Z 1c74670 New commits

Security findings

Advisory findings (2)

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

lydakis commented Oct 28, 2026

Copy link
Copy Markdown
Owner Author

@codex review


Generated by Claude Code

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e860f68ed9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread sonny/src/main.rs
Comment thread src/sandbox.rs Outdated
Comment thread sonny/src/approvals.rs Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛡️ Codex Security Review · Automatically triggered

Here are some automated security review suggestions for this pull request.

Reviewed commit: e860f68ed9

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

Comment thread src/sandbox.rs Outdated
… keep per-worktree-config git folders read-only

A project's lead is created with its sandbox, so a host that can't make
one refuses the lead rather than running it unconfined until configure.
A lead whose writable folders no longer match its folder's repository,
as after the folder moves, takes the sandbox again. A git folder whose
config turns on per-worktree config, or a repository's whose config is
missing, stays read-only as a whole: git reads those files wherever they
are made.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jkdGHSSeQctmSznfdNuRg

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: aa39c8b6b8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/sandbox.rs Outdated
A bot that may write the folder could rename HEAD for one call and the
next would build its sandbox without the hooks kept. The name, .git or
NAME.git, is the settings' and the bot can't change it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jkdGHSSeQctmSznfdNuRg

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a59e69226b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/sandbox.rs
A separate git folder named otherwise isn't known to the daemon as git's,
so its hooks and config wouldn't be kept; commits there ask instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jkdGHSSeQctmSznfdNuRg

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c2115930af

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/sandbox.rs
Comment thread src/sandbox.rs Outdated
Comment thread sonny/src/main.rs Outdated
…nd submodules' git folders read-only

A lead new_project makes is created with the auto gate as well as the
sandbox, so opening it doesn't read it as full and unconfine it; and
Sonny only ever confines a lead on opening it, never unconfines. A git
folder whose config includes other files stays read-only as a whole,
and its modules folder, which holds submodules' hooks and config, stays
read-only within it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jkdGHSSeQctmSznfdNuRg

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a375dc932b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/sandbox.rs Outdated
A bot could make .git/commondir point at a folder of its own, so git
outside the sandbox ran that folder's hooks and config. commondir is now
kept like hooks, in the git folder and in each linked worktree's folder;
a missing one may not be made on macOS, and on Linux, where a folder in
its place would stop git, one the command makes is removed when it ends.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jkdGHSSeQctmSznfdNuRg

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9d9f1db2af

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/sandbox.rs
A runtime shutting down drops blocking work not yet started, so a
placeholder a sandboxed command held, such as /tmp/.git, could be left
behind when its runtime ended, and Sonny then took folders under /tmp for
a repository's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jkdGHSSeQctmSznfdNuRg
A linked worktree's folder could be swapped for a link to another
repository's before the removal followed it. One without commondir was
made since, and serves only a checkout the bot made.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jkdGHSSeQctmSznfdNuRg

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛡️ Codex Security Review · Automatically triggered

Here are some automated security review suggestions for this pull request.

Reviewed commit: fd8bd93815

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

Comment thread src/sandbox.rs

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fd8bd93815

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/sandbox.rs
@lydakis lydakis mentioned this pull request Oct 28, 2026
Main now picks a lead's tools by its model (apply_patch or edit, web
search), so a project's lead takes its gate for the tools the client
gives it, and the client leaves out of a gate any tool the lead doesn't
have.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jkdGHSSeQctmSznfdNuRg

lydakis commented Oct 28, 2026

Copy link
Copy Markdown
Owner Author

On 1c74670 the runtime job failed in a single test: test_wait.test_waiters_share_output_before_and_after_process_completion. The test's daemon didn't exit within the 5 s close timeout (TimeoutExpired in runtime_client.close).

This looks like a flake unrelated to this PR's changes:

  • The test doesn't use a sandbox.
  • The same module passed on the previous head, fd8bd93.
  • It passes locally on 1c74670 three times out of three.
  • The other 45 modules and the rust job are green.

This session can't re-run jobs (403). Merging now, per George's call to merge everything for the release.


Generated by Claude Code

@lydakis
lydakis merged commit 35da9e4 into main Oct 28, 2026
3 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants