Part 3 of 4 · 2 min read
Exercises
Easy
Easy 1: Run it (30 minutes)
Run python -m pytest -q and python run_all.py in code/mini-sandbox.
Done when: you report which naive checks failed and which probes were stopped.
Easy 2: Add three escapes (30 minutes)
Add three new path, URL or command attacks of your own.
Done when: you show whether each strong check blocks them.
Easy 3: Why wall clock (30 minutes)
Run sleep_forever with only a CPU limit.
Done when: you explain what happened and why both limits are needed.
Hint
a sleeping process uses almost no CPU time.
Medium
Medium 1: Process limits (3 hours)
Add RLIMIT_NPROC and a safe probe that spawns a few children.
Done when: the limit denies new processes past your cap (do not write a fork bomb).
Hint
set the limit in preexec_fn before exec; the child inherits it.
Medium 2: Redirects (3 hours)
Make a local test server that redirects to a non-allowlisted host.
Done when: your fetch wrapper re-validates each hop and blocks it.
Medium 3: Safe open (4 hours)
Implement open relative to a directory descriptor with O_NOFOLLOW.
Done when: a test that swaps a symlink between check and open fails to escape.
Hint
os.open(name, os.O_RDONLY | os.O_NOFOLLOW, dir_fd=fd).
Hard
Hard 1: Container run (8 hours)
Run the probes in a container with no network, a read-only root and pid and memory limits. (I did not run this, so your numbers are the ground truth.)
Done when: you report what the container stopped that rlimits did not.
Hint
--cap-drop ALL and a non-root user are worth adding.
Hard 2: Egress proxy (8 hours)
Put a small allowlisting proxy between a sandbox and the network. (I did not run this.)
Done when: a request to a non-listed host fails and every request is logged.
Hard 3: Escape hunt (8 hours)
Try to escape your own sandbox. (I did not run this.)
Done when: you list each attempt, what blocked it, and one fix.