From 007bc1177477a5968e436f9c54e3fc986ceb3e3a Mon Sep 17 00:00:00 2001 From: Brett Kinny Date: Tue, 8 Sep 2026 21:12:48 +1000 Subject: [PATCH] Bound Box PID count with --pids-limit=4096 A runaway Box process could previously fork-bomb the host by exhausting its process table. All lifecycle adapters (Bash, PowerShell, Compose) and the rootless Podman e2e gate now pass --pids-limit=4096. The existing capability set was audited empirically against rootless Podman (keep-id): every one of CHOWN, DAC_OVERRIDE, FOWNER, SETUID, SETGID, and KILL is load-bearing for the passwordless-sudo APT provisioning contract, so no capability reduction is possible without redesigning that operational control. Verified locally with rootless Podman: provisioning (tmux via setup.sh --reconcile-box), workspace ownership parity, Managed-home persistence across stop/start, and pids.max=4096 enforcement. --- .github/workflows/e2e.yml | 1 + CHANGELOG.md | 8 ++++++++ SECURITY.md | 7 ++++--- docker-compose.yml | 3 +++ install.ps1 | 2 +- install.sh | 3 ++- tests/test-e2e-evidence.sh | 1 + tests/test-lifecycle-install-state.sh | 1 + tests/test-lifecycle-powershell.ps1 | 1 + 9 files changed, 22 insertions(+), 5 deletions(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 1af1321..74aa83a 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -527,6 +527,7 @@ jobs: --security-opt label=disable \ --cap-drop=ALL --cap-add=CHOWN --cap-add=DAC_OVERRIDE \ --cap-add=FOWNER --cap-add=SETUID --cap-add=SETGID --cap-add=KILL \ + --pids-limit=4096 \ -e PUID="$(id -u)" -e PGID="$(id -g)" \ -v "$workspace:/workspace" -v "$home_volume:/home/dev" \ "$IMAGE@$DIGEST" sleep infinity diff --git a/CHANGELOG.md b/CHANGELOG.md index 98d3b7b..93fe1e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## Unreleased + +### Changed + +- Lifecycle adapters and Compose bound the Box to 4096 PIDs + (`--pids-limit=4096` / `pids_limit`), so a runaway Box process cannot + exhaust the host process table. + ## v1.2.1 — 2026-07-31 v1.2.0 was built as a draft Candidate but was never published after final diff --git a/SECURITY.md b/SECURITY.md index 5548913..910b23e 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -226,9 +226,10 @@ does not contain that server. The upstream Feature enables root login in its SSHD configuration, but Squarebox publishes no SSH host port or password, and restricts remote-forwarded listeners to loopback. -Linux capabilities are reduced, but the Box has network access and the host -resources explicitly mounted by its Install identity. Treat code and tools run -inside it as having access to: +Linux capabilities are reduced, and the Box is bounded to 4096 PIDs so a +runaway process cannot exhaust the host's process table. The Box has network +access and the host resources explicitly mounted by its Install identity. +Treat code and tools run inside it as having access to: - the Workspace, read-write; - the Managed home, including persisted tool credentials; diff --git a/docker-compose.yml b/docker-compose.yml index 5857b64..92c56c1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -60,6 +60,9 @@ services: - SETGID - KILL + # Fork-bomb insurance: a runaway Box process cannot exhaust host PIDs. + pids_limit: 4096 + volumes: squarebox-home: # An explicit name keeps lifecycle commands and Compose on the same diff --git a/install.ps1 b/install.ps1 index 4c59b67..66e8db1 100644 --- a/install.ps1 +++ b/install.ps1 @@ -777,7 +777,7 @@ Add-Seed 'multiplexer' $env:SQUAREBOX_MULTIPLEXERS 'multiplexers' $RuntimeOptions = @( '--label', "$ManagedLabel=true", '--label', "$IdentityLabel=$InstallId", '--cap-drop=ALL', '--cap-add=CHOWN', '--cap-add=DAC_OVERRIDE', '--cap-add=FOWNER', - '--cap-add=SETUID', '--cap-add=SETGID', '--cap-add=KILL', + '--cap-add=SETUID', '--cap-add=SETGID', '--cap-add=KILL', '--pids-limit=4096', '-e', "PUID=$Puid", '-e', "PGID=$Pgid" ) $BindSuffix = ''; $ReadOnlyBindSuffix = ':ro' diff --git a/install.sh b/install.sh index 581fbc0..4d63f4c 100755 --- a/install.sh +++ b/install.sh @@ -850,7 +850,8 @@ seed multiplexer "${SQUAREBOX_MULTIPLEXERS:-}" multiplexers bind_mode=""; ro_bind_mode=ro RT_OPTS=(--label "$MANAGED_LABEL=true" --label "$IDENTITY_LABEL=$INSTALL_ID" --cap-drop=ALL --cap-add=CHOWN --cap-add=DAC_OVERRIDE --cap-add=FOWNER - --cap-add=SETUID --cap-add=SETGID --cap-add=KILL -e "PUID=$PUID" -e "PGID=$PGID") + --cap-add=SETUID --cap-add=SETGID --cap-add=KILL --pids-limit=4096 + -e "PUID=$PUID" -e "PGID=$PGID") if [ "$RUNTIME" = podman ]; then # This development Box mounts a host Workspace, managed configuration, # system time, and optionally SSH material. A private :Z relabel would make diff --git a/tests/test-e2e-evidence.sh b/tests/test-e2e-evidence.sh index ab15552..6f4cc31 100755 --- a/tests/test-e2e-evidence.sh +++ b/tests/test-e2e-evidence.sh @@ -121,6 +121,7 @@ grep -Fq '~/.squarebox-compose-e2e' "$WORKFLOW" grep -Fq '$SQUAREBOX_WORKSPACE/from-compose' "$WORKFLOW" grep -Fq -- '--cap-drop=ALL --cap-add=CHOWN --cap-add=DAC_OVERRIDE' "$WORKFLOW" grep -Fq -- '--cap-add=FOWNER --cap-add=SETUID --cap-add=SETGID --cap-add=KILL' "$WORKFLOW" +grep -Fq -- '--pids-limit=4096' "$WORKFLOW" grep -Fq -- '--userns=keep-id:uid=1000,gid=1000' "$WORKFLOW" grep -Fq -- '--security-opt label=disable' "$WORKFLOW" grep -Fq 'podman exec -u dev -e HOME=/home/dev' "$WORKFLOW" diff --git a/tests/test-lifecycle-install-state.sh b/tests/test-lifecycle-install-state.sh index bf7ff98..b6d7dc6 100755 --- a/tests/test-lifecycle-install-state.sh +++ b/tests/test-lifecycle-install-state.sh @@ -651,6 +651,7 @@ export SQUAREBOX_DIR="$TMP/podman" SQUAREBOX_RUNTIME=podman SQUAREBOX_TAG=v1.1.0 "$ROOT/install.sh"