internal/sandbox at 5ea687f383, reviewed by gemini-3.6-flash, verified by codex.
containerIsolator.Wrap ignores Bind.PerEntry, mounting host directories whole-tree read-only into container backends and causing EROFS errors for cache-writing toolchains.
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=== 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
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.
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
Recorded so that "found nothing here" can be told apart from "never looked here".
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.
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.