Skip to content

fix: confine grok worker state by permissions, not a deny-mount - #11

Merged
apresmoi merged 4 commits into
mainfrom
fix/grok-worker-state-permissions
Sep 4, 2026
Merged

fix: confine grok worker state by permissions, not a deny-mount#11
apresmoi merged 4 commits into
mainfrom
fix/grok-worker-state-permissions

Conversation

@apresmoi

@apresmoi apresmoi commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator

Grok has never run in production. Diagnosis, reproduced against the real grok 1.0.13 binary: whenever the sandbox deny list is non-empty, Grok re-execs inside bubblewrap and tries to open() a mode-000 placeholder it created, to prove the bind-over is genuine. Inside bwrap the process is capability-stripped, the open returns EACCES, and Grok concludes it is being spoofed:

error: sandbox reports bwrap but required read-deny mounts are not in effect
(read-deny path /x/state could not be opened: Permission denied (os error 13));
refusing to start (possible __GROK_INSIDE_BWRAP spoof)

It exits before writing any ProfileApplied event, so Daimon's attestation never runs. Controls confirmed the deny target is irrelevant — a plain directory, a directory with a child mount, and a vanilla root-owned home denying /secret/creds.txt all fail identically. Six previous fixes tuned this path; each uncovered the next thing wrong with it.

This removes the mechanism instead.

  • GROK_SANDBOX_DENY_PATHS = [], so the rendered profile carries deny = [] and the worker stays on builtin-strict Landlock, which starts cleanly and emits the required event.
  • The org state directory is confined by unix permissions instead: state joins privateModeDirectories, reusing the ownership guard's existing securePrivateDirectory (chown 0:0, chmod 0700, chown 2000:2000, verify — per-segment O_NOFOLLOW, fails closed). This runs for every Daimon org, not only Grok ones, and needs no CAP_FOWNER.

Root cause of the 0755 it replaces: privateDirectories only chowned ancestors and never chmoded, so Docker's root-owned 0755 mountpoint survived.

Nothing is lost. Landlock strict already restricts reads to CWD plus system paths, so state was reachable by a worker only with Landlock off — and the real alternative was not stronger confinement but no worker at all, since Grok refuses to start with a non-empty deny.

Review

An independent security review verified that no worker-uid reader touches state (the only child, state/wake-acceptance, is already 0700 2000:2000, so every working reader is uid 2000 or a root docker exec holding CAP_DAC_READ_SEARCH), and confirmed the broker's ancestor-traverse loop never walks through state — it visits only workspace ancestors, and state is a sibling.

The render test was tautological — it asserted against a locally built string. It now extracts and executes the deniedPaths and profileFor lines from the rendered program and asserts the resulting bytes; repopulating the deny list turns it red.

Requires the companion daimon change (fix/grok-attestation-empty-deny), which accepts an empty deny list against the pinned profile hash.

@apresmoi
apresmoi changed the base branch from fix/daimon-broker-registration-dir-mode to main September 4, 2026 03:17
@apresmoi
apresmoi merged commit 3767abf into main Sep 4, 2026
1 check 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.

1 participant