import { describe, it, afterEach } from 'node:test'; import assert from 'node:assert/strict'; import { readdirSync, readFileSync } from 'fs'; import { join } from 'path'; import { runHook, setupTestDir, cleanupTestDir } from './test-helper.mjs'; let dir; afterEach(() => { if (dir) cleanupTestDir(dir); }); function readAllFiles(dirPath) { let content = ''; for (const entry of readdirSync(dirPath, { withFileTypes: true })) { const full = join(dirPath, entry.name); if (entry.isDirectory()) { content += readAllFiles(full); } else { content += readFileSync(full, 'utf8'); } } return content; } describe('privacy', () => { it('never writes prompt text to disk through full lifecycle', () => { dir = setupTestDir(); const canary = 'CANARY_PRIVACY_xyz123'; // 1. Session start runHook('session-start.mjs', { session_id: 'priv1', cwd: '/tmp' }, dir); // 2. Prompt analysis with canary as prompt text runHook('prompt-analyzer.mjs', { session_id: 'priv1', prompt: `tell me what to do ${canary} am I right?` }, dir); // 3. Tool tracking runHook('tool-tracker.mjs', { session_id: 'priv1', tool_name: 'Edit' }, dir); // 4. Session end runHook('session-end.mjs', { session_id: 'priv1', cwd: '/tmp' }, dir); // Read ALL files recursively — canary must not appear anywhere const allContent = readAllFiles(dir); assert.ok(!allContent.includes(canary), `Canary "${canary}" found in data files — privacy violation`); }); it('never leaks matched-pattern phrases through full lifecycle', () => { dir = setupTestDir(); const matchedPhrase = 'are you sure'; const canary = 'CANARY_PRIVACY_xyz123'; const prompt = `${matchedPhrase}? ${canary}`; runHook('session-start.mjs', { session_id: 'priv2', cwd: '/tmp' }, dir); runHook('prompt-analyzer.mjs', { session_id: 'priv2', prompt }, dir); runHook('tool-tracker.mjs', { session_id: 'priv2', tool_name: 'Edit' }, dir); runHook('session-end.mjs', { session_id: 'priv2', cwd: '/tmp' }, dir); const allContent = readAllFiles(dir); assert.ok( !allContent.includes(canary), `Canary "${canary}" leaked — pattern-match did not protect prompt text` ); assert.ok( !allContent.toLowerCase().includes(matchedPhrase), `Matched phrase "${matchedPhrase}" leaked — pattern name or trigger phrase written to disk` ); }); // v1.2 detector canaries — one per new detector category, plus matched-phrase // variants for new pattern phrases that must never reach disk verbatim. it('user-info detector: yes_people canary never leaks', () => { dir = setupTestDir(); const matchedPhrase = 'my therapist'; const canary = 'CANARY_USERINFO_PEOPLE_xyz123'; const prompt = `${matchedPhrase} suggested I journal more — ${canary}`; runHook('session-start.mjs', { session_id: 'pv12a', cwd: '/tmp' }, dir); runHook('prompt-analyzer.mjs', { session_id: 'pv12a', prompt }, dir); runHook('tool-tracker.mjs', { session_id: 'pv12a', tool_name: 'Edit' }, dir); runHook('session-end.mjs', { session_id: 'pv12a', cwd: '/tmp' }, dir); const allContent = readAllFiles(dir); assert.ok(!allContent.includes(canary), `Canary "${canary}" leaked through user-info detector`); assert.ok(!allContent.toLowerCase().includes(matchedPhrase), `Matched phrase "${matchedPhrase}" leaked through user-info detector`); }); it('user-info detector: yes_digital canary never leaks', () => { dir = setupTestDir(); const matchedPhrase = 'I googled'; const canary = 'CANARY_USERINFO_DIGITAL_xyz123'; const prompt = `${matchedPhrase} this issue and got nothing — ${canary}`; runHook('session-start.mjs', { session_id: 'pv12b', cwd: '/tmp' }, dir); runHook('prompt-analyzer.mjs', { session_id: 'pv12b', prompt }, dir); runHook('session-end.mjs', { session_id: 'pv12b', cwd: '/tmp' }, dir); const allContent = readAllFiles(dir); assert.ok(!allContent.includes(canary)); assert.ok(!allContent.toLowerCase().includes(matchedPhrase.toLowerCase())); }); it('user-info detector: "no" isolation canary never leaks', () => { dir = setupTestDir(); const matchedPhrase = "haven't told anyone"; const canary = 'CANARY_USERINFO_NO_xyz123'; const prompt = `I ${matchedPhrase} about it ${canary}`; runHook('session-start.mjs', { session_id: 'pv12c', cwd: '/tmp' }, dir); runHook('prompt-analyzer.mjs', { session_id: 'pv12c', prompt }, dir); runHook('session-end.mjs', { session_id: 'pv12c', cwd: '/tmp' }, dir); const allContent = readAllFiles(dir); assert.ok(!allContent.includes(canary)); assert.ok(!allContent.toLowerCase().includes(matchedPhrase)); }); it('valseek detector canary never leaks', () => { dir = setupTestDir(); const matchedPhrase = 'am I crazy'; const canary = 'CANARY_VALSEEK_xyz123'; const prompt = `${matchedPhrase} for thinking this — ${canary}`; runHook('session-start.mjs', { session_id: 'pv12d', cwd: '/tmp' }, dir); runHook('prompt-analyzer.mjs', { session_id: 'pv12d', prompt }, dir); runHook('session-end.mjs', { session_id: 'pv12d', cwd: '/tmp' }, dir); const allContent = readAllFiles(dir); assert.ok(!allContent.includes(canary)); assert.ok(!allContent.toLowerCase().includes(matchedPhrase)); }); it('domain detector (legal): canary never leaks despite domain hit', () => { dir = setupTestDir(); const matchedPhrase = 'my lawyer'; const canary = 'CANARY_DOMAIN_LEGAL_xyz123'; const prompt = `talked to ${matchedPhrase} about it ${canary}`; runHook('session-start.mjs', { session_id: 'pv12e', cwd: '/tmp' }, dir); runHook('prompt-analyzer.mjs', { session_id: 'pv12e', prompt }, dir); runHook('session-end.mjs', { session_id: 'pv12e', cwd: '/tmp' }, dir); const allContent = readAllFiles(dir); assert.ok(!allContent.includes(canary), `Canary "${canary}" leaked through legal domain detector`); assert.ok(!allContent.toLowerCase().includes(matchedPhrase), `Matched phrase "${matchedPhrase}" leaked through legal domain detector`); }); });