claude-code icon indicating copy to clipboard operation
claude-code copied to clipboard

[BUG] TMPDIR not set in sandbox mode if bubblewrap is installed setuid root

Open Moredread opened this issue 2 months ago • 3 comments

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

The TMPDIR environment variable is not set in sandboxed bash commands, despite issue #8194 documenting that it should be automatically set to /tmp/claude when running in sandbox mode. This causes sandbox failures when commands like bats are run using the Bash tool in sandboxing mode, leading to many unnecessary retries with it set inside the call.

Observable behavior:

  • /tmp/claude directory is created by Claude Code ✓
  • Sandbox mode is active (CLAUDE_CODE_SANDBOX=1 is set) ✓
  • TMPDIR environment variable is not set

This causes programs that rely on TMPDIR to fall back to /tmp instead of using the sandboxed /tmp/claude/ directory.

More generally, TMPDIR not being set inside the Bash sandbox tool environment should be detected and flagged, as it explicitly is set to /tmp/claude.. Alternately warn if bwrap is set setuid, as other env variables might not be passed on. Flatpak handled that explicitly for example (see CVE-2021-21261 and below).

What Should Happen?

According to issue #8194:

"When running in sandbox mode, a temporary directory at /tmp/claude/ is made available. The TMPDIR environment variable is automatically set to this path, and most programs that respect TMPDIR will automatically use /tmp/claude/"

The TMPDIR environment variable should be set to /tmp/claude in all sandboxed bash commands.

Error Messages/Logs

$ env | grep TMPDIR
# (no output - TMPDIR is unset)

$ env | grep CLAUDE_CODE_SANDBOX
CLAUDE_CODE_SANDBOX=1

$ ls -la /tmp/claude
drwxr-xr-x addy addy 4.0 KB Tue Nov  4 01:19:00 2025 /tmp/claude

Steps to Reproduce

Quick verification:

  1. Run Claude Code on NixOS with gamescope/Steam configured (or any system with setuid bwrap)
  2. Execute any bash command in sandbox mode
  3. Check env | grep TMPDIR - it will be unset

Root cause verification (Docker repro):

I've created a minimal Dockerfile that reproduces this issue. It demonstrates that when bwrap is setuid root (as it is on NixOS with gamescope), glibc filters TMPDIR from the environment per the UNSECURE_ENVVARS security policy.

The repro shows:

  • Non-setuid bwrap: TMPDIR inherits ✓
  • Setuid bwrap as root: TMPDIR inherits ✓
  • Setuid bwrap as non-root: TMPDIR filtered ✗ ← The bug
  • With --setenv TMPDIR: Works ✓ ← The fix

FROM archlinux:base-20251019.0.436919

RUN pacman -Syu --noconfirm && \
    pacman -S --noconfirm bubblewrap zsh && \
    pacman -Scc --noconfirm && \
    useradd -m demo && \
    chmod u+s /usr/bin/bwrap

RUN cat > /test.sh << 'EOF'
#!/bin/bash
echo "=== Non-setuid bwrap ==="
chmod 755 /usr/bin/bwrap
TMPDIR=/tmp/test bwrap --bind / / --dev /dev -- zsh -c 'echo "TMPDIR: ${TMPDIR:-unset}"'

echo -e "\n=== Setuid bwrap (as root) ==="
chmod u+s /usr/bin/bwrap
TMPDIR=/tmp/test bwrap --bind / / --dev /dev -- zsh -c 'echo "TMPDIR: ${TMPDIR:-unset}"'

echo -e "\n=== Setuid bwrap (as non-root) - THE BUG ==="
su demo -c 'TMPDIR=/tmp/test bwrap --bind / / --dev /dev -- zsh -c "echo \"TMPDIR: \${TMPDIR:-unset}\""'

echo -e "\n=== With --setenv TMPDIR - THE FIX ==="
su demo -c 'TMPDIR=/tmp/test bwrap --bind / / --dev /dev --setenv TMPDIR /tmp/claude -- zsh -c "echo \"TMPDIR: \${TMPDIR:-unset}\""'
EOF

RUN chmod +x /test.sh
CMD ["/test.sh"]

Quick Run

docker build -f Dockerfile.bwrap-tmpdir-demo -t bwrap-tmpdir-demo .
docker run --rm --privileged bwrap-tmpdir-demo

Expected Output

=== Non-setuid bwrap ===
TMPDIR: /tmp/test

=== Setuid bwrap (as root) ===
TMPDIR: /tmp/test

=== Setuid bwrap (as non-root) - THE BUG ===
TMPDIR: unset

=== With --setenv TMPDIR - THE FIX ===
TMPDIR: /tmp/claude

What This Proves

When bwrap is setuid and run by a non-root user, glibc filters TMPDIR. The fix: Claude Code must pass --setenv TMPDIR /tmp/claude to bwrap.

Technical background:

Claude Code uses bubblewrap (bwrap) for Linux sandboxing. When bwrap is installed setuid root (common on NixOS and other distros for privilege separation), glibc automatically filters environment variables listed in UNSECURE_ENVVARS, which includes TMPDIR.

Reference: https://sourceware.org/git/?p=glibc.git&a=blob&f=sysdeps/generic/unsecvars.h

Claude Code already uses bwrap's --setenv flag for variables like CLAUDE_CODE_SANDBOX=1. The fix is to also pass --setenv TMPDIR /tmp/claude to explicitly set TMPDIR, bypassing glibc's filtering.

Claude Model

Not sure / Multiple models

Is this a regression?

I don't know

Last Working Version

No response

Claude Code Version

2.0.32

Platform

Anthropic API

Operating System

Other Linux

Terminal/Shell

Other

Additional Information

Affected systems:

  • NixOS with gamescope/Steam (creates setuid bwrap wrapper)
  • Any Linux distribution where bwrap is installed setuid
  • Not affected: Systems where bwrap is not setuid

Workaround: Add a non setuid bwrap executable early in PATH before launching claude-code.

Related issues:

  • #8194 - Documents expected TMPDIR behavior (this issue reports it's not working)
  • #10194 - Different issue (user-set invalid TMPDIR causing failures)

Moredread avatar Nov 04 '25 02:11 Moredread

Related Issues & Documentation

Flatpak encountered and fixed this exact issue:

  • flatpak/flatpak@6d1773d - "Convert all environment variables into bwrap arguments" (CVE-2021-21261 fix)
  • flatpak/flatpak#4081 - "More consistently convert bwrap->envp into --setenv arguments" (regression fix for setuid bwrap)

Glibc documentation:

Other affected software:

NixOS context:

Moredread avatar Nov 04 '25 03:11 Moredread

Leaving here this workaround for ppl trying to use the agent sdk

Set:

query({ 
   options: {
      env: {
          ...process.env,
          XDG_CACHE_DIR: "/tmp/claude",
          XDG_CACHE_HOME: "/tmp/claude",
          TMPDIR: "/tmp/claude",
        },
...

daralthus avatar Dec 06 '25 00:12 daralthus