diff --git a/src/browser/network-cache.test.ts b/src/browser/network-cache.test.ts index 88292fb86..c17bbf611 100644 --- a/src/browser/network-cache.test.ts +++ b/src/browser/network-cache.test.ts @@ -73,4 +73,25 @@ describe('network-cache', () => { expect(findEntry(file, 'B')?.key).toBe('B'); expect(findEntry(file, 'missing')).toBeNull(); }); + + it.skipIf(process.platform === 'win32')('writes the cache file with 0o600 owner-only permissions', () => { + saveNetworkCache('ws', [makeEntry('UserTweets')], baseDir); + const target = getCachePath('ws', baseDir); + const mode = fs.statSync(target).mode & 0o777; + expect(mode).toBe(0o600); + }); + + it.skipIf(process.platform === 'win32')('tightens an existing cache file before rewriting it', () => { + const target = getCachePath('ws', baseDir); + fs.mkdirSync(path.dirname(target), { recursive: true }); + fs.writeFileSync(target, '{"version":1,"session":"ws","savedAt":"old","entries":[]}', { mode: 0o644 }); + + saveNetworkCache('ws', [makeEntry('UserTweets')], baseDir); + + const mode = fs.statSync(target).mode & 0o777; + expect(mode).toBe(0o600); + const reloaded = loadNetworkCache('ws', { baseDir }); + expect(reloaded.status).toBe('ok'); + expect(reloaded.file?.entries[0].key).toBe('UserTweets'); + }); }); diff --git a/src/browser/network-cache.ts b/src/browser/network-cache.ts index ca6bba35a..4eb86591a 100644 --- a/src/browser/network-cache.ts +++ b/src/browser/network-cache.ts @@ -63,7 +63,18 @@ export function saveNetworkCache( savedAt: new Date().toISOString(), entries, }; - fs.writeFileSync(target, JSON.stringify(payload), 'utf-8'); + // 0o600: entries can include auth tokens and PII from captured response + // bodies. fchmod before writing also tightens a pre-existing broad file. + let fd: number | undefined; + try { + fd = fs.openSync(target, fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_TRUNC, 0o600); + fs.fchmodSync(fd, 0o600); + fs.writeFileSync(fd, JSON.stringify(payload), 'utf8'); + } finally { + if (fd !== undefined) { + fs.closeSync(fd); + } + } } export interface LoadOptions {