docs(#4653): describe the consolidated path-containment seam in the security model
The security model's input-validation section still described path traversal as a per-call check with a macOS symlink footnote. It now describes what actually exists: one predicate, a module-internal engine, three exported shapes none of which can hand back a usable path when the answer is unsafe, the branded return type, and the named acceptance policy — including the point the old wording invited a reader to get wrong, that allowing an absolute candidate does not relax containment. Also records the two checks deliberately NOT routed through the predicate and why each is narrower or stricter rather than a second opinion, so a later cleanup pass does not read them as stragglers. Required by the Changed changeset: scripts/lint-docs-required.cjs makes Added/Changed/Deprecated/Removed fragments demand a file under docs/, and CONTEXT.md is at the repo root, so the glossary entry alone would not have satisfied it. The lint currently reports invalid_pr against the mandated pr:0 placeholder and becomes meaningful after backfill. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -138,9 +138,22 @@ GSD Core addresses prompt injection at three levels.
|
||||
**Input validation (`security.cjs`).** The `gsd-core/bin/lib/security.cjs`
|
||||
module is the central security utility. It provides:
|
||||
|
||||
- Path traversal prevention: user-supplied file paths (`--text-file`, `--prd`)
|
||||
are validated to resolve within the project directory, with macOS
|
||||
`/var` → `/private/var` symlink resolution handled explicitly
|
||||
- Path containment: user-supplied file paths and directories are validated to
|
||||
resolve within a declared root before any filesystem access. One predicate
|
||||
answers this for the whole tree (epic #4636, ADR-4650). The resolution engine
|
||||
is module-internal and resolves symlinks, closes a dangling-symlink existence
|
||||
oracle, and canonicalizes ancestors so a not-yet-created path under a
|
||||
non-canonical base (macOS `/var` → `/private/var`) still resolves. The
|
||||
exported surface is `assertWithinRoot` (throws), `tryWithinRoot` (returns
|
||||
`null`), and `requireSafePath` (a preserved alias of the throwing form).
|
||||
All three return a branded `ContainedPath`: a plain `string` is not assignable
|
||||
to it, so validating one path and then handing a different one to the
|
||||
filesystem is a type error rather than a silent bug. Whether an absolute
|
||||
candidate is considered at all is a named policy — `PathAcceptance.RelativeOnly`
|
||||
or `PathAcceptance.AbsoluteInsideRoot` — and neither relaxes containment: an
|
||||
absolute path resolving outside the root is rejected exactly as a traversal is.
|
||||
A caller may decide how to degrade on rejection, never whether a path is
|
||||
contained.
|
||||
- Prompt injection detection: known injection patterns (role overrides,
|
||||
instruction bypasses, system tag injections) are scanned in user-supplied
|
||||
text before it enters any planning artifact
|
||||
@@ -149,6 +162,18 @@ module is the central security utility. It provides:
|
||||
- Shell argument validation: arguments passed to subshell commands are
|
||||
validated before use
|
||||
|
||||
Two containment checks elsewhere in the tree are deliberately NOT routed through
|
||||
this predicate, because each is narrower or stricter rather than a second opinion.
|
||||
The backup-restore gate in `gsd-core/bin/gsd-tools.cjs` rejects symlinks outright:
|
||||
the canonical predicate accepts a link whose target resolves inside the root, but
|
||||
for a restore that is still wrong, because writing through the link overwrites
|
||||
whatever it points at instead of materializing a regular file at the backed-up
|
||||
path. And `isPathConfined` in `src/external-descriptor-trust.cts` is lexical by
|
||||
design, because two install callers must validate a destination *before* the
|
||||
`mkdirSync` that creates it, where `realpath` cannot resolve. A lexical check
|
||||
cannot see a symlink, so callers that rely on it for a write-confinement
|
||||
guarantee must pair it with their own symlink refusal.
|
||||
|
||||
**Runtime hook: `gsd-prompt-guard.js`.** This hook fires on every Write or
|
||||
Edit call that targets `.planning/` files. It scans the content being written
|
||||
for injection patterns shared with `gsd-read-injection-scanner.js` through
|
||||
|
||||
Reference in New Issue
Block a user