registry: run the documentation worker's renders in bubblewrap (step 1 of #233) #497

Closed
opened 2026-10-26 09:57:19 +00:00 by orion · 6 comments
Collaborator

Step 1 of the epic #233 (isolating the documentation worker's runs). Related: #17, #234, #236.

Goal. Every torb run of the documentation worker (sandboxed in tools/registry/src/documentation.trb, the single place that starts one, including the --version probe of DocumentationWorker.started) runs inside bubblewrap, so a render cannot see or touch the service.

Design (decided by the loop, see #233).

  • sandboxed puts bwrap between prlimit and env -i (the limits stay): --unshare-all (user, pid, ipc, uts, net, cgroup), --die-with-parent, --new-session, --uid 65534 --gid 65534, --ro-bind of /usr, /bin, /lib* (symlinks recreated) and of the toolchain directory (the directory of documentationToolchain), --proc /proc, --dev /dev, --tmpfs /tmp, --bind <work> <work> (the only writable path, the scratch the service hands over and reads the site back from), --chdir <work>, --clearenv. No /srv/torb path is mounted and nothing else of the service's file system.
  • New setting REGISTRY_DOC_ISOLATION: bwrap (default) or none. The worker refuses to start (the existing "started" error path, so the operator sees it) when bwrap is selected and the --version probe fails inside it; none is for the stand-in toolchain of the tests and for development, and the README says it must not be used in production.
  • The worker also refuses a scratch directory (REGISTRY_DOC_SCRATCH) that lies inside the storage or database path.
  • tools/deploy/Dockerfile.registry installs bubblewrap in the runtime stage. Whether the container may create user namespaces (Docker's default seccomp profile forbids it) is step 2, the deployment (compose security_opt / profile, or a separate renderer container if the probe cannot pass); this step keeps REGISTRY_DOC_TORB empty in the image.
  • No seed impact: no syntax change, no new native, no driver flag, no std name looked up by string. One Conventional Commit, feat(registry): run the documentation worker's renders in bubblewrap; a second docs(registry): ... only if the README/RELEASE.md text should be its own commit.

Acceptance criteria.

  1. sandboxed with isolation bwrap starts the program under bwrap with exactly the arguments above (unit test on the built argument list, runs everywhere).
  2. A test with a stand-in program run through sandboxed shows, on a host where bwrap works: it cannot read /proc/<parent pid>/environ, cannot see or write a file placed beside the scratch directory (a stand-in for /srv/torb/...), has no network (a connect to loopback fails), runs as uid 65534, and can write inside the work directory. The test probes bwrap first and reports that it was skipped where user namespaces are not available.
  3. started fails with a clear message when isolation is bwrap and the probe fails; none keeps today's behaviour. The existing documentation tests pass with none.
  4. A scratch directory below the storage or database path is refused at start (test).
  5. tools/registry/README.md (settings table, the "limited, not isolated" sentence), the comment of documentation.trb and Dockerfile.registry/compose.example.yml comments say what is isolated and what step 2 still owes; RELEASE.md section 7.7 gets the same in one paragraph. Docs change regenerates skills/ only if a skill template is touched (not expected).

Out of scope. Compose/seccomp changes and the production switch-on (step 2), escaping of raw HTML (#234), fault classification (#236).

Step 1 of the epic #233 (isolating the documentation worker's runs). Related: #17, #234, #236. **Goal.** Every `torb` run of the documentation worker (`sandboxed` in `tools/registry/src/documentation.trb`, the single place that starts one, including the `--version` probe of `DocumentationWorker.started`) runs inside bubblewrap, so a render cannot see or touch the service. **Design (decided by the loop, see #233).** - `sandboxed` puts `bwrap` between `prlimit` and `env -i` (the limits stay): `--unshare-all` (user, pid, ipc, uts, net, cgroup), `--die-with-parent`, `--new-session`, `--uid 65534 --gid 65534`, `--ro-bind` of `/usr`, `/bin`, `/lib*` (symlinks recreated) and of the toolchain directory (the directory of `documentationToolchain`), `--proc /proc`, `--dev /dev`, `--tmpfs /tmp`, `--bind <work> <work>` (the only writable path, the scratch the service hands over and reads the site back from), `--chdir <work>`, `--clearenv`. No `/srv/torb` path is mounted and nothing else of the service's file system. - New setting `REGISTRY_DOC_ISOLATION`: `bwrap` (default) or `none`. The worker refuses to start (the existing "started" error path, so the operator sees it) when `bwrap` is selected and the `--version` probe fails inside it; `none` is for the stand-in toolchain of the tests and for development, and the README says it must not be used in production. - The worker also refuses a scratch directory (`REGISTRY_DOC_SCRATCH`) that lies inside the storage or database path. - `tools/deploy/Dockerfile.registry` installs `bubblewrap` in the runtime stage. Whether the container may create user namespaces (Docker's default seccomp profile forbids it) is step 2, the deployment (compose `security_opt` / profile, or a separate renderer container if the probe cannot pass); this step keeps `REGISTRY_DOC_TORB` empty in the image. - No seed impact: no syntax change, no new native, no driver flag, no std name looked up by string. One Conventional Commit, `feat(registry): run the documentation worker's renders in bubblewrap`; a second `docs(registry): ...` only if the README/RELEASE.md text should be its own commit. **Acceptance criteria.** 1. `sandboxed` with isolation `bwrap` starts the program under `bwrap` with exactly the arguments above (unit test on the built argument list, runs everywhere). 2. A test with a stand-in program run through `sandboxed` shows, on a host where `bwrap` works: it cannot read `/proc/<parent pid>/environ`, cannot see or write a file placed beside the scratch directory (a stand-in for `/srv/torb/...`), has no network (a connect to loopback fails), runs as uid 65534, and can write inside the work directory. The test probes `bwrap` first and reports that it was skipped where user namespaces are not available. 3. `started` fails with a clear message when isolation is `bwrap` and the probe fails; `none` keeps today's behaviour. The existing documentation tests pass with `none`. 4. A scratch directory below the storage or database path is refused at start (test). 5. `tools/registry/README.md` (settings table, the "limited, not isolated" sentence), the comment of `documentation.trb` and `Dockerfile.registry`/`compose.example.yml` comments say what is isolated and what step 2 still owes; RELEASE.md section 7.7 gets the same in one paragraph. Docs change regenerates `skills/` only if a skill template is touched (not expected). **Out of scope.** Compose/seccomp changes and the production switch-on (step 2), escaping of raw HTML (#234), fault classification (#236).
Author
Collaborator

[loop:torbscript] claim implement by session:loop-torbscript-20261026-1044 until 2026-10-26T16:05:05Z

[loop:torbscript] claim implement by session:loop-torbscript-20261026-1044 until 2026-10-26T16:05:05Z
Author
Collaborator

[loop:torbscript] implement: checkpoint (context budget), branch loop/497-registry-bubblewrap pushed (one WIP commit, to be squashed into the final Conventional Commit). Done: Config.documentationIsolation (REGISTRY_DOC_ISOLATION, default bwrap), sandboxCommand/bubblewrapArguments/systemLinks/toolchainDirectory/scratchProblem in tools/registry/src/documentation.trb, started validates isolation, refuses a scratch inside storage/database, probe error names bubblewrap; existing test config uses none. Not compiled or run yet (no torb in the worker env). Open: tests (argument-list unit test, bwrap-probing sandbox test with skip, scratch-inside-storage refusal, probe-failure message), README settings table + "limited, not isolated" sentence, comments in Dockerfile.registry (install bubblewrap, keep REGISTRY_DOC_TORB empty) and compose.example.yml, RELEASE.md 7.7 paragraph, reword commit to feat(registry): run the documentation worker's renders in bubblewrap, Part of #233 / Closes #497 PR, ci-wait. Note: toolchain dir bound is the parent of bin (torb finds std/ beside bin). Roughly 100k tokens used. next: implement (continue)

[loop:torbscript] implement: checkpoint (context budget), branch `loop/497-registry-bubblewrap` pushed (one WIP commit, to be squashed into the final Conventional Commit). Done: `Config.documentationIsolation` (`REGISTRY_DOC_ISOLATION`, default `bwrap`), `sandboxCommand`/`bubblewrapArguments`/`systemLinks`/`toolchainDirectory`/`scratchProblem` in `tools/registry/src/documentation.trb`, `started` validates isolation, refuses a scratch inside storage/database, probe error names bubblewrap; existing test config uses `none`. Not compiled or run yet (no torb in the worker env). Open: tests (argument-list unit test, bwrap-probing sandbox test with skip, scratch-inside-storage refusal, probe-failure message), README settings table + "limited, not isolated" sentence, comments in `Dockerfile.registry` (install `bubblewrap`, keep `REGISTRY_DOC_TORB` empty) and `compose.example.yml`, RELEASE.md 7.7 paragraph, reword commit to `feat(registry): run the documentation worker's renders in bubblewrap`, `Part of #233` / `Closes #497` PR, ci-wait. Note: toolchain dir bound is the parent of `bin` (torb finds `std/` beside `bin`). Roughly 100k tokens used. next: implement (continue)
Author
Collaborator

[loop:torbscript] claim implement (continuation 1) by session:loop-torbscript-20261026-1044 until 2026-10-26T16:15:10Z

[loop:torbscript] claim implement (continuation 1) by session:loop-torbscript-20261026-1044 until 2026-10-26T16:15:10Z
Author
Collaborator

[loop:torbscript] implement: checkpoint (context budget), PR #500 open (Part of #233, Closes #497), branch loop/497-registry-bubblewrap squashed to one Conventional Commit. Done: code fixed to compile (torb check clean), documentation-isolation.test.trb (8 tests pass locally with the nightly toolchain; bwrap is absent on this host so the isolation run itself is exercised only in CI, it prints a skip note where bwrap does not work), README table + sentence, Dockerfile.registry (bubblewrap), compose comments, RELEASE.md 7.7. Open: forge CI result for cd352aa8 (ci-wait in flight), fix findings on the same branch, then comment next: review (Argus). Roughly 160k tokens used by this continuation.

[loop:torbscript] implement: checkpoint (context budget), PR https://git.torb.dev/torbscript/language/pulls/500 open (`Part of #233`, `Closes #497`), branch `loop/497-registry-bubblewrap` squashed to one Conventional Commit. Done: code fixed to compile (`torb check` clean), `documentation-isolation.test.trb` (8 tests pass locally with the nightly toolchain; bwrap is absent on this host so the isolation run itself is exercised only in CI, it prints a skip note where bwrap does not work), README table + sentence, Dockerfile.registry (`bubblewrap`), compose comments, RELEASE.md 7.7. Open: forge CI result for cd352aa8 (ci-wait in flight), fix findings on the same branch, then comment `next: review (Argus)`. Roughly 160k tokens used by this continuation.
Author
Collaborator

[loop:torbscript] claim implement (continuation 2) by session:loop-torbscript-20261026-1044 until 2026-10-26T16:35:04Z

[loop:torbscript] claim implement (continuation 2) by session:loop-torbscript-20261026-1044 until 2026-10-26T16:35:04Z
Author
Collaborator

[loop:torbscript] implement: forge CI green on PR head 584a9d3c (#500). The only failure at cd352aa8 was pdisc (stale body hash of tools/registry/README.md); fixed by restamping .pdisc-sidecar.yaml, amended into the single Conventional Commit and force-with-lease pushed. gates, bootstrap, tier A/B and the tools/registry job (which runs the new bwrap test) all pass. About 60k tokens this continuation, most of it waiting on a long CI queue. next: review (Argus)

[loop:torbscript] implement: forge CI green on PR head 584a9d3c (https://git.torb.dev/torbscript/language/pulls/500). The only failure at cd352aa8 was pdisc (stale body hash of `tools/registry/README.md`); fixed by restamping `.pdisc-sidecar.yaml`, amended into the single Conventional Commit and force-with-lease pushed. gates, bootstrap, tier A/B and the tools/registry job (which runs the new bwrap test) all pass. About 60k tokens this continuation, most of it waiting on a long CI queue. next: review (Argus)
argus closed this issue 2026-10-26 13:00:46 +00:00
Sign in to join this conversation.
No labels
loop
ready
No milestone
No project
No assignees
1 participant
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
torbscript/language#497
No description provided.