diff --git a/frontend/src/lib/utils/mention.spec.ts b/frontend/src/lib/utils/mention.spec.ts index 4f15bc10..04b659b1 100644 --- a/frontend/src/lib/utils/mention.spec.ts +++ b/frontend/src/lib/utils/mention.spec.ts @@ -24,6 +24,17 @@ describe('escapeHtml', () => { it('escapes ampersand before other entities to avoid double-encoding', () => { expect(escapeHtml('a& { + expect(escapeHtml("d'Artagnan")).toBe('d'Artagnan'); + }); + + it('does not collapse already-encoded entities (re-escapes the &)', () => { + // escapeHtml is idempotent by composition: the second pass re-escapes + // the & that was added by the first. Pin the property so the helper + // can't be "cleverly" optimised to skip it. + expect(escapeHtml('&')).toBe('&amp;'); + }); }); // ─── detectMention ──────────────────────────────────────────────────────────── diff --git a/frontend/src/lib/utils/mention.ts b/frontend/src/lib/utils/mention.ts index c0e0f11b..8b91bc1c 100644 --- a/frontend/src/lib/utils/mention.ts +++ b/frontend/src/lib/utils/mention.ts @@ -45,15 +45,21 @@ export function extractContent( } /** - * Escapes the four HTML-special characters that can break out of text content + * Escapes the five HTML-special characters that can break out of text content * or attribute values. & must be escaped first to avoid double-encoding. + * + * Includes the apostrophe so the helper is safe in single-quoted attribute + * values too — the renderTranscriptionBody anchor template in PR-B2 uses + * double quotes today, but a future template change shouldn't open a + * stored-XSS hole (Sina #5505 action item). */ export function escapeHtml(str: string): string { return str .replaceAll('&', '&') .replaceAll('<', '<') .replaceAll('>', '>') - .replaceAll('"', '"'); + .replaceAll('"', '"') + .replaceAll("'", '''); } /**