Merge pull request #4666 from open-gsd/fix/4652-containment-at-boundaries
This commit is contained in:
@@ -1177,6 +1177,20 @@ from `todos/pending/` to `todos/completed/` and upserts `completed:` and
|
||||
`status: completed` inside the file's frontmatter block. Unknown flags are
|
||||
rejected loudly.
|
||||
|
||||
`<filename>` is a **basename inside the todos root**, not a path. A basename
|
||||
guard runs first, before `<filename>` is joined onto any directory: a value
|
||||
containing an embedded separator (either `/` or `\`, e.g. `sub/name.md` or
|
||||
`sub\name.md`), a value whose own basename differs from itself (e.g.
|
||||
`a/../../b.md`, `../sibling.md`), a bare `.` or `..`, an absolute path (e.g.
|
||||
`/etc/passwd`), or a NUL byte is rejected as a usage error **before** any file
|
||||
is read or moved (#4327, #4652). A traversal that only escapes the `pending`/
|
||||
`completed` subdirectory without leaving the todos root (`../sibling.md`) is
|
||||
caught by this same guard, not by containment. Containment against the todos
|
||||
root still runs afterward as defense-in-depth for the resolved source and
|
||||
target paths, so neither half of the move can land outside the root. The
|
||||
check covers both halves of the move, and `--dry-run` is rejected on the same
|
||||
terms rather than previewing a resolved outside path.
|
||||
|
||||
```bash
|
||||
# UAT audit — scan all phases for unresolved items
|
||||
node gsd-tools.cjs audit-uat
|
||||
|
||||
@@ -81,6 +81,18 @@ the gate context; all others are left untouched for `sh` to interpret:
|
||||
|
||||
An undefined placeholder interpolates to the empty string.
|
||||
|
||||
**`--phase-dir` is confined to the project.** The value is validated to resolve
|
||||
inside the project root before any predicate is evaluated; one that escapes is
|
||||
rejected as a usage error rather than evaluated. This applies to both kinds —
|
||||
`artifact-frontmatter-equals` resolves its artifact under that directory, and
|
||||
`command-exit-zero` interpolates it into `${PHASE_DIR}` — so an unconfined value
|
||||
would let a **blocking** gate return `block: false` on evidence from a directory
|
||||
the caller chose (#4354). An absolute path inside the project is still accepted;
|
||||
absolute is not a synonym for escaping. `${PHASE_DIR}` always interpolates the
|
||||
**resolved absolute path**, even when `--phase-dir` was given as a relative
|
||||
value — a command relying on `${PHASE_DIR}` staying relative must not assume
|
||||
that.
|
||||
|
||||
**Sandbox.** cwd = project root; env = inherited from the GSD process; killed
|
||||
(SIGTERM) on timeout. The command runs as the user, on the user's machine —
|
||||
there is no sandbox boundary vs. the user's own shell. See ADR-2008 "Trust
|
||||
|
||||
Reference in New Issue
Block a user