Skip to content

feat(build): add --build-isolation for sandboxed builds#1073

Open
pavank63 wants to merge 2 commits intopython-wheel-build:mainfrom
pavank63:build-isolation
Open

feat(build): add --build-isolation for sandboxed builds#1073
pavank63 wants to merge 2 commits intopython-wheel-build:mainfrom
pavank63:build-isolation

Conversation

@pavank63
Copy link
Copy Markdown
Contributor

@pavank63 pavank63 commented Apr 20, 2026

Summary

  • Add --build-isolation flag that sandboxes build backend subprocesses using ephemeral Unix users and Linux namespaces (network, PID, IPC, UTS)
  • Ephemeral user cannot read credential files like .netrc (root:root 600), network namespace blocks exfiltration, PID/IPC namespaces prevent cross-process
    interference
  • Works in unprivileged containers (Podman/Docker) without --privileged or --cap-add SYS_ADMIN
  • Add FROMAGER_SCRUB_ENV_VARS environment variable for downstream build systems to strip sensitive env vars from build subprocesses

Test plan

  • All 432 unit tests pass
  • Verified .netrc is unreadable by build subprocess (Permission denied)
  • Verified network is unreachable from build subprocess
  • Verified PID isolation (only build process visible)
  • Verified useradd/userdel overhead is ~10ms each
  • Verified uv pip install for build deps correctly opts out of isolation

Closes: #1019

🤖 Generated with Claude Code

pavank63 and others added 2 commits April 20, 2026 11:25
Add build isolation using ephemeral Unix users and Linux namespaces
to prevent untrusted build backends from reading credentials or
interfering with the host system.

The isolation mechanism:
- Creates an ephemeral Unix user (useradd/userdel ~10ms each)
  that cannot read credential files like .netrc (root:root 600)
- Uses Linux namespaces via unshare for network (no routing),
  PID (process visibility), IPC (shared memory), and UTS (hostname)
- setpriv drops privileges before entering namespaces, avoiding
  the setgroups() denial that affects runuser in user namespaces
- Works in unprivileged containers (Podman/Docker) without
  --privileged or --cap-add SYS_ADMIN

New CLI flag --build-isolation/--no-build-isolation supersedes
--network-isolation for build steps. The flag threads through
WorkContext, BuildEnvironment, and the build backend hook caller.
Build dependency installation (uv pip install) explicitly opts out
since it needs access to the local PyPI mirror.

Signed-off-by: Pavan Kalyan Reddy Cherupally <pcherupa@redhat.com>
Co-Authored-By: Claude <claude@anthropic.com>
When build isolation is enabled, remove environment variables listed
in FROMAGER_SCRUB_ENV_VARS (comma-separated) from the build subprocess
environment. This allows downstream build systems to strip sensitive
variables (e.g. internal registry tokens, CI credentials) without
hardcoding any variable names in fromager itself.

Signed-off-by: Pavan Kalyan Reddy Cherupally <pcherupa@redhat.com>
Co-Authored-By: Claude <claude@anthropic.com>
@pavank63 pavank63 requested a review from a team as a code owner April 20, 2026 16:44
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 20, 2026

📝 Walkthrough

Walkthrough

