Skip to content

feat(runner): arbitrary host uid support via keep-id uid remap - #228

Open
seal-agent wants to merge 2 commits into
mainfrom
compass-runner-1691-arbitrary-uid-impl
Open

feat(runner): arbitrary host uid support via keep-id uid remap#228
seal-agent wants to merge 2 commits into
mainfrom
compass-runner-1691-arbitrary-uid-impl

Conversation

@seal-agent

Copy link
Copy Markdown
Contributor

The embedded compass-runner refused to start unless its real uid was 1000, blocking hosted/GA deployments where the host uid is arbitrary. This implements the frozen design (docs/designs/platform/compass-runner-arbitrary-uid/design.md): launch containers with --userns=keep-id:uid=<agent-uid>,gid=<agent-gid> so an arbitrary host uid is remapped to the baked agent uid (1000) that owns /nix and $HOME, and replace the now-wrong uid refusal with a podman version-floor preflight.

Changes

  • Userns remapContainerSpec gains a UID field; Create's argv assembly is extracted into a unit-testable createArgs, which emits --userns=keep-id:uid=,gid= (gid collapses to uid: the image bakes gid==uid==1000). createAndStart threads Workspace.UID through.
  • Startup preflightverifyRunnerUID (and its test) are deleted; PodmanCLI.VerifyUsernsRemapSupport + parsePodmanVersion replace it at the same 'ahead of every operator-input check' startup position. Hard floor podman >= 4.3 (where keep-id:uid= is available), no --uidmap fallback; the error names the required floor and the found version.
  • Egress-integrity regression — a new podman-gated test asserts that an agent exec pinned to the agent uid holds an empty effective capability set (CapEff 0000000000000000) inside a NET_ADMIN container, while the container's default-user provisioning exec (the armEgress identity) retains the capability. This pins the boundary that was reported as a root-exec escape: every agent-controllable exec already sets --user, so a compromised agent cannot flush its own egress ruleset. A future rename that drops an --user now reddens rather than silently reintroducing the escape.
  • Comment sweep — corrected the stale 'runs as root' / 'read-only mounts' comments (podman.go, agent.go, agent-image/devenv.nix): the image default user is uid 1000, not root, and the agent gateway socket mount is read-write.

Tests

  • TestCreateArgsRemapsUserns, TestParsePodmanVersion (hermetic).
  • TestKeepIDRemapMapsHostUIDToSpecUID / BindMountRoundTrip / AgentOwnsNix — red-first against bare keep-id, green after the flag flip.
  • TestAgentExecDropsNetAdminInNetAdminContainer — the egress-integrity boundary, verified on real podman 5.8.4.

All podman-gated tests pass on the dev box; build, vet, gofmt, and compass-go:lint (0 issues) clean.

Spec-impact: none. Refs SEA-1691. Closes SEA-1542.

Co-authored-by: Matt Wilkinson matt@sealedsecurity.com

@linear-code

linear-code Bot commented Aug 7, 2026

Copy link
Copy Markdown

SEA-1542

SEA-1691

@seal-agent

Copy link
Copy Markdown
Contributor Author

CI status — the red is a pre-existing image-closure drift, not this PR's content.

compass-agent-image:build fails on a fixed-output-derivation hash mismatch:

hash mismatch in fixed-output derivation compass-agent-node-modules-0.1.0.drv:
  specified: sha256-HjRWz8xklL6GwJTf0zmB7iOBnCueYtvbjNDicPhnPCc=
     got:    sha256-RFDGAGbgyOcYkkAgTZyL8Ez89EDSPZi5tX1ZhjoZA5I=

bun.lock on main resolves @oh-my-pi/pi-* to 16.5.2 (up from the ^16.4.8 range), but the FOD hash pinned at agent-image/entrypoint.nix:120 still describes the pre-bump node_modules. That's a latent break on mainmain is green only because the prior merge didn't touch agent-image/, so moon's affected-detection (inputs: agent-image/**/*) never ran the image build. This PR's only agent-image/ change is a comment-only sweep in devenv.nix, which pulled compass-agent-image into the affected set and is the first to surface the drift.

Fix layer is the pinned hash in entrypoint.nix, in the compass-agent dependency-closure lane (owner pinged) — not this PR's runner changes, which are review-clean. Holding: I'll rebase onto main once the hash fix lands there.

Open question (parked for Matt, non-blocking): if the hash fix is not imminent, decouple this PR by dropping the devenv.nix comment sweep (the only thing pulling the image into the affected set)? Tradeoff: unblocks the runner change immediately, but the frozen record listed devenv.nix ~:69-82 as part of the comment sweep, and dropping it leaves those comments describing the old keep-id/verifyRunnerUID behavior. Default assumption: hold + rebase (no scope change, no lane crossing), since the dep fix is likely imminent.

