* test(3596): adversarial security/prompt-injection abuse suite
Adds `tests/security-prompt-injection.test.cjs` and a fixtures
directory at `tests/fixtures/adversarial/security/` covering the
attack classes enumerated in #3596:
- Command substitution / backticks / heredoc payloads in workstream
names — sentinel-file probes prove no shell is spawned, slugifier
neutralises the input.
- Path traversal through `--ws` and slash-bearing workstream names —
rejected with structured `--json-errors` payload, no stack trace,
no filesystem mutation outside the project root.
- Fake `<system>` / `[SYSTEM]` / `<<SYS>>` / `[INST]` boundary tags —
sanitizeForPrompt neutralises every form; structural negative
property locked across all six styles in one place.
- Zero-width / bidi-override codepoints — stripped per the documented
codepoint set; asserted via codePoint inspection, not regex
literals.
- Hostile read of CONTEXT.md / PLAN.md / ROADMAP.md fixtures —
`gsd-read-injection-scanner.js` surfaces the advisory; excluded
paths and non-Read tools stay silent; malformed JSON does not
crash the hook.
- Hostile write of `.planning/` files — `gsd-prompt-guard.js` emits
a `PreToolUse` advisory; non-Write/Edit tools stay silent.
- Fake `ghp_*` / `sk-*` env tokens — never echoed in CLI stdout or
stderr under hostile inputs; covered under
`// allow-test-rule: structural-regression-guard` because the only
way to assert byte-level absence is `.includes(token)` against the
captured streams.
- `validatePath`, `validateShellArg`, `validatePhaseNumber`,
`validateFieldName` — focused negative-input contract pins.
Pinned behavior gaps (documented, NOT fixed in this PR):
- `<instructions>` is intentionally whitelisted by both the scanner
and the sanitiser (GSD's own prompt scaffolding). Two REGRESSION
GUARD tests lock that contract.
- The current `scanForInjection` does NOT flag malicious markdown
links (javascript:/data:/embedded-credentials URLs). PINNED with
negative-proof so any future scope extension fails the assertion
and forces a deliberate update to the acceptance map.
- `prompt-builder.ts` does not yet wrap plan/context markdown in an
"untrusted data" envelope. That seam lives on the TS side and is
covered by `sdk/src/prompt-builder.test.ts`; out of scope for a
CJS test file. Mentioned in the file header.
Verification:
- `node --test tests/security-prompt-injection.test.cjs` → 73 tests
pass.
- `node scripts/lint-no-source-grep.cjs` → 0 violations across
546 test files (one `allow-test-rule: structural-regression-guard`
annotation on this file for the token-absence assertions).
- `node scripts/run-tests.cjs` → 9730 tests pass, 0 fail.
Refs #3596
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(3596): allow adversarial fixtures in scan + harden graphify status parse
* fix(3596): skip adversarial security fixtures in secret scan
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
agents/gsd-planner.md was 49,316 chars after the initial PR; the
planner-decomposition <48K test was passing on main at 49,150 chars (just under
the 49152 limit). My addition pushed it over.
Restructure: instead of teaching the planner agent to read .last-build-status.json
directly, fold the auto-build state into graphifyStatus()'s existing `stale: true`
signal. The planner's existing rule ("if stale: true, treat as approximate") fires
correctly for failed and in-flight auto-builds — no new planner-side prompt content
needed. The full state is exposed under `last_build_auto_update` for callers that
want exit_code / duration_ms / commit-sha context.
- get-shit-done/bin/lib/graphify.cjs: graphifyStatus() reads
.planning/graphs/.last-build-status.json; OR-folds status in {failed, running}
into the existing stale signal; exposes last_build_auto_update field
- agents/gsd-planner.md: revert the auto-update awareness paragraph (49,524 → 49,150)
- agents/gsd-phase-researcher.md: revert the parallel paragraph for consistency
- get-shit-done/references/planner-graphify-auto-update.md: rewrite to document
the graphifyStatus seam instead of planner-side prompt instructions
- tests/feat-3347-graphify-auto-update-config.test.cjs: 4 new graphifyStatus
tests pinning the failed/running/ok/missing matrix
- tests/feat-3347-graphify-auto-update-hook.test.cjs: bump per-spawn timeout
5s → 30s and wait-deadline 5s → 15s to absorb cold-start latency under
parallel-test-file load (full suite runs many *.test.cjs concurrently)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>