This pull request adds build isolation support to Fromager, extending the existing network isolation infrastructure. The changes introduce a new --build-isolation CLI flag, runtime detection of build isolation capability, and a bash script that sandboxes PEP 517 hook execution using Linux namespaces and ephemeral user accounts. The build_isolation parameter is threaded through the call stack from __main__.py into WorkContext, BuildEnvironment, and external_commands.run(). A new script creates isolated execution environments with UTS, network, PID, and IPC namespace isolation, environment variable scrubbing for sensitive credentials, and offline cargo enforcement. Build isolation applies only to build_sdist and build_wheel steps.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the primary change: adding a --build-isolation flag for sandboxed builds.
Linked Issues check ✅ Passed The PR fully addresses issue #1019 requirements: implements --build-isolation flag, uses ephemeral users and unshare-based isolation (network, PID, IPC, UTS namespaces), prevents credential/env var leaks, and correctly exempts dependency installation.
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing build isolation: CLI flag threading through WorkContext/BuildEnvironment, isolation wrapper in external_commands, build isolation script, and environment variable scrubbing—no out-of-scope modifications.
Description check ✅ Passed The PR description clearly relates to the changeset, detailing the addition of build isolation via ephemeral users and Linux namespaces across multiple modules.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/fromager/__main__.py`:
- Around line 33-40: The current module-level call to
external_commands.detect_build_isolation() runs on import and creates/deletes a
system user for every CLI invocation; move this probe to be lazy by removing the
import-time call and instead implement a helper (e.g.,
_ensure_build_isolation_supported()) that calls
external_commands.detect_build_isolation() only when build isolation is actually
requested (the branch that checks a --build-isolation flag), and preserve the
exported flags SUPPORTS_BUILD_ISOLATION and BUILD_ISOLATION_ERROR by setting
them inside that helper (or returning results) so they are initialized only
on-demand; alternatively make the probe a safe no-op that simply verifies the
presence of the isolation script and that unshare --user --map-root-user -- true
succeeds (without creating users) before enabling isolation.

In `@src/fromager/run_build_isolation.sh`:
- Around line 56-62: The command existence checks using "command -v ip" and
"command -v hostname" use the wrong redirection order ("2>&1 >/dev/null") which
leaves stderr attached; change both checks to redirect stdout then stderr (e.g.,
">/dev/null 2>&1") so both streams are discarded when checking presence of ip
and hostname in the run_build_isolation.sh script.
- Around line 37-64: The EXIT trap that runs userdel for BUILD_USER never
executes because the script uses exec to replace the shell with setpriv; change
the invocation so the script does not exec (i.e., invoke setpriv/unshare in a
child process and wait for it) or explicitly perform cleanup after the child
returns: remove the exec before setpriv --reuid/--regid/... -- unshare ... and
instead spawn the command (preserving BUILD_USER, BUILD_UID, BUILD_GID usage),
capture its exit status, then run the existing trap/cleanup (userdel
"$BUILD_USER" 2>/dev/null || true) before exiting so ephemeral fmr_* accounts
are always removed.
- Around line 40-42: The current chmod command makes the build dir
world-accessible; instead change ownership to the ephemeral build user/group so
only that user (or its primary group) can access it: locate the block that
checks FROMAGER_BUILD_DIR and replace the chmod -R o+rwX "$FROMAGER_BUILD_DIR"
call with a chown/chgrp approach that sets ownership to "$BUILD_USER" (and/or
its primary group) and applies permissions for owner/group (not "other"), e.g.
chown -R "$BUILD_USER": "$FROMAGER_BUILD_DIR" and adjust mode to u+rwX,g+rwX
(with safe stderr redirection and || true to preserve existing error handling).
Ensure references to FROMAGER_BUILD_DIR and BUILD_USER (and any fmr_* ephemeral
user naming convention) are used so the correct directory/user is targeted.
- Around line 52-53: The unshare invocation in run_build_isolation.sh currently
omits the mount namespace flag; update the exec line that calls setpriv/unshare
(the command including "setpriv --reuid=... --regid=... --clear-groups -- \
unshare --uts --net --pid --ipc --fork --map-root-user -- \") to include --mount
(i.e., add --mount after --ipc) so the script actually creates a mount namespace
and enables making system dirs read-only and isolating /tmp as promised.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f7e0a88e-542a-4c84-8bba-81cd8f699e26

📥 Commits

Reviewing files that changed from the base of the PR and between 1926e29 and 5a54dff.

📒 Files selected for processing (6)
  • src/fromager/__main__.py
  • src/fromager/build_environment.py
  • src/fromager/context.py
  • src/fromager/dependencies.py
  • src/fromager/external_commands.py
  • src/fromager/run_build_isolation.sh

Comment thread src/fromager/__main__.py
Comment on lines +33 to +40
try:
external_commands.detect_build_isolation()
except Exception as e:
SUPPORTS_BUILD_ISOLATION: bool = False
BUILD_ISOLATION_ERROR: str | None = str(e)
else:
SUPPORTS_BUILD_ISOLATION = True
BUILD_ISOLATION_ERROR = None
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Probe runs on every CLI invocation — creates and deletes a real system user for fromager --help.

detect_build_isolation() executes at module import, which means every invocation of the CLI (including --help, --version, shell-completion calls, and subcommands that never use isolation) forks useradd/userdel under root. Combined with the EXIT-trap-vs-exec leak flagged in run_build_isolation.sh, this will steadily pollute /etc/passwd on any host that runs Fromager frequently.

Consider detecting lazily only when --build-isolation is requested, or make the probe a no-op (e.g., just check that the script exists and unshare --user --map-root-user -- true works) instead of actually creating a user:

🛠 Lazy-detect sketch
-try:
-    external_commands.detect_build_isolation()
-except Exception as e:
-    SUPPORTS_BUILD_ISOLATION: bool = False
-    BUILD_ISOLATION_ERROR: str | None = str(e)
-else:
-    SUPPORTS_BUILD_ISOLATION = True
-    BUILD_ISOLATION_ERROR = None
+SUPPORTS_BUILD_ISOLATION: bool | None = None
+BUILD_ISOLATION_ERROR: str | None = None
+
+def _ensure_build_isolation_supported() -> None:
+    global SUPPORTS_BUILD_ISOLATION, BUILD_ISOLATION_ERROR
+    if SUPPORTS_BUILD_ISOLATION is not None:
+        return
+    try:
+        external_commands.detect_build_isolation()
+    except Exception as e:
+        SUPPORTS_BUILD_ISOLATION = False
+        BUILD_ISOLATION_ERROR = str(e)
+    else:
+        SUPPORTS_BUILD_ISOLATION = True

…then call _ensure_build_isolation_supported() inside the if build_isolation: branch at line 252.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/fromager/__main__.py` around lines 33 - 40, The current module-level call
to external_commands.detect_build_isolation() runs on import and creates/deletes
a system user for every CLI invocation; move this probe to be lazy by removing
the import-time call and instead implement a helper (e.g.,
_ensure_build_isolation_supported()) that calls
external_commands.detect_build_isolation() only when build isolation is actually
requested (the branch that checks a --build-isolation flag), and preserve the
exported flags SUPPORTS_BUILD_ISOLATION and BUILD_ISOLATION_ERROR by setting
them inside that helper (or returning results) so they are initialized only
on-demand; alternatively make the probe a safe no-op that simply verifies the
presence of the isolation script and that unshare --user --map-root-user -- true
succeeds (without creating users) before enabling isolation.

