From 616137c918e209c7ec8e3c94cd83fc566defd086 Mon Sep 17 00:00:00 2001 From: defangdevs Date: Thu, 17 Sep 2026 20:10:05 +0000 Subject: [PATCH 1/2] docs: warn that codex's own sandbox breaks the sudo allowlist entirely A codex TUI session opted out of codexFullAccess (skipPermissions = false) runs codex's own Linux sandbox in sandbox_mode=workspace-write, which execs through an unprivileged bubblewrap user namespace. Root is never mapped in that namespace, so sudo - and every sudoAllowlist entry, not just the self-serve caddy reload - shows up owned by nobody:nogroup and refuses outright, independent of what the sudoers file allows. Reproduced live: `codex exec -c sandbox_mode=workspace-write` fails inside this platform's own hardened session unit before even reaching sudo (`bwrap: loopback: Failed RTM_NEWADDR: Operation not permitted`), the same class of nested-sandbox conflict as agent-box#237's dev-container triage. There is no fix on the sudoers side: an unprivileged user namespace not mapping uid 0 is how bubblewrap sandboxes work, not a bug in our policy, and issue #234 already made codexFullAccess (skipping codex's own sandbox entirely) the box-wide default for exactly this reason. Document the limitation everywhere an agent or an operator would otherwise spend time trying to widen sudoers instead: the shipped AGENTS guide, the rendered Caddyfile comments, the codexFullAccess option description, and the README's option table and security model. Whether we also want a privilege-escalation-free reload path (so a sandboxed codex session can self-serve without sudo at all) is a separate, undecided design question - left open on #726 rather than folded into this docs fix. Addresses #726 Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01EEqb7WeohhYxTsYDwNvGgp --- README.md | 9 ++++-- modules/agent-box.nix | 30 +++++++++++++++++++ modules/agent-box.nix.in | 10 +++++++ modules/src/caddyfile-header.caddy | 7 +++++ modules/src/caddyfile-sites.caddy | 3 ++ modules/src/default-agents.md | 10 +++++++ .../vm/etc/agent-box-guides/AGENTS.agent.md | 10 +++++++ .../web/etc/agent-box-guides/AGENTS.agent.md | 10 +++++++ tests/golden/web/etc/caddy/caddy_config | 10 +++++++ .../etc/agent-box-guides/AGENTS.agent.md | 10 +++++++ .../etc/agent-box-guides/AGENTS.robot.md | 10 +++++++ tests/native/expected/etc/agent-box/Caddyfile | 10 +++++++ 12 files changed, 127 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b27e21dd..a8b093bb 100644 --- a/README.md +++ b/README.md @@ -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 `@` 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..sessions..*` | `{}` | 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`. | @@ -776,7 +776,12 @@ 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 session running codex's own sandbox + (`skipPermissions = false`, i.e. not `codexFullAccess`): that sandbox execs + 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 `//downloads/` file drop all sit behind the login (the CI tests diff --git a/modules/agent-box.nix b/modules/agent-box.nix index a6b8ec29..e2b9a6bd 100644 --- a/modules/agent-box.nix +++ b/modules/agent-box.nix @@ -749,6 +749,16 @@ let `systemctl` resolves through PATH to a Nix store path that won't match, silently falling back to asking for a password. + A Codex session running its OWN sandbox (`skipPermissions = false`, i.e. not + this box's `codexFullAccess` default) cannot run this at all: Codex's Linux + sandbox 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 @@ -13595,6 +13605,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. ''; }; @@ -25312,6 +25332,13 @@ if __name__ == "__main__": # 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 session running its OWN sandbox (skipPermissions = false, i.e. not + # this box's codexFullAccess default) cannot run this reload at all: that + # sandbox 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//...`. caddy.service has # ProtectHome=true, so it cannot read files under /home; use file_server @@ -25399,6 +25426,9 @@ if __name__ == "__main__": # # 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). '' + lib.concatMapStringsSep "" (name: "import /var/lib/agent-box-sites/${name}/*.caddy\n") (lib.attrNames cfg.users)); diff --git a/modules/agent-box.nix.in b/modules/agent-box.nix.in index 005f3d57..5e6206f4 100644 --- a/modules/agent-box.nix.in +++ b/modules/agent-box.nix.in @@ -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. ''; }; diff --git a/modules/src/caddyfile-header.caddy b/modules/src/caddyfile-header.caddy index 05986afe..239f14af 100644 --- a/modules/src/caddyfile-header.caddy +++ b/modules/src/caddyfile-header.caddy @@ -10,6 +10,13 @@ # 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 session running its OWN sandbox (skipPermissions = false, i.e. not +# this box's codexFullAccess default) cannot run this reload at all: that +# sandbox 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//...`. caddy.service has # ProtectHome=true, so it cannot read files under /home; use file_server diff --git a/modules/src/caddyfile-sites.caddy b/modules/src/caddyfile-sites.caddy index 0f2aeaf4..486de063 100644 --- a/modules/src/caddyfile-sites.caddy +++ b/modules/src/caddyfile-sites.caddy @@ -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). diff --git a/modules/src/default-agents.md b/modules/src/default-agents.md index cf42a787..33416c2d 100644 --- a/modules/src/default-agents.md +++ b/modules/src/default-agents.md @@ -248,6 +248,16 @@ $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 session running its OWN sandbox (`skipPermissions = false`, i.e. not +this box's `codexFullAccess` default) cannot run this at all: Codex's Linux +sandbox 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 diff --git a/tests/golden/vm/etc/agent-box-guides/AGENTS.agent.md b/tests/golden/vm/etc/agent-box-guides/AGENTS.agent.md index 4f8d7573..a9235f39 100644 --- a/tests/golden/vm/etc/agent-box-guides/AGENTS.agent.md +++ b/tests/golden/vm/etc/agent-box-guides/AGENTS.agent.md @@ -304,6 +304,16 @@ $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 session running its OWN sandbox (`skipPermissions = false`, i.e. not +this box's `codexFullAccess` default) cannot run this at all: Codex's Linux +sandbox 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 diff --git a/tests/golden/web/etc/agent-box-guides/AGENTS.agent.md b/tests/golden/web/etc/agent-box-guides/AGENTS.agent.md index afb462d9..0e6bc635 100644 --- a/tests/golden/web/etc/agent-box-guides/AGENTS.agent.md +++ b/tests/golden/web/etc/agent-box-guides/AGENTS.agent.md @@ -622,6 +622,16 @@ $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 session running its OWN sandbox (`skipPermissions = false`, i.e. not +this box's `codexFullAccess` default) cannot run this at all: Codex's Linux +sandbox 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: diff --git a/tests/golden/web/etc/caddy/caddy_config b/tests/golden/web/etc/caddy/caddy_config index 76cefbab..f50eb4ef 100644 --- a/tests/golden/web/etc/caddy/caddy_config +++ b/tests/golden/web/etc/caddy/caddy_config @@ -10,6 +10,13 @@ # 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 session running its OWN sandbox (skipPermissions = false, i.e. not +# this box's codexFullAccess default) cannot run this reload at all: that +# sandbox 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//...`. caddy.service has # ProtectHome=true, so it cannot read files under /home; use file_server @@ -593,5 +600,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 diff --git a/tests/native/expected/etc/agent-box-guides/AGENTS.agent.md b/tests/native/expected/etc/agent-box-guides/AGENTS.agent.md index 033ebea8..0ad18f45 100644 --- a/tests/native/expected/etc/agent-box-guides/AGENTS.agent.md +++ b/tests/native/expected/etc/agent-box-guides/AGENTS.agent.md @@ -659,6 +659,16 @@ $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 session running its OWN sandbox (`skipPermissions = false`, i.e. not +this box's `codexFullAccess` default) cannot run this at all: Codex's Linux +sandbox 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: diff --git a/tests/native/expected/etc/agent-box-guides/AGENTS.robot.md b/tests/native/expected/etc/agent-box-guides/AGENTS.robot.md index 033ebea8..0ad18f45 100644 --- a/tests/native/expected/etc/agent-box-guides/AGENTS.robot.md +++ b/tests/native/expected/etc/agent-box-guides/AGENTS.robot.md @@ -659,6 +659,16 @@ $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 session running its OWN sandbox (`skipPermissions = false`, i.e. not +this box's `codexFullAccess` default) cannot run this at all: Codex's Linux +sandbox 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: diff --git a/tests/native/expected/etc/agent-box/Caddyfile b/tests/native/expected/etc/agent-box/Caddyfile index b5fae9ff..984e6dae 100644 --- a/tests/native/expected/etc/agent-box/Caddyfile +++ b/tests/native/expected/etc/agent-box/Caddyfile @@ -11,6 +11,13 @@ # 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 session running its OWN sandbox (skipPermissions = false, i.e. not +# this box's codexFullAccess default) cannot run this reload at all: that +# sandbox 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//...`. caddy.service has # ProtectHome=true, so it cannot read files under /home; use file_server @@ -598,5 +605,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 From bdd5c0b20720b8110a19267ef52dca7ea2d1d23c Mon Sep 17 00:00:00 2001 From: defangdevs Date: Thu, 17 Sep 2026 20:17:54 +0000 Subject: [PATCH 2/2] fixup: scope the sandbox note to Codex TUI sessions CodeRabbit correctly flagged that "skipPermissions = false, i.e. not codexFullAccess" was a misleading equivalence: a remote-controlled codex session ignores skipPermissions entirely and always follows the box-wide codexFullAccess default (the option's own doc already says so); only a TUI session's per-session override actually switches sandbox modes. Reword every spot that had the same conflation to say "Codex TUI session ... even when codexFullAccess is true" instead. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01EEqb7WeohhYxTsYDwNvGgp --- README.md | 11 ++++--- modules/agent-box.nix | 33 ++++++++++--------- modules/src/caddyfile-header.caddy | 14 ++++---- modules/src/default-agents.md | 19 ++++++----- .../vm/etc/agent-box-guides/AGENTS.agent.md | 19 ++++++----- .../web/etc/agent-box-guides/AGENTS.agent.md | 19 ++++++----- tests/golden/web/etc/caddy/caddy_config | 14 ++++---- .../etc/agent-box-guides/AGENTS.agent.md | 19 ++++++----- .../etc/agent-box-guides/AGENTS.robot.md | 19 ++++++----- tests/native/expected/etc/agent-box/Caddyfile | 14 ++++---- 10 files changed, 98 insertions(+), 83 deletions(-) diff --git a/README.md b/README.md index a8b093bb..9e62daaf 100644 --- a/README.md +++ b/README.md @@ -777,11 +777,12 @@ arbitrary command execution as the agent user. 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. None of it - is reachable from a codex session running codex's own sandbox - (`skipPermissions = false`, i.e. not `codexFullAccess`): that sandbox execs - 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). + 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 `//downloads/` file drop all sit behind the login (the CI tests diff --git a/modules/agent-box.nix b/modules/agent-box.nix index e2b9a6bd..1e38fc17 100644 --- a/modules/agent-box.nix +++ b/modules/agent-box.nix @@ -749,15 +749,16 @@ let `systemctl` resolves through PATH to a Nix store path that won't match, silently falling back to asking for a password. - A Codex session running its OWN sandbox (`skipPermissions = false`, i.e. not - this box's `codexFullAccess` default) cannot run this at all: Codex's Linux - sandbox 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. + 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 @@ -25332,12 +25333,14 @@ if __name__ == "__main__": # 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 session running its OWN sandbox (skipPermissions = false, i.e. not - # this box's codexFullAccess default) cannot run this reload at all: that - # sandbox 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. + # 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//...`. caddy.service has diff --git a/modules/src/caddyfile-header.caddy b/modules/src/caddyfile-header.caddy index 239f14af..7ddae6e4 100644 --- a/modules/src/caddyfile-header.caddy +++ b/modules/src/caddyfile-header.caddy @@ -10,12 +10,14 @@ # 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 session running its OWN sandbox (skipPermissions = false, i.e. not -# this box's codexFullAccess default) cannot run this reload at all: that -# sandbox 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. +# 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//...`. caddy.service has diff --git a/modules/src/default-agents.md b/modules/src/default-agents.md index 33416c2d..0b937b60 100644 --- a/modules/src/default-agents.md +++ b/modules/src/default-agents.md @@ -248,15 +248,16 @@ $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 session running its OWN sandbox (`skipPermissions = false`, i.e. not -this box's `codexFullAccess` default) cannot run this at all: Codex's Linux -sandbox 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. +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 diff --git a/tests/golden/vm/etc/agent-box-guides/AGENTS.agent.md b/tests/golden/vm/etc/agent-box-guides/AGENTS.agent.md index a9235f39..bfce91da 100644 --- a/tests/golden/vm/etc/agent-box-guides/AGENTS.agent.md +++ b/tests/golden/vm/etc/agent-box-guides/AGENTS.agent.md @@ -304,15 +304,16 @@ $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 session running its OWN sandbox (`skipPermissions = false`, i.e. not -this box's `codexFullAccess` default) cannot run this at all: Codex's Linux -sandbox 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. +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 diff --git a/tests/golden/web/etc/agent-box-guides/AGENTS.agent.md b/tests/golden/web/etc/agent-box-guides/AGENTS.agent.md index 0e6bc635..5053c45c 100644 --- a/tests/golden/web/etc/agent-box-guides/AGENTS.agent.md +++ b/tests/golden/web/etc/agent-box-guides/AGENTS.agent.md @@ -622,15 +622,16 @@ $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 session running its OWN sandbox (`skipPermissions = false`, i.e. not -this box's `codexFullAccess` default) cannot run this at all: Codex's Linux -sandbox 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. +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 diff --git a/tests/golden/web/etc/caddy/caddy_config b/tests/golden/web/etc/caddy/caddy_config index f50eb4ef..21b27975 100644 --- a/tests/golden/web/etc/caddy/caddy_config +++ b/tests/golden/web/etc/caddy/caddy_config @@ -10,12 +10,14 @@ # 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 session running its OWN sandbox (skipPermissions = false, i.e. not -# this box's codexFullAccess default) cannot run this reload at all: that -# sandbox 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. +# 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//...`. caddy.service has diff --git a/tests/native/expected/etc/agent-box-guides/AGENTS.agent.md b/tests/native/expected/etc/agent-box-guides/AGENTS.agent.md index 0ad18f45..eefb3d8a 100644 --- a/tests/native/expected/etc/agent-box-guides/AGENTS.agent.md +++ b/tests/native/expected/etc/agent-box-guides/AGENTS.agent.md @@ -659,15 +659,16 @@ $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 session running its OWN sandbox (`skipPermissions = false`, i.e. not -this box's `codexFullAccess` default) cannot run this at all: Codex's Linux -sandbox 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. +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 diff --git a/tests/native/expected/etc/agent-box-guides/AGENTS.robot.md b/tests/native/expected/etc/agent-box-guides/AGENTS.robot.md index 0ad18f45..eefb3d8a 100644 --- a/tests/native/expected/etc/agent-box-guides/AGENTS.robot.md +++ b/tests/native/expected/etc/agent-box-guides/AGENTS.robot.md @@ -659,15 +659,16 @@ $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 session running its OWN sandbox (`skipPermissions = false`, i.e. not -this box's `codexFullAccess` default) cannot run this at all: Codex's Linux -sandbox 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. +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 diff --git a/tests/native/expected/etc/agent-box/Caddyfile b/tests/native/expected/etc/agent-box/Caddyfile index 984e6dae..90b9260e 100644 --- a/tests/native/expected/etc/agent-box/Caddyfile +++ b/tests/native/expected/etc/agent-box/Caddyfile @@ -11,12 +11,14 @@ # 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 session running its OWN sandbox (skipPermissions = false, i.e. not -# this box's codexFullAccess default) cannot run this reload at all: that -# sandbox 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. +# 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//...`. caddy.service has