mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-18 10:52:24 +02:00
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:
co-authored by
Claude Fable 5
parent
57a1d957f4
commit
4992a48c57
@@ -114,6 +114,20 @@ export function ensureStateDir(config: BrowseConfig): void {
|
|||||||
throw err;
|
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
|
// Ensure .gstack/ is in the project's .gitignore
|
||||||
// First, check if git already ignores .gstack/ (via global excludes, .git/info/exclude, or parent .gitignore)
|
// First, check if git already ignores .gstack/ (via global excludes, .git/info/exclude, or parent .gitignore)
|
||||||
if (isIgnoredByGit(config.projectDir, '.gstack/')) return;
|
if (isIgnoredByGit(config.projectDir, '.gstack/')) return;
|
||||||
|
|||||||
@@ -61,6 +61,21 @@ describe('config', () => {
|
|||||||
fs.rmSync(tmpDir, { recursive: true, force: true });
|
fs.rmSync(tmpDir, { recursive: true, force: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('writes a self-contained .gstack/.gitignore with * unconditionally', () => {
|
||||||
|
// Even with NO project .gitignore, the state dir must carry its own
|
||||||
|
// ignore so persisted cookies / network+audit logs can never be git-added.
|
||||||
|
const tmpDir = path.join(os.tmpdir(), `browse-selfignore-test-${Date.now()}`);
|
||||||
|
fs.mkdirSync(tmpDir, { recursive: true });
|
||||||
|
const config = resolveConfig({ BROWSE_STATE_FILE: path.join(tmpDir, '.gstack', 'browse.json') });
|
||||||
|
ensureStateDir(config);
|
||||||
|
const selfIgnore = path.join(config.stateDir, '.gitignore');
|
||||||
|
expect(fs.existsSync(selfIgnore)).toBe(true);
|
||||||
|
expect(fs.readFileSync(selfIgnore, 'utf-8')).toBe('*\n');
|
||||||
|
// No nesting: the ignore is directly inside the state dir, not .gstack/.gstack/.
|
||||||
|
expect(fs.existsSync(path.join(config.stateDir, '.gstack'))).toBe(false);
|
||||||
|
fs.rmSync(tmpDir, { recursive: true, force: true });
|
||||||
|
});
|
||||||
|
|
||||||
test('adds .gstack/ to .gitignore if not present', () => {
|
test('adds .gstack/ to .gitignore if not present', () => {
|
||||||
const tmpDir = path.join(os.tmpdir(), `browse-gitignore-test-${Date.now()}`);
|
const tmpDir = path.join(os.tmpdir(), `browse-gitignore-test-${Date.now()}`);
|
||||||
fs.mkdirSync(tmpDir, { recursive: true });
|
fs.mkdirSync(tmpDir, { recursive: true });
|
||||||
|
|||||||
@@ -42,9 +42,13 @@ const sigtermAfter = process.listenerCount('SIGTERM');
|
|||||||
const uncaughtAfter = process.listenerCount('uncaughtException');
|
const uncaughtAfter = process.listenerCount('uncaughtException');
|
||||||
|
|
||||||
// Check that the gstack home directory wasn't populated as a side effect.
|
// Check that the gstack home directory wasn't populated as a side effect.
|
||||||
|
// A lone \`.gitignore\` (the state-dir ignore guard, contents "*") is expected
|
||||||
|
// and is NOT leaked state — ensureStateDir writes it so persisted cookies/logs
|
||||||
|
// can never be git-committed. Any OTHER entry (browse.json, session-state.json,
|
||||||
|
// logs) would be a real auto-start write and must still fail the guard.
|
||||||
let gstackPopulated = false;
|
let gstackPopulated = false;
|
||||||
try {
|
try {
|
||||||
const entries = fs.readdirSync(${JSON.stringify(tmpGstack)});
|
const entries = fs.readdirSync(${JSON.stringify(tmpGstack)}).filter(e => e !== '.gitignore');
|
||||||
gstackPopulated = entries.length > 0;
|
gstackPopulated = entries.length > 0;
|
||||||
} catch {
|
} catch {
|
||||||
// Doesn't exist — that's the win we want.
|
// Doesn't exist — that's the win we want.
|
||||||
|
|||||||
Reference in New Issue
Block a user