Comment on lines +37 to +64
trap 'userdel "$BUILD_USER" 2>/dev/null || true' EXIT

# Make build dir writable by ephemeral user if set
if [ -n "${FROMAGER_BUILD_DIR:-}" ] && [ -d "$FROMAGER_BUILD_DIR" ]; then
chmod -R o+rwX "$FROMAGER_BUILD_DIR" 2>/dev/null || true
fi

# --- Enter namespaces as ephemeral user ---
# setpriv drops to the ephemeral user, then unshare creates namespaces.
# --map-root-user maps the ephemeral user to UID 0 inside the namespace
# so it can run ip/hostname.

BUILD_UID=$(id -u "$BUILD_USER")
BUILD_GID=$(id -g "$BUILD_USER")

exec setpriv --reuid="$BUILD_UID" --regid="$BUILD_GID" --clear-groups -- \
unshare --uts --net --pid --ipc --fork --map-root-user -- \
/bin/bash -c '
# bring loopback up
if command -v ip 2>&1 >/dev/null; then
ip link set lo up
fi
# set hostname
if command -v hostname 2>&1 >/dev/null; then
hostname localhost
fi
exec "$@"
' -- "$@"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Ephemeral user leaks: EXIT trap never fires after exec.

exec at line 52 replaces the current shell, so Bash never reaches its exit path and the trap ... EXIT set on line 37 is discarded. Every successful build (the common case) leaves a fmr_* entry behind in /etc/passwd//etc/shadow, and they accumulate indefinitely. Only the failure-before-exec path currently cleans up.

Run the child without exec and let the trap do the cleanup, or clean up explicitly:

🛠 Suggested fix
-exec setpriv --reuid="$BUILD_UID" --regid="$BUILD_GID" --clear-groups -- \
-   unshare --uts --net --pid --ipc --fork --map-root-user -- \
-   /bin/bash -c '
+setpriv --reuid="$BUILD_UID" --regid="$BUILD_GID" --clear-groups -- \
+   unshare --uts --net --pid --ipc --fork --map-root-user -- \
+   /bin/bash -c '
       # bring loopback up
       if command -v ip 2>&1 >/dev/null; then
          ip link set lo up
       fi
       # set hostname
       if command -v hostname 2>&1 >/dev/null; then
          hostname localhost
       fi
       exec "$@"
    ' -- "$@"
