Skip to content

Scrub the guest environment for the process backend - #285

Merged
seanwevans merged 1 commit into
mainfrom
claude/process-backend-env-scrub
Aug 15, 2026
Merged

Scrub the guest environment for the process backend#285
seanwevans merged 1 commit into
mainfrom
claude/process-backend-env-scrub

Conversation

@seanwevans

Copy link
Copy Markdown
Owner

The problem

ProcessSandbox launched its guest with subprocess.Popen(...) and no env=, so the child inherited the supervisor's entire os.environ. Confirmed on this branch's parent commit:

os.environ["MY_SECRET_TOKEN"] = "hunter2-supersecret"
sb = iso.spawn("p2", backend="process", allowed_imports=["os"])
sb.exec("import os; post(os.environ.get('MY_SECRET_TOKEN'))")
sb.recv(5)   # -> 'hunter2-supersecret'

Any credential held by the host process — AWS_SECRET_ACCESS_KEY, GITHUB_TOKEN, session secrets — was readable by guest code with a one-line os.environ.get(). This is the mode SECURITY.md designates as the security boundary, and no amount of seccomp/Landlock confinement helps once the secret is already inside the guest's address space.

The change

Build the child environment from a fixed allow-list rather than handing over a copy of the host's:

  • PYTHONPATH, PYTHONHOME, LD_LIBRARY_PATH — module and shared-library resolution
  • LANG, LC_ALL, LC_CTYPE, PYTHONIOENCODING, PYTHONUTF8 — text encoding
  • TMPDIR — the guest should land where the host chose, which may be the only writable directory its policy grants
  • PATH — set to a fixed /usr/local/bin:/usr/bin:/bin, not forwarded. The child is launched by absolute path (sys.executable), so PATH is never consulted to start it; forwarding the host's value only leaks machine layout.

An allow-list rather than a deny-list, so a credential variable nobody anticipated is withheld by default.

LD_LIBRARY_PATH is forwarded deliberately: conda and custom CPython builds need it to locate libpython, and dropping it would stop the child from starting at all. It names library directories, not secrets.

Callers that need to give the guest real configuration pass env=, merged last so an explicit value always wins.

Tests

Six new tests in tests/test_process_backend.py: the secret no longer crosses the boundary, the env is a subset of the allow-list (so future variables are withheld), PATH is the fixed value rather than the supervisor's, build_child_env filters correctly given a synthetic source, extra overrides, and an explicit env= reaches the guest.

Full suite: 511 passed, 6 skipped (was 505 before these six). flake8 clean, black/isort applied.

SECURITY.md §2 gains this as guarantee 8.

Scope

One orthogonal fix; quota forwarding and the fail-open filesystem default are separate branches.


Generated by Claude Code

The process backend launched its guest with subprocess.Popen and no env=,
so the child inherited the supervisor's entire os.environ. Any credential
held by the host process -- AWS keys, API tokens, session secrets -- was
readable by guest code with a plain os.environ.get(), which defeats the
point of the boundary regardless of how well seccomp and Landlock confine
the rest of the process.

Build the child environment from a fixed allow-list instead: the variables
the interpreter needs to boot and to resolve pyisolate (PYTHONPATH,
PYTHONHOME, LD_LIBRARY_PATH), plus locale and TMPDIR. PATH is set to a
fixed minimal value rather than forwarded, since the child is launched by
absolute path and the host's PATH only leaks machine layout.

LD_LIBRARY_PATH is forwarded deliberately: conda and custom CPython builds
need it to find libpython, and dropping it stops the child from starting.

Callers that need to hand the guest real configuration can pass env=, which
is merged last so an explicit value always wins.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012ebvMQ3vLxdK3joymz6Feg
@seanwevans
seanwevans merged commit 7c8ed3f into main Aug 15, 2026
18 checks passed
@seanwevans
seanwevans deleted the claude/process-backend-env-scrub branch August 15, 2026 20:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants