149 lines
6 KiB
JavaScript
149 lines
6 KiB
JavaScript
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`);
|
|
});
|
|
});
|