+status=$?
+exit "$status"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
trap 'userdel "$BUILD_USER" 2>/dev/null || true' EXIT
# Make build dir writable by ephemeral user if set
if [ -n "${FROMAGER_BUILD_DIR:-}" ] && [ -d "$FROMAGER_BUILD_DIR" ]; then
chmod -R o+rwX "$FROMAGER_BUILD_DIR" 2>/dev/null || true
fi
# --- Enter namespaces as ephemeral user ---
# setpriv drops to the ephemeral user, then unshare creates namespaces.
# --map-root-user maps the ephemeral user to UID 0 inside the namespace
# so it can run ip/hostname.
BUILD_UID=$(id -u "$BUILD_USER")
BUILD_GID=$(id -g "$BUILD_USER")
exec setpriv --reuid="$BUILD_UID" --regid="$BUILD_GID" --clear-groups -- \
unshare --uts --net --pid --ipc --fork --map-root-user -- \
/bin/bash -c '
# bring loopback up
if command -v ip 2>&1 >/dev/null; then
ip link set lo up
fi
# set hostname
if command -v hostname 2>&1 >/dev/null; then
hostname localhost
fi
exec "$@"
' -- "$@"
trap 'userdel "$BUILD_USER" 2>/dev/null || true' EXIT
# Make build dir writable by ephemeral user if set
if [ -n "${FROMAGER_BUILD_DIR:-}" ] && [ -d "$FROMAGER_BUILD_DIR" ]; then
chmod -R o+rwX "$FROMAGER_BUILD_DIR" 2>/dev/null || true
fi
# --- Enter namespaces as ephemeral user ---
# setpriv drops to the ephemeral user, then unshare creates namespaces.
# --map-root-user maps the ephemeral user to UID 0 inside the namespace
# so it can run ip/hostname.
BUILD_UID=$(id -u "$BUILD_USER")
BUILD_GID=$(id -g "$BUILD_USER")
setpriv --reuid="$BUILD_UID" --regid="$BUILD_GID" --clear-groups -- \
unshare --uts --net --pid --ipc --fork --map-root-user -- \
/bin/bash -c '
# bring loopback up
if command -v ip 2>&1 >/dev/null; then
ip link set lo up
fi
# set hostname
if command -v hostname 2>&1 >/dev/null; then
hostname localhost
fi
exec "$@"
' -- "$@"
status=$?
exit "$status"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/fromager/run_build_isolation.sh` around lines 37 - 64, The EXIT trap that
runs userdel for BUILD_USER never executes because the script uses exec to
replace the shell with setpriv; change the invocation so the script does not
exec (i.e., invoke setpriv/unshare in a child process and wait for it) or
explicitly perform cleanup after the child returns: remove the exec before
setpriv --reuid/--regid/... -- unshare ... and instead spawn the command
(preserving BUILD_USER, BUILD_UID, BUILD_GID usage), capture its exit status,
then run the existing trap/cleanup (userdel "$BUILD_USER" 2>/dev/null || true)
before exiting so ephemeral fmr_* accounts are always removed.

Comment on lines +40 to +42
if [ -n "${FROMAGER_BUILD_DIR:-}" ] && [ -d "$FROMAGER_BUILD_DIR" ]; then
chmod -R o+rwX "$FROMAGER_BUILD_DIR" 2>/dev/null || true
fi
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

chmod -R o+rwX makes the build dir world-accessible, not ephemeral-user-accessible.

o+rwX sets the "other" bits, so every user on the host/container — not just $BUILD_USER — gains read/write. With parallel builds each running under a different fmr_* user, one build's ephemeral user can read and tamper with another's build tree, defeating the isolation this PR advertises. Prefer chown/chgrp to the ephemeral user (or its primary group) instead:

🛠 Suggested fix
-if [ -n "${FROMAGER_BUILD_DIR:-}" ] && [ -d "$FROMAGER_BUILD_DIR" ]; then
-   chmod -R o+rwX "$FROMAGER_BUILD_DIR" 2>/dev/null || true
-fi
+if [ -n "${FROMAGER_BUILD_DIR:-}" ] && [ -d "$FROMAGER_BUILD_DIR" ]; then
+   chown -R "$BUILD_USER":"$BUILD_USER" "$FROMAGER_BUILD_DIR" 2>/dev/null || true
+fi
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if [ -n "${FROMAGER_BUILD_DIR:-}" ] && [ -d "$FROMAGER_BUILD_DIR" ]; then
chmod -R o+rwX "$FROMAGER_BUILD_DIR" 2>/dev/null || true
fi
if [ -n "${FROMAGER_BUILD_DIR:-}" ] && [ -d "$FROMAGER_BUILD_DIR" ]; then
chown -R "$BUILD_USER":"$BUILD_USER" "$FROMAGER_BUILD_DIR" 2>/dev/null || true
fi
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/fromager/run_build_isolation.sh` around lines 40 - 42, The current chmod
command makes the build dir world-accessible; instead change ownership to the
ephemeral build user/group so only that user (or its primary group) can access
it: locate the block that checks FROMAGER_BUILD_DIR and replace the chmod -R
o+rwX "$FROMAGER_BUILD_DIR" call with a chown/chgrp approach that sets ownership
to "$BUILD_USER" (and/or its primary group) and applies permissions for
owner/group (not "other"), e.g. chown -R "$BUILD_USER": "$FROMAGER_BUILD_DIR"
and adjust mode to u+rwX,g+rwX (with safe stderr redirection and || true to
preserve existing error handling). Ensure references to FROMAGER_BUILD_DIR and
BUILD_USER (and any fmr_* ephemeral user naming convention) are used so the
correct directory/user is targeted.

