-
Notifications
You must be signed in to change notification settings - Fork 3
fix: mount procfs in chroot for Java/dotnet runtime support #556
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
The static bind mount of /proc/self always resolved to the parent shell's executable, causing .NET CLR to fail with "Cannot execute dotnet when renamed to bash" and preventing proper /proc/cpuinfo access needed by the JVM. Replace the static /proc/self bind mount with a fresh container-scoped procfs mounted at /host/proc via 'mount -t proc'. This provides dynamic /proc/self/exe resolution per-process while only exposing container processes (not host). Changes: - Mount procfs in entrypoint.sh before chroot (requires SYS_ADMIN capability) - Add SYS_ADMIN to container cap_add, dropped via capsh before user code - Add apparmor:unconfined in chroot mode (Docker's default blocks mount) - Remove mount/umount from seccomp blocklist (safe: SYS_ADMIN dropped) - Add DOTNET_ROOT passthrough for .NET runtime path resolution Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
📰 VERDICT: Smoke Copilot has concluded. All systems operational. This is a developing story. 🎤 |
|
Chroot tests passed! Smoke Chroot - All security and functionality tests succeeded. |
|
🎬 THE END — Smoke Claude MISSION: ACCOMPLISHED! The hero saves the day! ✨ |
✅ Coverage Check PassedOverall Coverage
📁 Per-file Coverage Changes (1 files)
Coverage comparison generated by |
Deno Build Test Results
Overall: ✅ PASS All Deno tests completed successfully.
|
C++ Build Test Results
Overall: PASS ✅ All C++ projects built successfully.
|
Node.js Build Test Results
Overall: PASS ✅ All Node.js projects successfully installed and passed their test suites.
|
Bun Build Test Results
Overall: PASS ✅ All Bun projects installed and tested successfully.
|
|
Smoke Test: Claude Engine - PASS ✅
Status: PASS
|
Security Review:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR improves chroot-mode runtime compatibility by providing a container-scoped /proc inside the chroot (fixing dynamic /proc/self/exe and JVM/.NET runtime introspection needs) and by passing through DOTNET_ROOT for correct .NET discovery.
Changes:
- Replace the old
/proc/selfbind-mount approach with a procfs mount performed inentrypoint.sh(targeting/host/proc). - Add
DOTNET_ROOTpassthrough (AWF_DOTNET_ROOT) and export it into the chroot execution environment. - Adjust container security configuration for chroot mode (capabilities, seccomp, AppArmor) and update unit tests accordingly.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| src/docker-manager.ts | Adds DOTNET_ROOT passthrough; removes /proc/self bind mount; adds SYS_ADMIN and apparmor:unconfined in chroot mode. |
| src/docker-manager.test.ts | Updates expectations for proc mounts/capabilities and adds coverage for AppArmor + DOTNET_ROOT passthrough. |
| containers/agent/seccomp-profile.json | Allows mount/umount* syscalls (previously blocked) to enable procfs mounting. |
| containers/agent/entrypoint.sh | Mounts procfs at /host/proc in chroot mode; drops CAP_SYS_ADMIN; exports DOTNET_ROOT/PATH into the chroot script. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| "names": [ | ||
| "kexec_load", | ||
| "kexec_file_load", | ||
| "reboot", | ||
| "init_module", | ||
| "finit_module", | ||
| "delete_module", | ||
| "acct", | ||
| "swapon", | ||
| "swapoff", | ||
| "mount", | ||
| "umount", | ||
| "umount2", | ||
| "pivot_root", | ||
| "syslog", | ||
| "add_key", |
Copilot
AI
Feb 6, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change unblocks mount, umount, and umount2 for all container processes. The procfs mount requires mount, but umount/umount2 don’t appear to be needed by entrypoint.sh; consider keeping umount/umount2 blocked to minimize syscall surface while still allowing the procfs mount.
| // Apply seccomp profile and no-new-privileges to restrict dangerous syscalls and prevent privilege escalation | ||
| // In chroot mode, AppArmor is set to unconfined to allow mounting procfs at /host/proc | ||
| // (Docker's default AppArmor profile blocks mount). This is safe because SYS_ADMIN is | ||
| // dropped via capsh before user code runs, so user code cannot mount anything. | ||
| security_opt: [ | ||
| 'no-new-privileges:true', | ||
| `seccomp=${config.workDir}/seccomp-profile.json`, | ||
| ...(config.enableChroot ? ['apparmor:unconfined'] : []), |
Copilot
AI
Feb 6, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In chroot mode this sets apparmor:unconfined, which disables AppArmor confinement for the entire container (including the privileged pre-drop phase). Since the goal is only to allow mount -t proc, consider using a dedicated/limited AppArmor profile (checked into the repo) that permits the procfs mount but keeps other AppArmor restrictions, rather than fully unconfined.
| // Apply seccomp profile and no-new-privileges to restrict dangerous syscalls and prevent privilege escalation | |
| // In chroot mode, AppArmor is set to unconfined to allow mounting procfs at /host/proc | |
| // (Docker's default AppArmor profile blocks mount). This is safe because SYS_ADMIN is | |
| // dropped via capsh before user code runs, so user code cannot mount anything. | |
| security_opt: [ | |
| 'no-new-privileges:true', | |
| `seccomp=${config.workDir}/seccomp-profile.json`, | |
| ...(config.enableChroot ? ['apparmor:unconfined'] : []), | |
| // Apply seccomp profile and no-new-privileges to restrict dangerous syscalls and prevent privilege escalation. | |
| // In chroot mode, use a dedicated AppArmor profile that permits mounting procfs at /host/proc | |
| // while keeping other AppArmor restrictions in place (Docker's default profile blocks mount). | |
| security_opt: [ | |
| 'no-new-privileges:true', | |
| `seccomp=${config.workDir}/seccomp-profile.json`, | |
| ...(config.enableChroot ? ['apparmor:awf-procfs-mount'] : []), |
containers/agent/entrypoint.sh
Outdated
| if mount -t proc proc /host/proc; then | ||
| echo "[entrypoint] Mounted procfs at /host/proc" | ||
| else | ||
| echo "[entrypoint][WARN] Failed to mount procfs at /host/proc - some runtimes may not work" |
Copilot
AI
Feb 6, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The procfs mount is attempted without any hardening options and failure only logs a warning then continues. Since chroot-mode runtimes depend on this, consider (1) mounting with restrictive options (e.g., nosuid,nodev,noexec and hidepid=2 to reduce process info exposure for the unprivileged user) and (2) failing fast with an actionable error when the mount fails so runtime errors don’t show up later as confusing JVM/.NET failures.
| if mount -t proc proc /host/proc; then | |
| echo "[entrypoint] Mounted procfs at /host/proc" | |
| else | |
| echo "[entrypoint][WARN] Failed to mount procfs at /host/proc - some runtimes may not work" | |
| # Hardened procfs mount: prefer hidepid=2, fall back to nosuid,nodev,noexec if hidepid unsupported | |
| if mount -t proc -o nosuid,nodev,noexec,hidepid=2 proc /host/proc 2>/dev/null; then | |
| echo "[entrypoint] Mounted hardened procfs at /host/proc (nosuid,nodev,noexec,hidepid=2)" | |
| elif mount -t proc -o nosuid,nodev,noexec proc /host/proc 2>/dev/null; then | |
| echo "[entrypoint][WARN] Mounted procfs at /host/proc without hidepid=2 (kernel may not support hidepid)" | |
| echo "[entrypoint][WARN] Proc entries remain visible to awfuser within the container namespace" | |
| else | |
| echo "[entrypoint][ERROR] Failed to mount procfs at /host/proc" | |
| echo "[entrypoint][ERROR] Chroot mode requires a working /host/proc for runtimes that use /proc/self/exe" | |
| echo "[entrypoint][ERROR] Ensure the container has SYS_ADMIN capability and the kernel supports procfs" | |
| exit 1 |
Smoke Test Results (Copilot)Last 2 Merged PRs:
Tests:
Status: PASS cc @Mossaka
|
Go Build Test Results
Overall: PASS ✅ All Go projects successfully downloaded dependencies and passed tests.
|
🔴 Build Test: Rust - FAILEDError: Rust toolchain not installed The test environment does not have Required: Add Rust toolchain setup to the workflow before running this test: - uses: actions-rust-lang/setup-rust-toolchain@v1
Overall: FAIL - Missing Rust toolchain
|
Java Build Test Results
Overall: FAIL Error DetailsBoth projects failed to compile due to network connectivity issues. Maven was unable to download required plugins from Maven Central repository ( Error: Root Cause: The domain Required Action: Add
|
Chroot Runtime Version Test Results
Overall Status: ❌ Failed (2/3 tests failed) The chroot mode is not accessing the same runtime versions as the host. Python and Node.js versions differ between host and chroot environments.
|
- Mount procfs with nosuid,nodev,noexec options for defense-in-depth - Fail fast (exit 1) on mount failure instead of warning, since Java/.NET runtimes will fail with confusing errors without /proc/self/exe - Add umount and umount2 back to seccomp blocked list since only mount (not unmount) is needed for the procfs setup Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
🎬 THE END — Smoke Claude MISSION: ACCOMPLISHED! The hero saves the day! ✨ |
|
Chroot tests passed! Smoke Chroot - All security and functionality tests succeeded. |
|
📰 VERDICT: Smoke Copilot has concluded. All systems operational. This is a developing story. 🎤 |
Build Test: Java - FAILED ❌Both projects failed to compile due to network access restrictions.
Overall: FAIL Error DetailsBoth projects failed with the same error when Maven attempted to download dependencies: Root Cause: The domain Required Action: Add
|
Node.js Build Test Results
Overall: PASS ✅ All Node.js projects installed successfully and passed their test suites.
|
Smoke Test Results (Copilot)Last 2 Merged PRs:
Test Results:
Overall: PASS ✅ cc @Mossaka (author)
|
Bun Build Test Results
Overall: PASS ✅ All Bun projects installed and tested successfully.
|
C++ Build Test Results
Overall: PASS ✅ All C++ projects built successfully.
|
Rust Build Test Results ✅
Overall: PASS All Rust projects built and tested successfully.
|
Deno Build Test Results
Overall: ✅ PASS All Deno tests completed successfully.
|
Go Build Test Results
Overall: PASS ✅ All Go projects successfully downloaded dependencies and passed tests.
|
Claude Smoke Test ResultsRecent PRs:
Test Results:
Status: FAIL (Playwright timeout)
|
Chroot Version Comparison Test Results
Overall Result: ❌ Tests did not pass (not all versions matched) The chroot environment successfully accessed host binaries but version mismatches were detected for Python and Node.js. This indicates the chroot is using different runtime versions than what's available on the host.
|
Security Review - PR #556I've conducted a thorough security review of this PR's changes to support Java/.NET runtimes in chroot mode. Here are my findings:
|
| Security Layer | Status | Notes |
|---|---|---|
| Capability Dropping | ✅ Secure | SYS_ADMIN dropped before user code |
| Seccomp Profile | ✅ Secure | Umount blocked, mount allowed only for entrypoint |
| No New Privileges | ✅ Secure | Prevents privilege escalation via execve |
| Non-root Execution | ✅ Secure | User code runs as awfuser |
| AppArmor | Disabled in chroot mode (apparmor:unconfined) | |
| Procfs Isolation | ✅ Secure | Container-scoped with hardened mount options |
🎯 Recommendation
The PR maintains functional security BUT weakens defense-in-depth by removing AppArmor. I recommend implementing Option 1 (Custom AppArmor Profile) before merging to maintain all security layers while enabling the required functionality.
If AppArmor remains disabled, ensure this is:
- ✅ Documented in security audit logs
- ✅ Tested with security regression tests
- ✅ Reviewed by security team
The other changes (procfs mounting, capability management, seccomp profile updates) are implemented securely and follow security best practices.
References
- [AppArmor security profiles for Docker | Docker Docs]((docs.docker.com/redacted)
- [Container security fundamentals part 5: AppArmor and SELinux | Datadog Security Labs]((securitylabs.datadoghq.com/redacted)
AI generated by Security Guard
Summary
/proc/selfbind mount always resolving to parent shell's exe/proc/cpuinfoand/proc/meminfofor CPU feature detection and heap sizing/host/procviamount -t procinstead of static bind mount, providing dynamic/proc/self/exeresolutionDOTNET_ROOTenv var passthrough for .NET runtime path resolution in chrootRoot Cause
The previous approach bind-mounted
/proc/selffrom the host to/host/proc/self. This creates a static mount — every process reading/proc/self/exeinside the chroot sees/bin/bash(the parent shell) instead of their own executable. The .NET CLR reads/proc/self/exeto verify its binary name and refuses to run when it sees "bash".Security Model
The fix adds
SYS_ADMINcapability to the container (needed formount -t proc) with multiple defense layers:capsh --drop=cap_sys_admin— capability dropped before user code runscapsh --userno-new-privileges:true— prevents privilege escalation via execveapparmor:unconfinedadded only in chroot mode (required because Docker's default AppArmor blocksmount)Verified: mount attempts fail after privilege drop (
mount exit=32).Local Verification
Before fix:
After fix:
Test plan
--versionworks in chroot mode--versionworks in chroot mode🤖 Generated with Claude Code