Skip to content

fix(sandbox,danger): residual hardening after sandbox networks - #335

Merged
jkyberneees merged 4 commits into
mainfrom
fix/residuals-2026-10-25
Oct 25, 2026
Merged

jkyberneees merged 4 commits into
mainfrom
fix/residuals-2026-10-25

Conversation

@jkyberneees

Copy link
Copy Markdown
Contributor

Summary

Follow-up hardening for the sandbox network support in v2.36.0, plus a classifier fix and a test-suite flake fix found along the way.

Changes

Classifier: a $HOME spelled through /private is protected again (internal/danger)

  • The path classifier strips the /private prefix from macOS paths (/private/var/… → /var/…) before comparing, but compared them with $HOME as spelled.
  • With HOME under /private/var or /private/tmp, writes to ~/.ssh keys, shell rc files and odek's ~/.odek trust anchors classified local_write instead of system_write.
  • accountHomes now also compares the stripped spelling. It only adds home candidates, so no verdict can go down.

Project-requested networks need a recorded approval (cmd/odek)

  • The setup-time network check skipped enforcement when no approval had been recorded in the process.
  • The sandbox config now carries the project's own network request. Setup refuses project-contributed networks unless an approval covering their IDs was recorded; ODEK_APPROVE_PROJECT_SANDBOX=1 still approves.
  • Every current surface runs the approval gate first. This closes the gap for any path that doesn't.

Sub-agents are pinned to their parent's network IDs

  • The parent writes the IDs of the networks it attached into the task file. A child refuses any network that no longer carries one of them.
  • Before, a network recreated under the same name between the parent's and the child's start was attached under its new ID.

Docker-gated tests run on Docker Desktop

  • TestMain swaps HOME for a fixture directory, which hides the docker CLI context Docker Desktop uses, so every Docker-gated test skipped as "Docker daemon unavailable".
  • TestMain now pins the active context's endpoint as DOCKER_HOST before the swap. An explicit DOCKER_HOST or DOCKER_CONTEXT is left alone.

Intermittent TempDir RemoveAll cleanup: unlinkat: bad file descriptor failures

  • TestRED_RtOps_KeyFDStrictParse handed a live pipe's descriptor to readKeyFromInheritedFD, which closes the descriptor it is given.
  • The pipe's finalizer later closed the same number again after the kernel had reused it, typically for the directory fd os.RemoveAll was walking in a parallel test.
  • The test now passes a dup, and a meta-test requires every fd-handoff test to do the same.

Docs: SECURITY.md, SANDBOXING.md, AGENTS.md.

Testing

  • Regression tests: new TestRED_* tests for each fix, including a revert check for each.
  • Full suites: go test -count=1 for ./cmd/odek, ./internal/sandbox, ./internal/config and ./internal/danger (with a non-root HOME) pass.
  • The flake: ./cmd/odek with coverage was run three times in a row with no bad file descriptor failures. Before the fix it showed up in most full runs on this machine.
  • Docker E2E without DOCKER_HOST set: ODEK_E2E=true go test ./cmd/odek -run TestE2E_Sandbox, 5 tests run and pass (all 5 skipped before).
  • Race and lint: go test -race on ./cmd/odek, ./internal/sandbox and ./internal/config passes. golangci-lint reports 0 issues and gofmt is clean.
  • Coverage: unchanged at one decimal (cmd/odek 78.9%, internal/danger 93.3%, internal/sandbox 93.7%). Changed executable lines in cmd/odek: 71/76. The rest is setupSandbox wiring that needs a live daemon; it's pinned by a source test and exercised by the Docker E2E.
  • Review: an adversarial review (Opus) of the branch found no blocking or major issues.

🤖 Generated with Claude Code

jkyberneees and others added 4 commits October 25, 2026 14:16
classifyPathLexical strips the /private prefix from the target path, but
accountHomes compared it with $HOME as spelled. With HOME under
/private/var or /private/tmp (macOS physical paths), writes to ~/.ssh,
shell rc files and odek's trust anchors classified as local_write
instead of system_write. Compare against the stripped spelling too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tworks by ID

The setup-time grant check returned early when no approval had been
recorded in the process, so a surface that reached setupSandbox without
the approval gate would attach project-requested networks unchecked. The
sandbox config now carries the project's own network request, and setup
refuses those networks unless an approval with their IDs was recorded
(ODEK_APPROVE_PROJECT_SANDBOX=1 still approves).

Sub-agents narrowed networks by name only, so a network recreated under
the same name between the parent's and the child's start was attached
under its new ID. The parent now stamps the IDs of the networks it
attached into the task file, and the child refuses any network that no
longer carries one of them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TestMain swaps HOME for a fixture directory, which hides the docker CLI
context Docker Desktop relies on, so every Docker-gated test skipped as
"Docker daemon unavailable" on macOS. Pin the active context's endpoint
as DOCKER_HOST before the swap (an explicit DOCKER_HOST or DOCKER_CONTEXT
is left alone).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TestRED_RtOps_KeyFDStrictParse passed r.Fd() of a live pipe to
readKeyFromInheritedFD, which closes the descriptor it is handed. r's
finalizer later closed the same number again after the kernel had
reused it, typically for the directory fd os.RemoveAll was walking in a
parallel test, which then failed with "unlinkat: bad file descriptor".
Pass a dup, close r explicitly, and pin the rule for every fd-handoff
test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
odek 0f08e48 Commit Preview URL

Branch Preview URL
Oct 25 2026, 01:46 PM

@jkyberneees
jkyberneees merged commit c57281c into main Oct 25, 2026
10 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.

1 participant