feat: decouple person-mention display text from person name (#372) #373
@@ -316,6 +316,40 @@ describe('PersonMentionEditor — disabled state', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ─── Security — XSS in displayName (CWE-79) ──────────────────────────────────
|
||||||
|
|
||||||
|
describe('PersonMentionEditor — XSS resistance', () => {
|
||||||
|
it('renders a malicious displayName as text, not as HTML elements', async () => {
|
||||||
|
// A historical sidecar entry whose displayName contains an HTML payload
|
||||||
|
// that would execute if interpolated as raw HTML. Tiptap's renderHTML
|
||||||
|
// returns the @-prefixed string as the third tuple entry, which
|
||||||
|
// ProseMirror's DOMSerializer treats as a Text node — escaping it.
|
||||||
|
const maliciousMention: PersonMention = {
|
||||||
|
personId: '00000000-0000-0000-0000-000000000001',
|
||||||
|
displayName: '<img src=x onerror=alert(1)>'
|
||||||
|
};
|
||||||
|
|
||||||
|
renderHost({
|
||||||
|
value: '@<img src=x onerror=alert(1)>',
|
||||||
|
mentionedPersons: [maliciousMention]
|
||||||
|
});
|
||||||
|
|
||||||
|
await vi.waitFor(() => {
|
||||||
|
const textbox = document.querySelector('[role="textbox"]') as HTMLElement | null;
|
||||||
|
expect(textbox).not.toBeNull();
|
||||||
|
// No element from the malicious payload should have appeared as a real
|
||||||
|
// DOM node. (Tiptap inserts its own ProseMirror-separator <img> in empty
|
||||||
|
// paragraphs — that is internal markup and never carries user attrs;
|
||||||
|
// guard against the injection by checking the user-controlled attrs.)
|
||||||
|
expect(textbox!.querySelector('img[onerror]')).toBeNull();
|
||||||
|
expect(textbox!.querySelector('img[src="x"]')).toBeNull();
|
||||||
|
expect(textbox!.querySelector('script')).toBeNull();
|
||||||
|
// The payload should appear as visible text content instead.
|
||||||
|
expect(textbox!.textContent ?? '').toContain('<img src=x onerror=alert(1)>');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// ─── Touch target (WCAG 2.2 AA) ──────────────────────────────────────────────
|
// ─── Touch target (WCAG 2.2 AA) ──────────────────────────────────────────────
|
||||||
|
|
||||||
describe('PersonMentionEditor — touch target', () => {
|
describe('PersonMentionEditor — touch target', () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user