Comment on lines +52 to +53
exec setpriv --reuid="$BUILD_UID" --regid="$BUILD_GID" --clear-groups -- \
unshare --uts --net --pid --ipc --fork --map-root-user -- \
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

unshare --mount --map-root-user unprivileged user namespace requirements

💡 Result:

To run unshare --mount --map-root-user as an unprivileged (non-root) user, the following requirements must be met: Kernel configuration: - CONFIG_USER_NS=y (user namespaces enabled at compile time). - CONFIG_NAMESPACES=y (general namespace support). Runtime kernel settings (check with cat /proc/sys/kernel/unprivileged_userns_clone; some distros like older Debian disable it): - kernel.unprivileged_userns_clone=1 (allows unprivileged creation of user namespaces). Set with sudo sysctl kernel.unprivileged_userns_clone=1. - Sufficient user.max_user_namespaces limit (default usually allows it; check /proc/sys/user/max_user_namespaces). User setup: - Entries in /etc/subuid and /etc/subgid for the user (e.g., username:100000:65536), as --map-root-user uses subordinate IDs to map the caller's UID/GID to root (0:0) inside the namespace. The newuidmap/newgidmap setuid helpers (provided by util-linux or shadow) use these. util-linux version: - Recent enough for --map-root-user (available since ~2.26; works in modern versions). Process state: - Not in a chroot (EPERM if caller's root dir differs from mount namespace root). - Caller must have write access to /proc/self/{uid_map,gid_map,setgroups} post-unshare (handled by unshare). How it works: 1. unshare creates a user namespace (CLONE_NEWUSER, unprivileged since Linux 3.8). 2. --map-root-user writes to uid_map/gid_map (e.g., 0 1) and setgroups=deny, gaining CAP_SYS_ADMIN in the new user namespace. 3. This CAP_SYS_ADMIN (in user ns owning the mount ns) allows unsharing/using mount namespace (CLONE_NEWNS) for mounts like bind mounts, proc, tmpfs (but not block devices). Examples from man pages work unprivileged: unshare --user --map-root-user sh -c whoami # outputs root unshare --map-root-user --mount mount --bind foo bar If it fails with "Operation not permitted": - Check/enable kernel.unprivileged_userns_clone. - Verify subuid/subgid (use id -u and check files). - Ensure kernel >=3.8, CONFIG_USER_NS=y (grep CONFIG_USER_NS /boot/config-$(uname -r)). - No seccomp/container restrictions (e.g., Docker blocks by default). As of 2026, most distros (Ubuntu 22+, Fedora, etc.) enable by default; older/secure setups may need tweaks.

Citations:


🏁 Script executed:

cat -n src/fromager/run_build_isolation.sh | head -60

Repository: python-wheel-build/fromager

Length of output: 2580


🏁 Script executed:

# Search for Issue `#1019` references in the codebase
rg "1019" --type-list | head -20
rg "mount" src/fromager/run_build_isolation.sh

Repository: python-wheel-build/fromager

