Comparing two RL environment architectures for coding bug-fix tasks (agent edits repo, hidden tests grade the fix):
Architecture A (uid-wall + in-process grading):
- Keep full git history in the container but hide it behind root:700 permissions
- Agent shell dropped to uid 1000 via
setpriv - Grading happens in-process:
shutil.copytreerepo to/tmp,git applyhidden test patch, run pytest, cleanup infinallyblock - Risk: privilege escalation reveals git history (branches contain the fix and hidden tests)
- Risk: grading cleanup failure leaks hidden tests to disk
Architecture B (git-scrub + separate verifier container):
- Scrub git history entirely:
rm -rf .git && git init && git add -A && git commit -m base - Hidden tests live only in a separate verifier container (never in agent's filesystem)
- Agent patch collected via
git diff --cached, filtered throughapply_agent_patch.pyto strip test/config file modifications before grading - Verifier runs with
network_mode: no-network— can't exfiltrate
Architecture B is strictly stronger: there's no history to leak (vs. permission-based hiding), tests are architecturally isolated (vs. cleanup-dependent), and agent patches are filtered (vs. trusting the agent not to modify grading infrastructure).
However, Architecture A has one defense-in-depth practice worth adopting everywhere: running the agent as a non-root user. Even with git scrubbed and tests in a separate container, root inside the environment container gives agents unnecessary filesystem access. Adding USER agent (uid 1000) to the environment Dockerfile is cheap and reduces the blast radius of any container escape.
Additional hardening layers in Architecture B worth noting: canary footers for training contamination detection, HTTPS apt mirrors for SNI-compatible egress allowlists, Node.js pre-baking to remove raw.githubusercontent.com from runtime allowlists, and automated hardening audit tools that check all these properties.