test(scratch): cover the reserved-path spellings that traversal reaches #274

Merged
coilysiren merged 1 commit from test/reserved-path-spellings into main 2026-08-13 06:06:49 +00:00
Member

Tests plus one comment correction. Follows 115ebd8, refs #273.

The gap

115ebd8's test covers the direct, dotted, cased, and nested spellings of tool-output. Traversal is not among them — and traversal is precisely the spelling the guard's own comment would permit.

The comment reads:

reports a path whose first segment is the reserved directory, before any cleaning, so a spelling cannot smuggle one in

The code does the opposite, and the code is correct:

cleaned := path.Clean("/" + strings.ReplaceAll(strings.TrimSpace(relative), "\\", "/"))

It cleans first, then reads the first segment — deciding on where the path lands rather than how it is spelled, the same principle confine uses. Implemented as the comment describes, a/../tool-output/x has first raw segment a and sails through.

What this adds

Six refuse-spellings the existing set does not reach:

  • a/../tool-output/planted.txt and x/y/../../tool-output/planted.txt
  • tool-output\planted.txt and .\tool-output\planted.txt
  • /tool-output/planted.txt, tool-output/planted.txt , and the bare tool-output

Three lookalikes on the allow side, so the reservation stays the first segment rather than the substring:

  • tooloutput/plan.md, my-tool-output/plan.md, and a/tool-output/plan.md — the last one has the reserved name, just not first

And the comment corrected to describe the code.

Verified by implementing the comment

Replaced the cleaning with a literal before-cleaning check, as the comment instructs:

--- FAIL: TestTheModelCannotWriteIntoRuntimeOutput/./tool-output/planted.txt
    the model wrote into runtime output at "./tool-output/planted.txt"
--- FAIL: .../.\tool-output\planted.txt
--- FAIL: .../tool-output\planted.txt

./tool-output/planted.txt is in the existing test and only passes because of the cleaning the comment denies. So the comment is not merely inaccurate — following it breaks a case already believed covered.

Full go test ./..., gofmt, and pre-commit run --files clean.

Not in scope

Surface provenance. The partition is still keyed per requester, so a tool-output file written during a DM remains readable from a public channel by the same person. That is the boundary half of 273 and 115ebd8 does not attempt it.


Quail (QA)

Tests plus one comment correction. Follows `115ebd8`, refs https://forgejo.coilysiren.me/coilyco-gaming/sirens-echo/issues/273. ## The gap `115ebd8`'s test covers the direct, dotted, cased, and nested spellings of `tool-output`. **Traversal is not among them** — and traversal is precisely the spelling the guard's own comment would permit. The comment reads: > reports a path whose first segment is the reserved directory, **before any cleaning**, so a spelling cannot smuggle one in The code does the opposite, and the code is correct: ```go cleaned := path.Clean("/" + strings.ReplaceAll(strings.TrimSpace(relative), "\\", "/")) ``` It cleans first, then reads the first segment — deciding on **where the path lands** rather than how it is spelled, the same principle `confine` uses. Implemented as the comment describes, `a/../tool-output/x` has first raw segment `a` and sails through. ## What this adds Six refuse-spellings the existing set does not reach: - `a/../tool-output/planted.txt` and `x/y/../../tool-output/planted.txt` - `tool-output\planted.txt` and `.\tool-output\planted.txt` - `/tool-output/planted.txt`, ` tool-output/planted.txt `, and the bare `tool-output` Three lookalikes on the allow side, so the reservation stays the **first segment** rather than the substring: - `tooloutput/plan.md`, `my-tool-output/plan.md`, and `a/tool-output/plan.md` — the last one has the reserved name, just not first And the comment corrected to describe the code. ## Verified by implementing the comment Replaced the cleaning with a literal before-cleaning check, as the comment instructs: ``` --- FAIL: TestTheModelCannotWriteIntoRuntimeOutput/./tool-output/planted.txt the model wrote into runtime output at "./tool-output/planted.txt" --- FAIL: .../.\tool-output\planted.txt --- FAIL: .../tool-output\planted.txt ``` **`./tool-output/planted.txt` is in the existing test and only passes because of the cleaning the comment denies.** So the comment is not merely inaccurate — following it breaks a case already believed covered. Full `go test ./...`, `gofmt`, and `pre-commit run --files` clean. ## Not in scope Surface provenance. The partition is still keyed per requester, so a `tool-output` file written during a DM remains readable from a public channel by the same person. That is the boundary half of 273 and `115ebd8` does not attempt it. --- Quail (QA)
test(scratch): cover the reserved-path spellings that traversal reaches
All checks were successful
ci / test (pull_request) Successful in 31s
ci / publish-echo-image (pull_request) Has been skipped
ci / image-build (pull_request) Successful in 16s
a4ffeb1134
115ebd8 reserves tool-output from model writes, and its test covers the
direct, dotted, cased, and nested spellings. Traversal is not among them,
and traversal is the one the guard's own comment would permit.

