Skip to content

Add build isolation to sandbox build_sdist and build_wheel steps #1019

@pavank63

Description

@pavank63

Problem

During build_sdist and build_wheel, Fromager executes arbitrary code from upstream packages (their setup.py, build backends, custom build scripts). This code currently runs with the same privileges as Fromager itself — full filesystem access, full environment variables, and
(when running in a builder container) access to credentials like .netrc.

A compromised or malicious build backend can:

  • Read .netrc credentials (GitLab tokens, PyPI upload credentials)
  • Modify system directories (/usr) or Fromager's own venv
  • Access sensitive environment variables (NETRC, TWINE_PASSWORD, NGC_API_KEY)
  • Interfere with parallel builds via shared /tmp, IPC, or process signaling
  • Exfiltrate stolen data over the network (when network isolation is disabled)

Proposal

Extend the existing unshare-based network isolation (#477) to create mount, PID, and IPC namespaces for build steps. A new --build-isolation flag sandboxes PEP 517 hook calls with:

  • Mount namespace: Makes /usr read-only (bind + remount), hides credentials by overmounting $HOME with tmpfs, gives each build its own /tmp
  • PID namespace: Build process cannot see or signal other processes
  • IPC namespace: Isolated shared memory, semaphores, message queues
  • Network namespace: Inherited from existing --network-isolation
  • Environment scrubbing: Strips NETRC, TWINE_PASSWORD, NGC_API_KEY and similar sensitive variables before executing the build backend

The sandbox applies only to build_sdist and build_wheel (via the PEP 517 hook runner in dependencies.py). Source download, dependency installation, and upload steps are unaffected since they need network access and/or credentials.

Alternatives and Their Caveats

Why unshare and not bubblewrap: bwrap was evaluated in #473 and rejected because it requires --privileged or CAP_SYS_ADMIN inside containers. This is still the case (containers/bubblewrap#505 remains open). The unshare approach works in unprivileged Podman/Docker containers out of the box.

Why not ephemeral user accounts (useradd per package): Tested inside builder containers. Basic file permission blocking works (.netrc returns "Permission denied"), but /tmp, IPC, and process list are still shared across builds. Parallel useradd contends on /etc/passwd locks. Once you mitigate all the gaps, you end up reimplementing unshare.

Why not systemd DynamicUser=yes: Would be ideal — it provides exactly the isolation we need. But it requires systemd as PID 1 inside the container, which build containers don't have. Tested: systemd-run fails with "System has not been booted with systemd as init system." Design influenced by systemd's DynamicUser= (reference) — same kernel primitives, applied without the systemd dependency.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions