import assert from 'node:assert/strict'; import test from 'node:test'; import { LOG_EXPORT_REDACTION_RULES, redactLogExportText } from './log-redaction'; test('log export redaction rules expose auditable fixtures', () => { assert.deepEqual( LOG_EXPORT_REDACTION_RULES.map((rule) => rule.name), [ 'signed-youtube-media-url-query', 'url-sensitive-components', 'sensitive-headers', 'yt-dlp-cookie-args', 'json-secret-values', 'generic-sensitive-key-values', 'home-path-usernames', 'email-addresses', 'ip-addresses', ], ); const names = new Set(); for (const rule of LOG_EXPORT_REDACTION_RULES) { assert.equal(names.has(rule.name), false, `duplicate rule name: ${rule.name}`); names.add(rule.name); assert.ok(rule.pattern.length > 0, `${rule.name} pattern`); assert.ok(rule.replacement.length > 0, `${rule.name} replacement`); assert.ok(rule.risk.length > 0, `${rule.name} risk`); assert.ok(rule.examples.length > 0, `${rule.name} examples`); for (const example of rule.examples) { assert.equal(rule.redact(example.input), example.expected, `${rule.name} direct`); assert.equal(redactLogExportText(example.input), example.expected, rule.name); } } }); test('redactLogExportText redacts parsed URL query values without swallowing punctuation', () => { const masked = redactLogExportText( [ 'failed URL (https://example.test/watch?access_token=tok123).', 'callback subminer://anilist-setup?access_token=ani-token&state=ok', 'encoded https://example.test/watch?client%5Fsecret=secret-value&plain=ok!', ].join('\n'), ); assert.match(masked, /\(https:\/\/example\.test\/watch\?access_token=\)\./); assert.match(masked, /subminer:\/\/anilist-setup\?access_token=&state=ok/); assert.match(masked, /client%5Fsecret=&plain=ok!/); assert.doesNotMatch(masked, /tok123/); assert.doesNotMatch(masked, /ani-token/); assert.doesNotMatch(masked, /secret-value/); }); test('redactLogExportText redacts URL credentials containing at signs', () => { const masked = redactLogExportText('GET https://alice:p@ss@example.test/watch?state=ok'); assert.equal(masked, 'GET https://@example.test/watch?state=ok'); assert.doesNotMatch(masked, /alice/); assert.doesNotMatch(masked, /p@ss/); }); test('redactLogExportText redacts signed YouTube media URL query strings', () => { const masked = redactLogExportText( [ 'ffmpeg failed for (https://rr1---sn-a5mekn6r.googlevideo.com/videoplayback?expire=1777777777&ei=EI_VALUE&id=o-SECRETID&itag=251&ip=203.0.113.42&source=youtube&requiressl=yes&n=NSECRET&sparams=expire,ei,ip,id,itag,source,requiressl&sig=SIGSECRET&lsig=LSIGSECRET).', 'manifest https://manifest.googlevideo.com/videoplayback?expire=1777777777&signature=SIG2#frag', ].join('\n'), ); assert.match( masked, /\(https:\/\/rr1---sn-a5mekn6r\.googlevideo\.com\/videoplayback\?\)\./, ); assert.match(masked, /https:\/\/manifest\.googlevideo\.com\/videoplayback\?#frag/); assert.doesNotMatch(masked, /1777777777/); assert.doesNotMatch(masked, /EI_VALUE/); assert.doesNotMatch(masked, /o-SECRETID/); assert.doesNotMatch(masked, /203\.0\.113\.42/); assert.doesNotMatch(masked, /NSECRET/); assert.doesNotMatch(masked, /SIGSECRET/); assert.doesNotMatch(masked, /LSIGSECRET/); }); test('redactLogExportText redacts nested JSON secret values', () => { const masked = redactLogExportText( 'json {"nested":{"token":123,"safe":"ok"},"items":[{"password":true},{"client_secret":null}]}', ); assert.match(masked, /"token":""/); assert.match(masked, /"safe":"ok"/); assert.match(masked, /"password":""/); assert.match(masked, /"client_secret":""/); assert.doesNotMatch(masked, /123/); assert.doesNotMatch(masked, /true/); assert.doesNotMatch(masked, /null/); }); test('redactLogExportText redacts compound secret keys', () => { const masked = redactLogExportText( [ 'json {"openaiApiKey":"sk-secret","google_api_key":"AIza-secret","userPassword":"hunter2","safe":"ok"}', 'openaiApiKey=sk-inline google_api_key=AIza-inline userPassword=hunter-inline', ].join('\n'), ); assert.match(masked, /"openaiApiKey":""/); assert.match(masked, /"google_api_key":""/); assert.match(masked, /"userPassword":""/); assert.match(masked, /"safe":"ok"/); assert.match(masked, /openaiApiKey=/); assert.match(masked, /google_api_key=/); assert.match(masked, /userPassword=/); assert.doesNotMatch(masked, /sk-secret|AIza-secret|hunter2/); assert.doesNotMatch(masked, /sk-inline|AIza-inline|hunter-inline/); }); test('redactLogExportText redacts quoted secret values containing the opposite quote', () => { const masked = redactLogExportText(`"password":"pa'ss" 'token':'to"ken' "safe":"pa'ss"`); assert.equal(masked, `"password":"" 'token':'' "safe":"pa'ss"`); }); test('redactLogExportText redacts IPv6 addresses with zone identifiers', () => { const masked = redactLogExportText('connected [fe80::1%en0]:443 and fe80::2%eth0'); assert.equal(masked, 'connected []:443 and '); }); test('redactLogExportText keeps malformed JSON scans bounded', () => { const malformedLine = `${'{'.repeat(70 * 1024)} token=secret`; const masked = redactLogExportText(`${malformedLine}\njson {"token":"secret","safe":"ok"}`); assert.match(masked, /token=/); assert.match(masked, /json {"token":"","safe":"ok"}/); assert.doesNotMatch(masked, /token=secret/); assert.doesNotMatch(masked, /"token":"secret"/); });