From 498070d0363fd0492ec3d84fdb0c582752dbfeb2 Mon Sep 17 00:00:00 2001 From: Xubin Ren <52506698+Re-bin@users.noreply.github.com> Date: Wed, 12 Aug 2026 22:27:56 +0900 Subject: [PATCH] test(docker): separate host bwrap requirements Co-authored-by: yu-xin-c <175149126+yu-xin-c@users.noreply.github.com> --- .github/workflows/ci.yml | 36 +++++++++++++----------------------- docs/configuration.md | 2 +- docs/deployment.md | 4 +++- 3 files changed, 17 insertions(+), 25 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3b294950a..833f5badc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -213,29 +213,19 @@ jobs: done INNER OUTER - docker compose -f docker-compose.yml -f docker-compose.bwrap.yml \ - run --rm --no-deps -T --user nanobot --entrypoint sh nanobot-cli -s <<'BWRAP' - 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 - mkdir -p /home/nanobot/.nanobot/workspace - 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 + docker compose -f docker-compose.yml -f docker-compose.bwrap.yml --profile cli \ + config --format json > "${RUNNER_TEMP}/bwrap-compose.json" + python - <<'PY' + import json + import os + from pathlib import Path + + config = json.loads(Path(os.environ["RUNNER_TEMP"], "bwrap-compose.json").read_text()) + for service_name in ("nanobot-gateway", "nanobot-api", "nanobot-cli"): + service = config["services"][service_name] + assert {"CHOWN", "SETGID", "SETUID", "SYS_ADMIN"} <= set(service["cap_add"]) + assert "no-new-privileges:true" in service["security_opt"] + PY - name: Verify default WhatsApp dependencies run: docker run --rm --entrypoint python nanobot:test -c "import neonize, segno" diff --git a/docs/configuration.md b/docs/configuration.md index f3d28bcb3..75b9cc451 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -2095,7 +2095,7 @@ For API keys, tokens, and other secrets, see [Environment Variables for Secrets] | `tools.ssrfWhitelist` | `[]` | CIDR ranges exempted from the shared SSRF guard used by web fetches and HTTP/SSE MCP connections. Prefer exact host CIDRs such as `192.168.1.50/32`; broad ranges increase SSRF exposure. | | `channels.*.allowFrom` | omitted | Access control per channel. Omit to use pairing-only mode; set `["*"]` to allow everyone; or list specific user IDs. See [Pairing](#pairing) for details. | -**Docker security**: The official Docker image runs as a non-root user (`nanobot`, UID 1000) with bubblewrap pre-installed. The default `docker-compose.yml` drops all Linux capabilities and keeps Docker's default AppArmor/seccomp profiles enabled. If you enable `"tools.exec.sandbox": "bwrap"` inside Docker, start Compose with `docker-compose.bwrap.yml` as an additional override so bubblewrap can create nested namespaces. +**Docker security**: The official Docker image runs as a non-root user (`nanobot`, UID 1000) with bubblewrap pre-installed. The default `docker-compose.yml` drops all Linux capabilities except the `CHOWN`, `SETGID`, and `SETUID` capabilities required by the root entrypoint to initialize bind-mount ownership and become UID 1000. It enables `no-new-privileges` so the final non-root process cannot regain those bootstrap capabilities, and keeps Docker's default AppArmor/seccomp profiles enabled. If you enable `"tools.exec.sandbox": "bwrap"` inside Docker, start Compose with `docker-compose.bwrap.yml` as an additional override so bubblewrap can create nested namespaces. The host must also allow unprivileged user namespaces; the override cannot bypass a host-level namespace restriction. ## Pairing diff --git a/docs/deployment.md b/docs/deployment.md index a5a1b56ad..529e928a1 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -175,7 +175,9 @@ docker compose -f docker-compose.yml -f docker-compose.bwrap.yml run --rm nanobo 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. +`no-new-privileges`. The host must also allow unprivileged user namespaces; the +override cannot bypass a host-level namespace restriction. Use it only when the +bwrap sandbox is enabled. ### Docker