fix(write-intercept): close bare-relative-path bypass in STATE.md regex

The previous regex `/[/\\]\.gsd[/\\]STATE\.md$/` required a path
separator *before* `.gsd`, so a bare relative path like `.gsd/STATE.md`
(no leading directory component) was not blocked. If the file doesn't
exist yet, `realpathSync` throws and the bare path slipped through
undetected.

Fix: change both patterns to `(^|[/\\])` so paths starting with `.gsd/`
are caught regardless of whether a separator precedes them.

Caught during e2e team verification (write-intercept-e2e agent).
Updated test to assert the bare path is now blocked.
This commit is contained in:
Jeremy McSpadden 2026-03-24 23:55:25 -05:00 committed by Lex Christopherson
parent eab3851a56
commit 5130b04d5a
2 changed files with 9 additions and 8 deletions

View file

@ -12,14 +12,13 @@ test('write-intercept: blocks unix .gsd/STATE.md path', () => {
});
test('write-intercept: blocks relative path with dir prefix before .gsd/STATE.md', () => {
// The regex requires a path separator before .gsd — bare '.gsd/STATE.md' is not blocked
// but 'project/.gsd/STATE.md' is (has separator before .gsd)
assert.strictEqual(isBlockedStateFile('project/.gsd/STATE.md'), true);
});
test('write-intercept: does NOT block bare .gsd/STATE.md without leading separator', () => {
// Regex requires [/\\] before .gsd — bare relative path has no such separator
assert.strictEqual(isBlockedStateFile('.gsd/STATE.md'), false);
test('write-intercept: blocks bare relative .gsd/STATE.md (no leading separator)', () => {
// (^|[/\\]) matches paths that start with .gsd/ — covers the case where write
// tools receive a bare relative path before the file exists (realpathSync fails).
assert.strictEqual(isBlockedStateFile('.gsd/STATE.md'), true);
});
test('write-intercept: blocks nested project .gsd/STATE.md path', () => {

View file

@ -16,10 +16,12 @@ import { realpathSync } from "node:fs";
* - SUMMARY.md, KNOWLEDGE.md, CONTEXT.md non-authoritative content
*/
const BLOCKED_PATTERNS: RegExp[] = [
// STATE.md is the only purely engine-rendered file
/[/\\]\.gsd[/\\]STATE\.md$/,
// STATE.md is the only purely engine-rendered file.
// (^|[/\\]) matches both absolute paths (/project/.gsd/…) and bare relative
// paths (.gsd/STATE.md) so a path without a leading separator is also blocked.
/(^|[/\\])\.gsd[/\\]STATE\.md$/,
// Also match resolved symlink paths under ~/.gsd/projects/ (Pitfall #6)
/[/\\]\.gsd[/\\]projects[/\\][^/\\]+[/\\]STATE\.md$/,
/(^|[/\\])\.gsd[/\\]projects[/\\][^/\\]+[/\\]STATE\.md$/,
];
/**