← the record

Review f29544721ecd

internal/sandbox at 5ea687f383, reviewed by gemini-3.6-flash, verified by codex.

repo
git@github.com:pdbethke/corralai
commit
5ea687f3836b705bc422564c52efa7fd84bd7079
scope
internal/sandbox · go
reviewer
gemini-3.6-flash
verifier
codex (codex-cli 0.152.0) different model than the reviewer different vendor: google → openai
substrate
workspace (a detached worktree at the commit; not a jail)
started
2026-09-07 20:15 UTC
audited party
Peter D Bethke · co-authored: Peter D Bethke Claude Code
files shown
23 file(s)

Findings (1)

R1 high reproduced confirmed by a person

containerIsolator.Wrap ignores Bind.PerEntry, mounting host directories whole-tree read-only into container backends and causing EROFS errors for cache-writing toolchains.

where
internal/sandbox/container.go:183
tier
REPRODUCED (declared REPRODUCED)
the script that was executed
cat << 'EOF' > internal/sandbox/repro_perentry_test.go
package sandbox

import (
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"testing"
)

func TestReproContainerPerEntry(t *testing.T) {
	hostDir := t.TempDir()
	_ = os.MkdirAll(filepath.Join(hostDir, "pkg1"), 0755)
	_ = os.MkdirAll(filepath.Join(hostDir, ".vite"), 0755)

	c := containerIsolator{runtime: "docker", image: "img"}
	argv, err := c.Wrap("echo hi", Options{
		Workspace:     "/ws",
		ReadOnlyBinds: []Bind{{Host: hostDir, Target: "/ws/node_modules", PerEntry: true}},
	}, nil)
	if err != nil {
		t.Fatalf("unexpected wrap error: %v", err)
	}

	joined := strings.Join(argv, " ")
	badMount := "-v " + hostDir + ":/ws/node_modules:ro"
	if strings.Contains(joined, badMount) {
		fmt.Printf("EVIDENCE: containerIsolator.Wrap ignored PerEntry=true and produced whole-tree read-only mount flag: %s\nFull argv: %v\n", badMount, argv)
		return
	}
	t.Fatalf("defect not present")
}
EOF
go test -v ./internal/sandbox -run TestReproContainerPerEntry
what it printed
=== RUN   TestReproContainerPerEntry
EVIDENCE: containerIsolator.Wrap ignored PerEntry=true and produced whole-tree read-only mount flag: -v /tmp/TestReproContainerPerEntry1793314079/001:/ws/node_modules:ro
Full argv: [docker run --rm --network=none --read-only --cap-drop=ALL --pids-limit=512 --memory=2g --memory-swap=2g --security-opt=no-new-privileges --tmpfs /tmp:rw,exec,nosuid,nodev --tmpfs /home/agent:rw,exec,nosuid,nodev -e HOME=/home/agent -e GOTOOLCHAIN=local -v /ws:/ws -w /ws -v /tmp/TestReproContainerPerEntry1793314079/001:/ws/node_modules:ro img sh -c echo hi]
--- PASS: TestReproContainerPerEntry (0.00s)
PASS
ok  	github.com/pdbethke/corralai/internal/sandbox	0.003s

refutation — codex STANDS

I traced the flag from its contract and callers through both implementations. internal/sandbox/sandbox.go:30-52 defines PerEntry as mounting top-level entries separately so Target remains writable, explicitly to prevent JavaScript cache writes from failing with EROFS. internal/adequacy/jail.go:128-138 sets PerEntry=true for dependency binds. internal/sandbox/isolator_linux.go:145-158 honors it using perEntryBinds, but internal/sandbox/container.go:193-195 unconditionally emits one `-v Host:Target:ro` mount and never examines PerEntry. I also checked container backend selection and found no adapter that expands these binds beforehand. Thus the reviewer’s supplied script does not reproduce an actual cache-writing runtime failure, but its asserted mount shape is correct and the claim could not be refuted.

human verdict

confirmed by pdbethke — real, reproduced: the container backend ignores Bind.PerEntry, which the bwrap backend honours; cache-writing toolchains hit EROFS there only

ref f29544721ecdcda86b16638f65f013a5e9257e2c8c54b622b0d7a362bbeaad56#R1

Looked at and could not break (5)

Recorded so that "found nothing here" can be told apart from "never looked here".

Reviewer's opinion

The internal/sandbox package is generally well-structured and carefully guarded with unit tests covering backend isolation, secret scrubbing, process termination, and context cancellation semantics. However, containerIsolator in container.go completely ignores the PerEntry flag on ReadOnlyBinds (finding 1), unlike bwrapIsolator. When running commands inside container runtimes (docker/podman), node_modules trees marked as PerEntry are mounted as single read-only volumes, causing JavaScript toolchains (vite, vitest, jest) that write to node_modules/.vite or node_modules/.cache to fail with EROFS.

Verifier's opinion

R1 stands. The reviewer’s test proves only the malformed container argv, not an actual EROFS execution, but the code and documented Bind contract support the full defect: production callers set PerEntry=true specifically for dependency trees, while the container backend ignores it and mounts the parent read-only.