DiamondLightSource / DiamondLightSource/claude-sandbox
Egress jail: pasta is never reaped — orphaned processes outlive their holder netns
- Dominant language
- Shell
- Stars
- 0
- Forks
- 2
- Avg merge
- 3h 11m
- Merged PRs (30d)
- 26
Description
## Summary
`netns_launch` starts pasta and never stops it. Observed live on the dogfood box: a pasta process alive in the host netns whose holder PID no longer exists.
```
pasta PID 415189 (host netns, alive)
holder PID 172187 (gone)
```
pasta normally exits when the namespace it serves goes away, so this is a leak that only shows up when that shutdown path doesn't fire — but when it doesn't, the process is orphaned indefinitely.
## Where
In `claude-shadow`:
- `jail_fail()` kills the **holder** only:
```bash
jail_fail() {
echo "claude-sandbox: egress jail $1" >&2
if [ -n "${2:-}" ]; then kill "$2" 2>/dev/null || true; fi
exit 1
}
```
- `netns_launch()` waits on the holder, removes its temp files, and exits. pasta's PID is never captured, never killed, and there is no `trap` covering the launch.
So on every abnormal exit after the pasta attach — a `jail_fail` from the holder's routing steps, a signal to the shadow, or anything that kills the holder without tearing the netns down cleanly — pasta is left behind.
## Impact
Low severity, but not zero:
- Orphaned processes accumulate across sessions on long-lived devcontainers.
- Each one holds a `/dev/net/tun` fd and its own tap state.
- A stale pasta whose netns is gone is confusing during diagnosis: it looks like an active jail when there is none. (It cost time during the investigation in #11 — the log path `/tmp/claude-pasta.log` pointed at a mount namespace that no longer existed.)
## Related: staged resolv.conf temp file leaks on the fail path
Same function, same cause. `netns_launch` cleans up with:
```bash
rm -f "$CLAUDE_JAIL_READY" "${CLAUDE_SANDBOX_JAIL_RESOLV:-}"
```
That line is only reached on the normal path. Every `jail_fail` exit leaves the staged `/tmp/claude-jail-resolv.XXXXXX` behind. Since #11 made `jail_stage_dns` stage unconditionally, this now applies to **every** host rather than only stub-resolver ones, so the leak rate goes up.
## Suggested fix
Capture pasta's PID after attach and install a single `trap` in `netns_launch` that reaps pasta and removes both temp files on EXIT/INT/TERM, so the normal path, the `jail_fail` paths, and signal deaths all converge on one cleanup. Keep it fail-soft — cleanup must never turn a working launch into a failed one.
Worth checking whether `jail_fail` should also take the pasta PID, or whether the trap alone is sufficient (the trap is preferable: one place, no extra parameter threading through every call site).
Contributor guide
Research direction
Start at the netns_launch and jail_fail entry points in claude-shadow, then trace how pasta is attached and how the holder exits. Verify that normal, failure, and signal paths converge on cleanup of the pasta process and both temporary files, without turning a successful launch into a failure.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- linux, shell
- Domain
- networking, security
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 68/100