fix(browse): write self-contained .gstack/.gitignore unconditionally

ensureStateDir only appended .gstack/ to the project .gitignore when that file
already existed, skipped silently on ENOENT, and swallowed other append
failures. With BROWSE_PERSIST_STATE=1, session-state.json (live cookies +
localStorage/sessionStorage tokens) and browse-network.log / browse-audit.jsonl
(request headers) then sat git-add-able under <git-root>/.gstack/. Write a
self-contained <stateDir>/.gitignore containing "*" unconditionally, before
return, so the state dir's contents can never be committed regardless of the
project .gitignore. The project-.gitignore append is kept as redundant safety.

The no-import-side-effects guard is relaxed to allow exactly this lone
.gitignore guard file (still fails on browse.json / session-state.json / logs /
listener binds) — the guard is written eagerly by ensureStateDir at import and
is not leaked state.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-08-16 10:14:32 -07:00
co-authored by Claude Fable 5
parent 57a1d957f4
commit 4992a48c57
3 changed files with 34 additions and 1 deletions
+14
View File
@@ -114,6 +114,20 @@ export function ensureStateDir(config: BrowseConfig): void {
throw err;
}
// Load-bearing guard: a self-contained ignore INSIDE the state dir so its
// contents can NEVER be `git add`-ed, regardless of the project's own
// .gitignore (which may be absent, or the append below may silently fail).
// The state dir holds session-state.json (live cookies + localStorage/
// sessionStorage tokens) and browse-network.log / browse-audit.jsonl
// (captured request headers can carry bearer tokens). Written unconditionally,
// synchronously, before return — the project-.gitignore dance below is now
// redundant safety, kept so `.gstack/` still reads as ignored in git status.
try {
fs.writeFileSync(path.join(config.stateDir, '.gitignore'), '*\n');
} catch {
// Best-effort; the project-.gitignore path below is the fallback.
}
// Ensure .gstack/ is in the project's .gitignore
// First, check if git already ignores .gstack/ (via global excludes, .git/info/exclude, or parent .gitignore)
if (isIgnoredByGit(config.projectDir, '.gstack/')) return;