Length of output: 460


🏁 Script executed:

# Check the full context around lines 52-53 and look for any mount setup
sed -n '40,70p' src/fromager/run_build_isolation.sh

Repository: python-wheel-build/fromager

Length of output: 895


🏁 Script executed:

# Search for /tmp isolation or mount setup elsewhere
rg "/tmp" src/fromager/ -A 2 -B 2

Repository: python-wheel-build/fromager

Length of output: 880


🏁 Script executed:

# Search for references to Issue 1019 or mount-related isolation setup
rg "1019\|mount" src/fromager/ -i

Repository: python-wheel-build/fromager

Length of output: 53


🏁 Script executed:

# Check if there's any mount setup or planned isolation features in the codebase
rg "FROMAGER.*MOUNT\|ISOLATION.*MOUNT" -i

Repository: python-wheel-build/fromager

Length of output: 53


🏁 Script executed:

# Look for any TODOs or FIXMEs related to mount isolation
rg "TODO\|FIXME" src/fromager/run_build_isolation.sh

Repository: python-wheel-build/fromager

Length of output: 53


🏁 Script executed:

# Check the __main__.py file more thoroughly for sandbox-isolation context
rg -A 10 -B 5 "mount.*PID.*IPC" src/fromager/__main__.py

Repository: python-wheel-build/fromager

Length of output: 541


Add --mount to unshare invocation to fulfill promised isolation.

The help text (--build-isolation) explicitly promises "mount, PID, IPC, and network namespace isolation" and "makes system directories read-only, and isolates /tmp," but the unshare command at lines 52–53 omits --mount. Without it:

  • World-readable files in $HOME (.config/*, SSH known_hosts, etc.) remain accessible
  • /tmp is only isolated via sticky bit + ephemeral user, not per-build mount namespace
  • Cannot enforce read-only /usr or tmpfs overlays as promised

Add --mount after --ipc:

unshare --uts --net --pid --ipc --mount --fork --map-root-user -- \

The --map-root-user flag already grants sufficient CAP_SYS_ADMIN privilege for mount operations.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/fromager/run_build_isolation.sh` around lines 52 - 53, The unshare
invocation in run_build_isolation.sh currently omits the mount namespace flag;
update the exec line that calls setpriv/unshare (the command including "setpriv
--reuid=... --regid=... --clear-groups -- \ unshare --uts --net --pid --ipc
--fork --map-root-user -- \") to include --mount (i.e., add --mount after --ipc)
so the script actually creates a mount namespace and enables making system dirs
read-only and isolating /tmp as promised.

Comment on lines +56 to +62
if command -v ip 2>&1 >/dev/null; then
ip link set lo up
fi
# set hostname
if command -v hostname 2>&1 >/dev/null; then
hostname localhost
fi
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Redirection order is reversed.

2>&1 >/dev/null first points stderr at the current stdout (the terminal), then redirects stdout to /dev/null, so stderr still leaks to the caller. For command -v this is usually harmless because it only writes to stdout, but the idiom is a footgun — flip the order:

🛠 Suggested fix
-      if command -v ip 2>&1 >/dev/null; then
+      if command -v ip >/dev/null 2>&1; then
          ip link set lo up
       fi
       # set hostname
-      if command -v hostname 2>&1 >/dev/null; then
+      if command -v hostname >/dev/null 2>&1; then
          hostname localhost
       fi
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if command -v ip 2>&1 >/dev/null; then
ip link set lo up
fi
# set hostname
if command -v hostname 2>&1 >/dev/null; then
hostname localhost
fi
if command -v ip >/dev/null 2>&1; then
ip link set lo up
fi
# set hostname
if command -v hostname >/dev/null 2>&1; then
hostname localhost
fi
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/fromager/run_build_isolation.sh` around lines 56 - 62, The command
existence checks using "command -v ip" and "command -v hostname" use the wrong
redirection order ("2>&1 >/dev/null") which leaves stderr attached; change both
checks to redirect stdout then stderr (e.g., ">/dev/null 2>&1") so both streams
are discarded when checking presence of ip and hostname in the
run_build_isolation.sh script.

Copy link
Copy Markdown
Collaborator

@tiran tiran left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer to start with a design proposal first and come to a mutual agreement how we want to address build isolation.

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.

Add build isolation to sandbox build_sdist and build_wheel steps

2 participants