feat: add namespace isolation and minimal root filesystem to bwrap sandboxes #5

Open
Nanobot wants to merge 4 commits from Nanobot/chat-app:feat/bwrap-isolation-v2 into master
First-time contributor

Replace --ro-bind / / (entire host filesystem) with minimal root:
only explicitly bound paths are accessible.

Namespace isolation (no user namespace — breaks Bun module resolution):

  • --unshare-pid: isolated PID namespace (can't see host processes)
  • --unshare-net: isolated network (auth server only, backend needs HTTP)
  • --unshare-ipc: isolated IPC/shared memory
  • --unshare-uts: isolated hostname

Minimal filesystem binds:

  • /usr, /etc, /lib, /lib64, /usr/local — system runtime (read-only)
  • BUN_DIR — Bun installation (auto-detected from BUN_PATH, read-only)
  • APP_DIR — application code (read-only)
  • /var/lib/chatapp-auth — auth data (read-write, auth only)
  • /run/chatapp — Unix socket (RW auth, RO backend)
  • /var/log/chat-app — logs (read-write, backend only)
  • --proc /proc — fresh procfs (shows only sandbox PIDs)
  • --dev /dev — fresh devtmpfs
  • --tmpfs /tmp — fresh writable /tmp

Verified:

  • --proc /proc works with bubblewrap 0.11.1 on Ubuntu 24.04
  • --unshare-net blocks outbound connections
  • Unix socket communication works across namespace boundary
  • Backend can serve HTTP from inside sandbox
  • Bun runs fine without user namespace (no UID remapping)

Updated AGENTS.md with PR workflow (fork-based PRs)

Replace --ro-bind / / (entire host filesystem) with minimal root: only explicitly bound paths are accessible. Namespace isolation (no user namespace — breaks Bun module resolution): - --unshare-pid: isolated PID namespace (can't see host processes) - --unshare-net: isolated network (auth server only, backend needs HTTP) - --unshare-ipc: isolated IPC/shared memory - --unshare-uts: isolated hostname Minimal filesystem binds: - /usr, /etc, /lib, /lib64, /usr/local — system runtime (read-only) - BUN_DIR — Bun installation (auto-detected from BUN_PATH, read-only) - APP_DIR — application code (read-only) - /var/lib/chatapp-auth — auth data (read-write, auth only) - /run/chatapp — Unix socket (RW auth, RO backend) - /var/log/chat-app — logs (read-write, backend only) - --proc /proc — fresh procfs (shows only sandbox PIDs) - --dev /dev — fresh devtmpfs - --tmpfs /tmp — fresh writable /tmp Verified: - --proc /proc works with bubblewrap 0.11.1 on Ubuntu 24.04 - --unshare-net blocks outbound connections - Unix socket communication works across namespace boundary - Backend can serve HTTP from inside sandbox - Bun runs fine without user namespace (no UID remapping) Updated AGENTS.md with PR workflow (fork-based PRs)
Replace --ro-bind / / (entire host filesystem) with minimal root:
only explicitly bound paths are accessible.

Namespace isolation (no user namespace — breaks Bun module resolution):
- --unshare-pid: isolated PID namespace (can't see host processes)
- --unshare-net: isolated network (auth server only, backend needs HTTP)
- --unshare-ipc: isolated IPC/shared memory
- --unshare-uts: isolated hostname

Minimal filesystem binds:
- /usr, /etc, /lib, /lib64, /usr/local — system runtime (read-only)
- BUN_DIR — Bun installation (auto-detected from BUN_PATH, read-only)
- APP_DIR — application code (read-only)
- /var/lib/chatapp-auth — auth data (read-write, auth only)
- /run/chatapp — Unix socket (RW auth, RO backend)
- /var/log/chat-app — logs (read-write, backend only)
- --proc /proc — fresh procfs (shows only sandbox PIDs)
- --dev /dev — fresh devtmpfs
- --tmpfs /tmp — fresh writable /tmp

Verified:
- --proc /proc works with bubblewrap 0.11.1 on Ubuntu 24.04
- --unshare-net blocks outbound connections
- Unix socket communication works across namespace boundary
- Backend can serve HTTP from inside sandbox
- Bun runs fine without user namespace (no UID remapping)

Updated AGENTS.md with PR workflow (AGit: git push origin HEAD:refs/for/master/<topic>)
Owner

Broken: log line

log "Starting chat-app (Bun ${BUN} --version) with bubblewrap isolation..."

Prints the literal string /usr/local/bin/bun --version. Should be $(${BUN} --version).

Broken: SESSION_SECRET_PATH on the backend

The backend gets --setenv SESSION_SECRET_PATH "${AUTH_DIR}/session-secret" but has no bind mount for AUTH_DIR. That path doesn't exist in the backend's filesystem view. The backend should read the session secret via the auth socket, delete this env var.

wait doesn't handle crashes

If either process dies, wait just waits for the remaining one forever. The container stays "up" with half the app dead. wait -n exits as soon as either child dies, letting the container runtime restart cleanly.

**Broken: log line** ```bash log "Starting chat-app (Bun ${BUN} --version) with bubblewrap isolation..." ``` Prints the literal string `/usr/local/bin/bun --version`. Should be `$(${BUN} --version)`. **Broken: `SESSION_SECRET_PATH` on the backend** The backend gets `--setenv SESSION_SECRET_PATH "${AUTH_DIR}/session-secret"` but has no bind mount for `AUTH_DIR`. That path doesn't exist in the backend's filesystem view. The backend should read the session secret via the auth socket, delete this env var. **`wait` doesn't handle crashes** If either process dies, `wait` just waits for the remaining one forever. The container stays "up" with half the app dead. `wait -n` exits as soon as either child dies, letting the container runtime restart cleanly.
- Fix log line: use $($BUN --version) instead of literal string
- Remove SESSION_SECRET_PATH env var from backend (path not mounted)
- Use wait -n to detect when either process crashes
Author
First-time contributor

Fixed all three:

  1. Log line: changed to $($BUN --version) — now prints actual version
  2. Removed SESSION_SECRET_PATH from backend sandbox — path wasn't mounted
  3. Both wait calls → wait -n — exits when either process dies
Fixed all three: 1. Log line: changed to `$($BUN --version)` — now prints actual version 2. Removed `SESSION_SECRET_PATH` from backend sandbox — path wasn't mounted 3. Both `wait` calls → `wait -n` — exits when either process dies
This pull request can be merged automatically.
You are not authorized to merge this pull request.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u feat/bwrap-isolation-v2:Nanobot-feat/bwrap-isolation-v2
git switch Nanobot-feat/bwrap-isolation-v2

Merge

Merge the changes and update on Forgejo.

Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.

git switch master
git merge --no-ff Nanobot-feat/bwrap-isolation-v2
git switch Nanobot-feat/bwrap-isolation-v2
git rebase master
git switch master
git merge --ff-only Nanobot-feat/bwrap-isolation-v2
git switch Nanobot-feat/bwrap-isolation-v2
git rebase master
git switch master
git merge --no-ff Nanobot-feat/bwrap-isolation-v2
git switch master
git merge --squash Nanobot-feat/bwrap-isolation-v2
git switch master
git merge --ff-only Nanobot-feat/bwrap-isolation-v2
git switch master
git merge Nanobot-feat/bwrap-isolation-v2
git push origin master
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
trainraider/chat-app!5
No description provided.