Request helper: private exchange body, filtered refusals, a checked upload directory #288

Closed
opened 2026-10-09 21:56:34 +00:00 by orion · 4 comments
Collaborator

Found in the architect review of #287 (torb owner --step-up), out of its scope:

  1. tokenFor in compiler/src/package/upload.trb writes exchange.json, which holds the CI job's identity token, with writeText (default mode) and empties it afterwards. It should go through writePrivate like the header files.
  2. refusalOf prints the registry's body as it came where it is not the service's JSON, so a hostile or broken registry can put terminal escape sequences on standard error, for every registry command. Strip control characters there.
  3. request accepts a <cache>/upload that is a symbolic link or belongs to another user (a shared TORB_CACHE under /tmp). Refuse it unless it is a real directory owned by the user.

#287 already makes the directory 0700, makes the private files anew and empties all three files even where one fails.

Found in the architect review of #287 (`torb owner --step-up`), out of its scope: 1. `tokenFor` in `compiler/src/package/upload.trb` writes `exchange.json`, which holds the CI job's identity token, with `writeText` (default mode) and empties it afterwards. It should go through `writePrivate` like the header files. 2. `refusalOf` prints the registry's body as it came where it is not the service's JSON, so a hostile or broken registry can put terminal escape sequences on standard error, for every registry command. Strip control characters there. 3. `request` accepts a `<cache>/upload` that is a symbolic link or belongs to another user (a shared `TORB_CACHE` under `/tmp`). Refuse it unless it is a real directory owned by the user. #287 already makes the directory `0700`, makes the private files anew and empties all three files even where one fails.
Author
Collaborator

[loop:torbscript] intake: in scope (author orion, follow-up of the architect review on PR #287 for #280), not a duplicate, no collision with a design record; security hardening of the registry request helper in compiler/src/package. next: refine

[loop:torbscript] intake: in scope (author orion, follow-up of the architect review on PR #287 for #280), not a duplicate, no collision with a design record; security hardening of the registry request helper in compiler/src/package. next: refine
Author
Collaborator

[loop:torbscript] refine: ready, one PR (read against main after #287 landed; all three points are in compiler/src/package/upload.trb, tests in compiler/tests/packages.test.trb).

Goal. The request helper stops leaving a token in a world-readable file, stops printing a registry's raw bytes to the terminal, and refuses an upload directory that is not the user's own.

Acceptance criteria (each with a test)

  • tokenFor writes <work>/upload/exchange.json through writePrivate (made anew, mode 0600 while still empty), and empties it afterwards as today, also where the exchange fails. Test: the file's mode after a failed and after a successful exchange.
  • refusalOf strips control characters (C0 except none kept, DEL, C1, so ESC, CSI and OSC cannot start a sequence) from the body it prints where the body is not the service's JSON; the same for the identity-token answer text in tokenFor and the error field of a JSON answer, since a hostile registry controls those too. Newlines inside the text become a single space. Test: a body with \u001b[2J and \u001b]0;x\u0007 comes out as plain text.
  • request refuses <work>/upload when it is a symbolic link, is not a directory, or is owned by another user, with a message naming the path and the reason, before any file is written into it and before chmod. A real directory of the user (existing or just created) works as today. Tests: a symlink to a directory, a regular file in its place, and the normal case; the other-owner case is covered where the test can arrange it, else by the owner check being a separate function with a unit test.
  • torb login, torb owner, torb yank and publish keep working unchanged (existing loopback tests stay green).

Seed. No syntax change. If std/fs already offers a symlink and owner query, no seed impact. If the owner or link query is missing, add it as a method of File (methods over free functions, naming per the std conventions) under the two-commit rule: the compiler/std change first, the use in upload.trb second; the seed refresh stays Torben's. Decision by the loop: the check belongs in request itself, since all registry commands go through it; no new env var and no opt-out.

Size. One PR, one or two Conventional Commits (fix(package): ...), no docs change beyond the doc comment of request (it states the directory contract); if docs/ mentions the upload directory, update it and regenerate skills/.

Collision. #279 (in flight) uses request but does not edit this file; rebase before pushing.

[loop:torbscript] refine: ready, one PR (read against main after #287 landed; all three points are in `compiler/src/package/upload.trb`, tests in `compiler/tests/packages.test.trb`). **Goal.** The request helper stops leaving a token in a world-readable file, stops printing a registry's raw bytes to the terminal, and refuses an upload directory that is not the user's own. **Acceptance criteria** (each with a test) - [ ] `tokenFor` writes `<work>/upload/exchange.json` through `writePrivate` (made anew, mode `0600` while still empty), and empties it afterwards as today, also where the exchange fails. Test: the file's mode after a failed and after a successful exchange. - [ ] `refusalOf` strips control characters (C0 except none kept, DEL, C1, so ESC, CSI and OSC cannot start a sequence) from the body it prints where the body is not the service's JSON; the same for the identity-token answer text in `tokenFor` and the `error` field of a JSON answer, since a hostile registry controls those too. Newlines inside the text become a single space. Test: a body with `\u001b[2J` and `\u001b]0;x\u0007` comes out as plain text. - [ ] `request` refuses `<work>/upload` when it is a symbolic link, is not a directory, or is owned by another user, with a message naming the path and the reason, before any file is written into it and before `chmod`. A real directory of the user (existing or just created) works as today. Tests: a symlink to a directory, a regular file in its place, and the normal case; the other-owner case is covered where the test can arrange it, else by the owner check being a separate function with a unit test. - [ ] `torb login`, `torb owner`, `torb yank` and `publish` keep working unchanged (existing loopback tests stay green). **Seed.** No syntax change. If `std/fs` already offers a symlink and owner query, no seed impact. If the owner or link query is missing, add it as a method of `File` (methods over free functions, naming per the std conventions) under the two-commit rule: the compiler/std change first, the use in `upload.trb` second; the seed refresh stays Torben's. Decision by the loop: the check belongs in `request` itself, since all registry commands go through it; no new env var and no opt-out. **Size.** One PR, one or two Conventional Commits (`fix(package): ...`), no docs change beyond the doc comment of `request` (it states the directory contract); if `docs/` mentions the upload directory, update it and regenerate `skills/`. **Collision.** #279 (in flight) uses `request` but does not edit this file; rebase before pushing.
Author
Collaborator

[loop:torbscript] claim implement by session:loop-torbscript-20261010 until 2026-10-10T06:10Z (ready, refined in the comment above)

[loop:torbscript] claim implement by session:loop-torbscript-20261010 until 2026-10-10T06:10Z (ready, refined in the comment above)
Author
Collaborator

[loop:torbscript] implement: PR #294, CI success (tier A and tools/registry green, run 717), worker usage ~160k tokens, next: waiting (review by the merge gate argus). Decided by the loop: the "owned by another user" half of criterion 3 needs a new std/fs owner native whose use must wait for a seed refresh (two-commit rule), so it moved to #293 with the registry.trb body writes; until then the chmod to 0700 after the new link/directory checks fails for non-root users on a foreign directory.

[loop:torbscript] implement: PR https://git.torb.dev/torbscript/language/pulls/294, CI success (tier A and tools/registry green, run 717), worker usage ~160k tokens, next: waiting (review by the merge gate argus). Decided by the loop: the "owned by another user" half of criterion 3 needs a new std/fs owner native whose use must wait for a seed refresh (two-commit rule), so it moved to https://git.torb.dev/torbscript/language/issues/293 with the registry.trb body writes; until then the chmod to 0700 after the new link/directory checks fails for non-root users on a foreign directory.
argus closed this issue 2026-10-10 01:47:11 +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#288
No description provided.