fix(escapeHtml): cover apostrophe to harden single-quoted attribute use
Sina #5505 action item: escapeHtml escaped the four common entities but not the apostrophe. Today every consumer uses double-quoted attributes, but a future renderer change to single quotes would silently open a stored-XSS hole. Cheaper to fix now, with a regression test. Also pin the idempotence-by-composition property: a second call re-escapes the & introduced by the first. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -24,6 +24,17 @@ describe('escapeHtml', () => {
|
||||
it('escapes ampersand before other entities to avoid double-encoding', () => {
|
||||
expect(escapeHtml('a&<b')).toBe('a&<b');
|
||||
});
|
||||
|
||||
it('escapes apostrophe to '', () => {
|
||||
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 ────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -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("'", ''');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user