Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -733,7 +733,7 @@ All under `services.agent-box`:
| `package` | selected agent default | Override package to run for every agent user. |
| `installAgents` | all supported | Harnesses installed on the box (independent of what sessions run). |
| `sessionLimit` | `null` (automatic) | Maximum running or queued sessions. Null derives roughly one slot per GiB of physical RAM; a positive integer overrides it. |
| `codexFullAccess` | `true` | Run codex with no approval prompts and no sandbox, box-wide, via `/etc/codex/config.toml`. The box is the sandbox. That file is codex's *system* config layer, so a user's own `~/.codex/config.toml` still overrides it — and it is the only path that reaches the app-server daemon behind a remote-controlled codex session. |
| `codexFullAccess` | `true` | Run codex with no approval prompts and no sandbox, box-wide, via `/etc/codex/config.toml`. The box is the sandbox. That file is codex's *system* config layer, so a user's own `~/.codex/config.toml` still overrides it — and it is the only path that reaches the app-server daemon behind a remote-controlled codex session. Opting a TUI session out (`skipPermissions = false`) switches it to codex's own bubblewrap sandbox, where `sudoAllowlist` stops working entirely (issue #726). |
| `restartNotice` | `"auto"` | Whether a resumed Claude or Codex session gets the built-in "you were interrupted and automatically restarted" prompt. `"auto"` sends it for an unresolved hook lease, a webhook subscription filter, or a Claude turn cut mid-turn; `"always"` sends it whenever there is a concrete resume target; `"never"` never does. A Codex TUI uses its exact rollout target. Remote-controlled Codex can be targeted only after an app task creates a session subscription; the restarted daemon wakes that task with `codex queue`. A per-session `resumePrompt` overrides the built-in text. |
| `remoteControlHost` | `fqdnOrHostName` | Host label for the `@<host>` suffix of auto-derived Remote Control names. Empty -> falls back to the public `web.domain`, then the live kernel hostname. The AWS image sets it to the box's public sslip.io host. |
| `users.<name>.sessions.<s>.*` | `{}` | Seed sessions (first boot only): per session `agent`, `skipPermissions`, `remoteControl`, `remoteControlName`, `workingDirectory`, `extraArgs`. Empty = the legacy per-user options below seed a session named `main`. |
Expand Down Expand Up @@ -776,7 +776,13 @@ arbitrary command execution as the agent user.
setuid and needs the euid transition) - a deliberate trade of a bit of
containment for scoped elevation.
- **Tight sudo:** whatever's in `sudoAllowlist` is the entire root-capable
surface. `NOPASSWD` only - no `SETENV`, no blanket sudo, no ALL.
surface. `NOPASSWD` only - no `SETENV`, no blanket sudo, no ALL. None of it
is reachable from a codex TUI session with `skipPermissions = false`, even
when `codexFullAccess` is `true` (that per-session override reaches a TUI
session only, not a remote-controlled one): it runs codex's own sandbox
instead, through an unprivileged bubblewrap user namespace where root is
never mapped, so sudo shows up owned by nobody:nogroup and refuses
outright regardless of the allowlist (issue #726).
- **Login on everything a human reaches, brute-force damping (web
deployments):** the terminal workspace, per-session terminals, settings, and
the `/<user>/downloads/` file drop all sit behind the login (the CI tests
Expand Down
33 changes: 33 additions & 0 deletions modules/agent-box.nix

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions modules/agent-box.nix.in
Original file line number Diff line number Diff line change
Expand Up @@ -1766,6 +1766,16 @@ in
USER, shared by every remote thread, so there is nothing per-session
to override. The codex TUI arms do honour it — the supervisor pins
the restricted values back on the command line.

A TUI session with skipPermissions = false runs codex's OWN
sandbox instead (sandbox_mode=workspace-write), which execs
commands inside an unprivileged bubblewrap user namespace. Root is
never mapped in that namespace, so sudo — including every
sudoAllowlist entry, not just the self-serve caddy reload — shows
up owned by nobody:nogroup and refuses outright (issue 726). That
opt-out trades away this option's autonomy AND the box's own sudo
escape hatch; there is no substitute privileged path for a session
in that mode yet.
'';
};

Expand Down
9 changes: 9 additions & 0 deletions modules/src/caddyfile-header.caddy
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,15 @@
# exactly and a bare `systemctl` can resolve through PATH to one it will
# not match — which asks for a password the agent does not have.
#
# A Codex TUI session with skipPermissions = false runs Codex's OWN sandbox
# instead, even when this box's codexFullAccess default is true (that
# per-session override reaches a TUI session only, not a remote-controlled
# one). That sandbox cannot run this reload at all: it execs commands inside
# an unprivileged bubblewrap user namespace, where root is never mapped, so
# sudo shows up owned by nobody:nogroup and refuses regardless of the
# sudoers file (agent-box#726). Use a session with full access for this, or
# reload from one that has it.
#
# Recommended snippet shape — reverse-proxy to a localhost port your
# agent runs, NOT `file_server /home/<you>/...`. caddy.service has
# ProtectHome=true, so it cannot read files under /home; use file_server
Expand Down
3 changes: 3 additions & 0 deletions modules/src/caddyfile-sites.caddy
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,6 @@
#
# One import per user: Caddyfile's `import` directive only accepts a
# single `*` per pattern, so we can't collapse this to `*/*.caddy`.
#
# A sandboxed Codex session can't run that reload at all — see the note at
# the top of this file (agent-box#726).
11 changes: 11 additions & 0 deletions modules/src/default-agents.md
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,17 @@ $HOME (caddy can't read /home). Use the full path shown, not bare
`systemctl` resolves through PATH to a Nix store path that won't match,
silently falling back to asking for a password.

A Codex TUI session with `skipPermissions = false` runs Codex's OWN sandbox
instead, even when this box's `codexFullAccess` default is `true` (that
per-session override reaches a TUI session only, not a remote-controlled
one). That sandbox cannot run this at all: it execs commands inside an
unprivileged `bubblewrap` user namespace, where root is never mapped, so
`sudo` shows up owned by `nobody:nogroup` and refuses - "must be owned by
uid 0 and have the setuid bit set" - no matter what the sudoers file allows
(agent-box#726). That is every sudoAllowlist entry, not just this one; there
is no per-command workaround. Use a session with full access for this, or
reload caddy from a different session that has it.

@UPDATE_SECTION@## This platform has its own upstream repo

The box itself - the terminal, session manager, webhook wiring, this
Expand Down
11 changes: 11 additions & 0 deletions tests/golden/vm/etc/agent-box-guides/AGENTS.agent.md
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,17 @@ $HOME (caddy can't read /home). Use the full path shown, not bare
`systemctl` resolves through PATH to a Nix store path that won't match,
silently falling back to asking for a password.

A Codex TUI session with `skipPermissions = false` runs Codex's OWN sandbox
instead, even when this box's `codexFullAccess` default is `true` (that
per-session override reaches a TUI session only, not a remote-controlled
one). That sandbox cannot run this at all: it execs commands inside an
unprivileged `bubblewrap` user namespace, where root is never mapped, so
`sudo` shows up owned by `nobody:nogroup` and refuses - "must be owned by
uid 0 and have the setuid bit set" - no matter what the sudoers file allows
(agent-box#726). That is every sudoAllowlist entry, not just this one; there
is no per-command workaround. Use a session with full access for this, or
reload caddy from a different session that has it.

## This platform has its own upstream repo

The box itself - the terminal, session manager, webhook wiring, this
Expand Down
11 changes: 11 additions & 0 deletions tests/golden/web/etc/agent-box-guides/AGENTS.agent.md
Original file line number Diff line number Diff line change
Expand Up @@ -622,6 +622,17 @@ $HOME (caddy can't read /home). Use the full path shown, not bare
`systemctl` resolves through PATH to a Nix store path that won't match,
silently falling back to asking for a password.

A Codex TUI session with `skipPermissions = false` runs Codex's OWN sandbox
instead, even when this box's `codexFullAccess` default is `true` (that
per-session override reaches a TUI session only, not a remote-controlled
one). That sandbox cannot run this at all: it execs commands inside an
unprivileged `bubblewrap` user namespace, where root is never mapped, so
`sudo` shows up owned by `nobody:nogroup` and refuses - "must be owned by
uid 0 and have the setuid bit set" - no matter what the sudoers file allows
(agent-box#726). That is every sudoAllowlist entry, not just this one; there
is no per-command workaround. Use a session with full access for this, or
reload caddy from a different session that has it.

## Updating

Update the box's software with:
Expand Down
12 changes: 12 additions & 0 deletions tests/golden/web/etc/caddy/caddy_config
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,15 @@
# exactly and a bare `systemctl` can resolve through PATH to one it will
# not match — which asks for a password the agent does not have.
#
# A Codex TUI session with skipPermissions = false runs Codex's OWN sandbox
# instead, even when this box's codexFullAccess default is true (that
# per-session override reaches a TUI session only, not a remote-controlled
# one). That sandbox cannot run this reload at all: it execs commands inside
# an unprivileged bubblewrap user namespace, where root is never mapped, so
# sudo shows up owned by nobody:nogroup and refuses regardless of the
# sudoers file (agent-box#726). Use a session with full access for this, or
# reload from one that has it.
#
# Recommended snippet shape — reverse-proxy to a localhost port your
# agent runs, NOT `file_server /home/<you>/...`. caddy.service has
# ProtectHome=true, so it cannot read files under /home; use file_server
Expand Down Expand Up @@ -593,5 +602,8 @@ golden.example.org {
#
# One import per user: Caddyfile's `import` directive only accepts a
# single `*` per pattern, so we can't collapse this to `*/*.caddy`.
#
# A sandboxed Codex session can't run that reload at all — see the note at
# the top of this file (agent-box#726).
import /var/lib/agent-box-sites/agent/*.caddy
import /var/lib/agent-box-sites/robot/*.caddy
11 changes: 11 additions & 0 deletions tests/native/expected/etc/agent-box-guides/AGENTS.agent.md
Original file line number Diff line number Diff line change
Expand Up @@ -659,6 +659,17 @@ $HOME (caddy can't read /home). Use the full path shown, not bare
`systemctl` resolves through PATH to a Nix store path that won't match,
silently falling back to asking for a password.

A Codex TUI session with `skipPermissions = false` runs Codex's OWN sandbox
instead, even when this box's `codexFullAccess` default is `true` (that
per-session override reaches a TUI session only, not a remote-controlled
one). That sandbox cannot run this at all: it execs commands inside an
unprivileged `bubblewrap` user namespace, where root is never mapped, so
`sudo` shows up owned by `nobody:nogroup` and refuses - "must be owned by
uid 0 and have the setuid bit set" - no matter what the sudoers file allows
(agent-box#726). That is every sudoAllowlist entry, not just this one; there
is no per-command workaround. Use a session with full access for this, or
reload caddy from a different session that has it.

## Updating

Update the box's software with:
Expand Down
11 changes: 11 additions & 0 deletions tests/native/expected/etc/agent-box-guides/AGENTS.robot.md
Original file line number Diff line number Diff line change
Expand Up @@ -659,6 +659,17 @@ $HOME (caddy can't read /home). Use the full path shown, not bare
`systemctl` resolves through PATH to a Nix store path that won't match,
silently falling back to asking for a password.

A Codex TUI session with `skipPermissions = false` runs Codex's OWN sandbox
instead, even when this box's `codexFullAccess` default is `true` (that
per-session override reaches a TUI session only, not a remote-controlled
one). That sandbox cannot run this at all: it execs commands inside an
unprivileged `bubblewrap` user namespace, where root is never mapped, so
`sudo` shows up owned by `nobody:nogroup` and refuses - "must be owned by
uid 0 and have the setuid bit set" - no matter what the sudoers file allows
(agent-box#726). That is every sudoAllowlist entry, not just this one; there
is no per-command workaround. Use a session with full access for this, or
reload caddy from a different session that has it.

## Updating

Update the box's software with:
Expand Down
12 changes: 12 additions & 0 deletions tests/native/expected/etc/agent-box/Caddyfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,15 @@
# exactly and a bare `systemctl` can resolve through PATH to one it will
# not match — which asks for a password the agent does not have.
#
# A Codex TUI session with skipPermissions = false runs Codex's OWN sandbox
# instead, even when this box's codexFullAccess default is true (that
# per-session override reaches a TUI session only, not a remote-controlled
# one). That sandbox cannot run this reload at all: it execs commands inside
# an unprivileged bubblewrap user namespace, where root is never mapped, so
# sudo shows up owned by nobody:nogroup and refuses regardless of the
# sudoers file (agent-box#726). Use a session with full access for this, or
# reload from one that has it.
#
# Recommended snippet shape — reverse-proxy to a localhost port your
# agent runs, NOT `file_server /home/<you>/...`. caddy.service has
# ProtectHome=true, so it cannot read files under /home; use file_server
Expand Down Expand Up @@ -598,5 +607,8 @@ handle {
#
# One import per user: Caddyfile's `import` directive only accepts a
# single `*` per pattern, so we can't collapse this to `*/*.caddy`.
#
# A sandboxed Codex session can't run that reload at all — see the note at
# the top of this file (agent-box#726).
import /var/lib/agent-box-sites/agent/*.caddy
import /var/lib/agent-box-sites/robot/*.caddy