seal-agent and others added 2 commits August 7, 2026 16:43
The embedded compass-runner refused to start unless its real uid was 1000, blocking hosted/GA deployments where the host uid is arbitrary. This implements the frozen design (`docs/designs/platform/compass-runner-arbitrary-uid/design.md`): launch containers with `--userns=keep-id:uid=<agent-uid>,gid=<agent-gid>` so an arbitrary host uid is remapped to the baked agent uid (1000) that owns `/nix` and `$HOME`, and replace the now-wrong uid refusal with a podman version-floor preflight.

### Changes

- **Userns remap** — `ContainerSpec` gains a `UID` field; `Create`'s argv assembly is extracted into a unit-testable `createArgs`, which emits `--userns=keep-id:uid=,gid=` (gid collapses to uid: the image bakes gid==uid==1000). `createAndStart` threads `Workspace.UID` through.
- **Startup preflight** — `verifyRunnerUID` (and its test) are deleted; `PodmanCLI.VerifyUsernsRemapSupport` + `parsePodmanVersion` replace it at the same 'ahead of every operator-input check' startup position. Hard floor podman >= 4.3 (where `keep-id:uid=` is available), no `--uidmap` fallback; the error names the required floor and the found version.
- **Egress-integrity regression** — a new podman-gated test asserts that an agent exec pinned to the agent uid holds an empty effective capability set (`CapEff 0000000000000000`) inside a NET_ADMIN container, while the container's default-user provisioning exec (the `armEgress` identity) retains the capability. This pins the boundary that was reported as a root-exec escape: every agent-controllable exec already sets `--user`, so a compromised agent cannot flush its own egress ruleset. A future rename that drops an `--user` now reddens rather than silently reintroducing the escape.
- **Comment sweep** — corrected the stale 'runs as root' / 'read-only mounts' comments (`podman.go`, `agent.go`, `agent-image/devenv.nix`): the image default user is uid 1000, not root, and the agent gateway socket mount is read-write.

### Tests

- `TestCreateArgsRemapsUserns`, `TestParsePodmanVersion` (hermetic).
- `TestKeepIDRemapMapsHostUIDToSpecUID` / `BindMountRoundTrip` / `AgentOwnsNix` — red-first against bare keep-id, green after the flag flip.
- `TestAgentExecDropsNetAdminInNetAdminContainer` — the egress-integrity boundary, verified on real podman 5.8.4.

All podman-gated tests pass on the dev box; build, vet, gofmt, and `compass-go:lint` (0 issues) clean.

Spec-impact: none. Refs SEA-1691. Closes SEA-1542.

Co-authored-by: Matt Wilkinson <matt@sealedsecurity.com>
Address the three low-severity findings from review of the arbitrary-host-uid
PR (no behavior change):

- Rescope the egress-integrity test docstring to the property it actually
  proves — that podman's --user strips the container's ambient CAP_NET_ADMIN
  — and point to agentenv_test.go / lifecycle_test.go for the production
  call-site coverage, rather than claiming a rename-drop reddens this test.
- Add the major-below-floor row (3.4.4, Ubuntu 22.04 LTS) to
  TestParsePodmanVersion so the major<4 disjunct of the floor predicate is
  covered by the design record's named real-world GA-refusal case.
- Scope the ExecSpec/StreamingExecSpec User doc comments' uid-1000 fact to the
  compass-agent image, since the seam itself is image-agnostic.

Co-authored-by: Matt Wilkinson <matt@sealedsecurity.com>
@seal-agent
seal-agent force-pushed the compass-runner-1691-arbitrary-uid-impl branch from e1216c6 to 6da0cd5 Compare August 7, 2026 20:45
@seal-agent

Copy link
Copy Markdown
Contributor Author

Correction to the previous comment — root cause was mis-diagnosed, now rebased.

The earlier comment attributed the red to a stale FOD pin in agent-image/entrypoint.nix needing a bump. That was wrong, and I'm retracting it. Ground truth, source-verified:

  • The pin is correct on the gate platform. main (94754d0a) is green and its full-sweep built compass-agent-node-modules to sha256-HjRW… — exactly the pinned value — with bun.lock and entrypoint.nix byte-identical to this PR's base (jj diff base..main = 0 files for both). A bump to RFDG… would break main on x64.
  • The red was a stale-base transient. This PR's run (31210910727) built the FOD to RFDG… on the pre-rebase base e226b60a. This PR's only agent-image/ change is comment-only, which cannot alter a content-addressed FOD output — so the RFDG was FOD nondeterminism/cache artifact on the stale base, not a real input delta.
  • nilaway is non-blocking here. main is green while carrying 302 ##[error] compass-go:nilaway annotations (same files). This PR's moon summary was 9 completed, 1 failed, 1 skipped — the one failure was compass-agent-image:build, not nilaway.

Action taken: rebased onto current main (1f4a0901) and resubmitted (head 6da0cd5d). CI is re-running the image build from inputs identical to main's green build. Verifying at source when it settles.

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