registry: isolate the documentation worker's runs from the service #233

Open
opened 2026-10-08 22:38:44 +00:00 by orion · 3 comments
Collaborator

Follow-up of #17.

The documentation worker (tools/registry/src/documentation.trb) runs torb doc --check --no-run and torb doc on a stranger's package. Those runs are bounded by timeout, prlimit and env -i, but they are not isolated. They run as the service's user, beside /srv/torb/registry, /srv/torb/database and the index mirror, and they can read /proc/<registry>/environ, which holds the signing key and the operator token.

No code of the package runs, so this is defence in depth against a toolchain bug. The path-traversal bug fixed in the PR of #17 is one example of such a bug.

Proposal: render in a separate container or process with:

  • no network,
  • a read-only root,
  • a tmpfs scratch,
  • no /srv/torb mounts, and
  • a different uid.

The service hands over the unpacked package and takes the site back. Bubblewrap is an alternative where user namespaces are allowed.

Acceptance: a render cannot read the service's environment or write to its files, and a test shows it.

Should be done before the worker is enabled in production.

Epic. Too large for one PR; steps (each its own issue when it starts):

  • Step 1: bubblewrap around every render, fail-closed probe, scratch check, tests: #497
  • Step 2: deployment, container allowed to create user namespaces (compose security_opt/seccomp profile; a separate no-network renderer container if the probe cannot pass), live check that the worker starts; then the production switch-on
Follow-up of #17. The documentation worker (`tools/registry/src/documentation.trb`) runs `torb doc --check --no-run` and `torb doc` on a stranger's package. Those runs are bounded by `timeout`, `prlimit` and `env -i`, but they are not isolated. They run as the service's user, beside `/srv/torb/registry`, `/srv/torb/database` and the index mirror, and they can read `/proc/<registry>/environ`, which holds the signing key and the operator token. No code of the package runs, so this is defence in depth against a toolchain bug. The path-traversal bug fixed in the PR of #17 is one example of such a bug. **Proposal:** render in a separate container or process with: - no network, - a read-only root, - a tmpfs scratch, - no `/srv/torb` mounts, and - a different uid. The service hands over the unpacked package and takes the site back. Bubblewrap is an alternative where user namespaces are allowed. **Acceptance:** a render cannot read the service's environment or write to its files, and a test shows it. Should be done before the worker is enabled in production. **Epic.** Too large for one PR; steps (each its own issue when it starts): - [ ] Step 1: bubblewrap around every render, fail-closed probe, scratch check, tests: #497 - [ ] Step 2: deployment, container allowed to create user namespaces (compose `security_opt`/seccomp profile; a separate no-network renderer container if the probe cannot pass), live check that the worker starts; then the production switch-on
Author
Collaborator

[loop:torbscript] intake: in scope (author orion, label loop), follow-up of #17, not a duplicate. Registry hardening of the documentation worker (isolation of stranger packages); security-relevant, refine should pick the isolation mechanism. next: waiting (refine queued, ready buffer full) until 2026-10-10T04:00:00Z

[loop:torbscript] intake: in scope (author orion, label loop), follow-up of #17, not a duplicate. Registry hardening of the documentation worker (isolation of stranger packages); security-relevant, refine should pick the isolation mechanism. next: waiting (refine queued, ready buffer full) until 2026-10-10T04:00:00Z
Author
Collaborator

[loop:torbscript] claim refine by session:loop-torbscript-20261026-1044 until 2026-10-26T11:56:08Z

[loop:torbscript] claim refine by session:loop-torbscript-20261026-1044 until 2026-10-26T11:56:08Z
Author
Collaborator

[loop:torbscript] decided by the loop: isolation mechanism is bubblewrap inside the registry image, not a sibling container. Why: sandboxed in tools/registry/src/documentation.trb is the one place every run goes through, so a wrapper there meets all acceptance points (no network, read-only root, tmpfs, no /srv/torb mount, other uid, own pid namespace so /proc/<registry>/environ is gone) without a new protocol, service or shared volume; a sibling renderer container would need a request/response channel for the package and the site (a second server in TorbScript, or a shared exchange volume) and is kept as the fallback for step 2 if user namespaces cannot be allowed in the container. Fail-closed: the worker refuses to start when the isolated --version probe fails.

[loop:torbscript] refine: #233 is too large for one PR (code + image + deployment/seccomp + live proof), so it stays an epic without ready; its checklist now holds both steps. Step 1 is filed as #497 (label loop + ready, no keyword duplicate among the open issues; #236 and #234 touch the same file but other concerns), seed untouched, one commit. Step 2 (deployment, production switch-on) is filed when step 1 has landed. next: waiting (epic, step 1 = #497 ready for implement). Tokens: ~60k.

[loop:torbscript] decided by the loop: isolation mechanism is bubblewrap inside the registry image, not a sibling container. Why: `sandboxed` in `tools/registry/src/documentation.trb` is the one place every run goes through, so a wrapper there meets all acceptance points (no network, read-only root, tmpfs, no `/srv/torb` mount, other uid, own pid namespace so `/proc/<registry>/environ` is gone) without a new protocol, service or shared volume; a sibling renderer container would need a request/response channel for the package and the site (a second server in TorbScript, or a shared exchange volume) and is kept as the fallback for step 2 if user namespaces cannot be allowed in the container. Fail-closed: the worker refuses to start when the isolated `--version` probe fails. [loop:torbscript] refine: #233 is too large for one PR (code + image + deployment/seccomp + live proof), so it stays an epic without `ready`; its checklist now holds both steps. Step 1 is filed as #497 (label `loop` + `ready`, no keyword duplicate among the open issues; #236 and #234 touch the same file but other concerns), seed untouched, one commit. Step 2 (deployment, production switch-on) is filed when step 1 has landed. next: waiting (epic, step 1 = #497 ready for implement). Tokens: ~60k.
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#233
No description provided.