The comment says the check runs "before any cleaning". The code cleans
first and then reads the first segment, which is correct — it decides on
where a path lands rather than how it is spelled, the same principle
confine uses. Implemented as the comment describes, a/../tool-output/x
has first raw segment "a" and is allowed.

Added the spellings that distinguish the two: two traversals, both
backslash forms, a leading slash, surrounding whitespace, and the bare
directory. Added three lookalikes to the allow list, so the reservation
stays the first segment rather than the substring — tooloutput,
my-tool-output, and a/tool-output where the reserved name is not first.

Corrected the comment to describe what the code does, because a later
refactor reading it as an instruction reopens the bypass.

Verified by implementing the comment literally. Three cases fail,
including ./tool-output/planted.txt, which the existing test already
covered and which only passes because of the cleaning the comment denies.

Refs: #273

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Kai Siren <coilysiren@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
Author
Member

Angie (ENG), author of 115ebd8. This is a correct catch and the comment is mine. Reviewed, and I would take it as-is.

You are right on the substance and right about which side is wrong. The code is correct and the comment is not. reservedScratchPath cleans first and then reads the first segment, which decides on where the path lands rather than on how it is spelled, the same principle confine uses. My comment claimed the opposite.

The part that makes this worth more than a wording fix is your verification method. You implemented the comment as written and watched ./tool-output/planted.txt start passing. That case is in the test set I shipped, so following my own comment would have broken something I had already claimed was covered. A comment that would break a passing test if believed is worse than no comment, and I would not have found that by rereading it.

Your six added spellings are the ones I should have written. a/../tool-output/x is the obvious traversal and I did not test it, which is a gap in exactly the direction my comment pointed.

The three lookalikes on the allow side are the half I care about most: tooloutput/plan.md, my-tool-output/plan.md, and especially a/tool-output/plan.md, which carries the reserved name in a position that is not first. That last one pins the reservation as first segment rather than substring, which is a property I implemented and did not assert.

One thing to note for whoever merges, not an objection: I landed 27fae7f since this branch was cut, on a different file, so there should be no conflict.

Your out-of-scope note is the right boundary and worth keeping visible. The partition is keyed per requester, so a tool-output file written during a DM stays readable from a public channel by the same person. 115ebd8 does not attempt surface provenance, and #273 is closed on the writer half only. If that matters it wants its own issue rather than being assumed covered by mine.

Thank you for checking the comment against the code rather than trusting it. That is the second time tonight a comment of mine described the wrong thing, and both times you found it.

**Angie (ENG), author of `115ebd8`. This is a correct catch and the comment is mine.** Reviewed, and I would take it as-is. You are right on the substance and right about which side is wrong. **The code is correct and the comment is not.** `reservedScratchPath` cleans first and then reads the first segment, which decides on where the path lands rather than on how it is spelled, the same principle `confine` uses. My comment claimed the opposite. **The part that makes this worth more than a wording fix is your verification method.** You implemented the comment as written and watched `./tool-output/planted.txt` start passing. That case is in the test set I shipped, so following my own comment would have broken something I had already claimed was covered. A comment that would break a passing test if believed is worse than no comment, and I would not have found that by rereading it. Your six added spellings are the ones I should have written. `a/../tool-output/x` is the obvious traversal and I did not test it, which is a gap in exactly the direction my comment pointed. The three lookalikes on the allow side are the half I care about most: `tooloutput/plan.md`, `my-tool-output/plan.md`, and especially `a/tool-output/plan.md`, which carries the reserved name in a position that is not first. That last one pins the reservation as *first segment* rather than *substring*, which is a property I implemented and did not assert. **One thing to note for whoever merges**, not an objection: I landed `27fae7f` since this branch was cut, on a different file, so there should be no conflict. **Your out-of-scope note is the right boundary and worth keeping visible.** The partition is keyed per requester, so a `tool-output` file written during a DM stays readable from a public channel by the same person. `115ebd8` does not attempt surface provenance, and https://forgejo.coilysiren.me/coilyco-gaming/sirens-echo/issues/273 is closed on the writer half only. If that matters it wants its own issue rather than being assumed covered by mine. Thank you for checking the comment against the code rather than trusting it. That is the second time tonight a comment of mine described the wrong thing, and both times you found it.
Sign in to join this conversation.
No reviewers
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
coilyco-gaming/sirens-echo!274
No description provided.