fix(docker): prevent privilege regain after bootstrap

Co-authored-by: yu-xin-c <175149126+yu-xin-c@users.noreply.github.com>
This commit is contained in:
Xubin Ren
2026-08-13 02:13:51 +09:00
co-authored by yu-xin-c
parent 3741ecda0b
commit b571d3b9ff
3 changed files with 56 additions and 8 deletions
+39 -2
View File
@@ -189,10 +189,47 @@ jobs:
- name: Build image with default channel dependencies
run: docker build -t nanobot:test .
- name: Verify Docker Compose startup
- name: Verify Docker Compose startup and privilege boundary
env:
HOME: ${{ runner.temp }}
run: docker compose run --rm --no-deps --build -T nanobot-cli status
run: |
docker compose run --rm --no-deps --build -T nanobot-cli status
docker compose run --rm --no-deps -T --entrypoint sh nanobot-cli -s <<'OUTER'
set -eu
field() {
awk -v key="$1:" '$1 == key { print $2 }' /proc/self/status
}
test "$(id -u)" = "0"
test "$(field NoNewPrivs)" = "1"
setpriv --reuid=nanobot --regid=nanobot --init-groups sh -s <<'INNER'
set -eu
field() {
awk -v key="$1:" '$1 == key { print $2 }' /proc/self/status
}
test "$(id -u)" = "1000"
test "$(field NoNewPrivs)" = "1"
for capability_set in CapInh CapPrm CapEff CapAmb; do
test "$(field "$capability_set")" = "0000000000000000"
done
INNER
OUTER
docker compose -f docker-compose.yml -f docker-compose.bwrap.yml \
run --rm --no-deps -T --entrypoint sh nanobot-cli -s <<'BWRAP'
set -eu
mkdir -p /home/nanobot/.nanobot/workspace
chown -R nanobot:nanobot /home/nanobot/.nanobot
setpriv --reuid=nanobot --regid=nanobot --init-groups \
bwrap --new-session --die-with-parent \
--ro-bind /usr /usr \
--ro-bind-try /bin /bin \
--ro-bind-try /lib /lib \
--ro-bind-try /lib64 /lib64 \
--proc /proc --dev /dev --tmpfs /tmp \
--bind /home/nanobot/.nanobot/workspace /home/nanobot/.nanobot/workspace \
--chdir /home/nanobot/.nanobot/workspace \
-- sh -c 'test "$(id -u)" = 1000; touch sandbox-ok'
test -f /home/nanobot/.nanobot/workspace/sandbox-ok
BWRAP
- name: Verify default WhatsApp dependencies
run: docker run --rm --entrypoint python nanobot:test -c "import neonize, segno"
+4
View File
@@ -13,6 +13,10 @@ x-common-config: &common-config
- CHOWN
- SETGID
- SETUID
# Prevent the non-root process from regaining capabilities through setuid
# binaries or file capabilities left inside the container image.
security_opt:
- no-new-privileges:true
services:
nanobot-gateway:
+13 -6
View File
@@ -160,8 +160,11 @@ docker compose logs -f nanobot-gateway # view logs
docker compose down # stop
```
The default Compose file drops all Linux capabilities and keeps Docker's default
AppArmor/seccomp profiles enabled. If you explicitly set
The default Compose file drops all Linux capabilities except `CHOWN`, `SETUID`, and
`SETGID`, which the root entrypoint needs to fix bind-mount ownership and become UID
1000. It also enables `no-new-privileges`, so the non-root process cannot regain those
bootstrap capabilities through setuid binaries or file capabilities. Docker's default
AppArmor/seccomp profiles remain enabled. If you explicitly set
`"tools.exec.sandbox": "bwrap"` in `~/.nanobot/config.json`, add the bwrap
override file when starting containers:
@@ -170,9 +173,9 @@ docker compose -f docker-compose.yml -f docker-compose.bwrap.yml up -d nanobot-g
docker compose -f docker-compose.yml -f docker-compose.bwrap.yml run --rm nanobot-cli agent -m "Hello!"
```
The override grants `CAP_SYS_ADMIN` and disables AppArmor/seccomp confinement for
the container so bubblewrap can create its nested namespaces. Use it only when the
bwrap sandbox is enabled.
The override adds `CAP_SYS_ADMIN` and disables AppArmor/seccomp confinement for the
container so bubblewrap can create its nested namespaces. It preserves
`no-new-privileges`. Use it only when the bwrap sandbox is enabled.
### Docker
@@ -197,6 +200,8 @@ vim ~/.nanobot/config.json
# health endpoint on 18790.
docker run \
--cap-drop ALL \
--cap-add CHOWN --cap-add SETGID --cap-add SETUID \
--security-opt no-new-privileges:true \
-v ~/.nanobot:/home/nanobot/.nanobot \
-p 18790:18790 -p 8765:8765 \
nanobot gateway
@@ -205,7 +210,9 @@ docker run \
# bubblewrap needs for nested namespaces. Without them, `bwrap` may exit with
# `clone3: Operation not permitted`.
docker run \
--cap-drop ALL --cap-add SYS_ADMIN \
--cap-drop ALL \
--cap-add CHOWN --cap-add SETGID --cap-add SETUID --cap-add SYS_ADMIN \
--security-opt no-new-privileges:true \
--security-opt apparmor=unconfined \
--security-opt seccomp=unconfined \
-v ~/.nanobot:/home/nanobot/.nanobot \