diff --git a/lib/services/triage-note-sanitize.test.ts b/lib/services/triage-note-sanitize.test.ts new file mode 100644 index 0000000..b472dea --- /dev/null +++ b/lib/services/triage-note-sanitize.test.ts @@ -0,0 +1,58 @@ +import { describe, it, expect } from 'vitest'; +import { sanitizeUrl, sanitizeNoteText } from './triage-note-sanitize'; + +describe('sanitizeUrl', () => { + it('strips query string and fragment, keeping scheme+host+path', () => { + expect(sanitizeUrl('https://evil.example/login?token=abc123&next=/x#frag')).toBe( + 'https://evil.example/login' + ); + }); + + it('returns exactly the scheme+host+path for a token+fragment URL', () => { + expect(sanitizeUrl('https://evil.example/a?token=abc#f')).toBe('https://evil.example/a'); + }); + + it('does not throw on a malformed/non-URL string and strips any ?/# tail', () => { + expect(() => sanitizeUrl('not a url')).not.toThrow(); + expect(sanitizeUrl('not a url')).toBe('not a url'); + + expect(() => sanitizeUrl('')).not.toThrow(); + + expect(sanitizeUrl('not-a-url?foo=bar#baz')).toBe('not-a-url'); + }); + + it('always strips query even for benign params (not selective)', () => { + expect(sanitizeUrl('https://good.example/path?utm_source=newsletter')).toBe( + 'https://good.example/path' + ); + }); +}); + +describe('sanitizeNoteText', () => { + it('redacts an Authorization/Bearer token', () => { + const input = 'Header seen: Bearer eyJabc.def.ghi in the request'; + const output = sanitizeNoteText(input); + expect(output).not.toContain('eyJabc.def.ghi'); + expect(output).toContain('[REDACTED]'); + }); + + it('redacts credential-style query-param values inside free text', () => { + const input = 'see http://x/y?access_token=SECRET&password=p'; + const output = sanitizeNoteText(input); + expect(output).not.toContain('access_token=SECRET'); + expect(output).not.toContain('password=p'); + }); + + it('preserves a bare sender email and a 64-char hex attachment hash unchanged', () => { + const hash = 'a'.repeat(64); + const input = `Reported by attacker@evil.example with attachment hash ${hash}`; + const output = sanitizeNoteText(input); + expect(output).toContain('attacker@evil.example'); + expect(output).toContain(hash); + }); + + it('is pure and deterministic', () => { + const input = 'plain text with no secrets'; + expect(sanitizeNoteText(input)).toBe(sanitizeNoteText(input)); + }); +});