From c726ed4a0d54454968d70e8131474ae9ca370e09 Mon Sep 17 00:00:00 2001 From: Ritvik Sardana Date: Tue, 9 Jun 2026 12:22:24 +0530 Subject: [PATCH] fix: render safeHTML in toasts --- src/components/Toast/Toast.sanitize.test.ts | 50 +++++++++++++++++++++ src/components/Toast/Toast.test.ts | 5 +++ src/components/Toast/ToastProvider.vue | 9 ++++ src/components/Toast/toast.ts | 40 +++++++++++++---- 4 files changed, 96 insertions(+), 8 deletions(-) create mode 100644 src/components/Toast/Toast.sanitize.test.ts diff --git a/src/components/Toast/Toast.sanitize.test.ts b/src/components/Toast/Toast.sanitize.test.ts new file mode 100644 index 000000000..2c0540d9e --- /dev/null +++ b/src/components/Toast/Toast.sanitize.test.ts @@ -0,0 +1,50 @@ +/** + * @vitest-environment jsdom + * + * Sanitization is verified against the REAL DOMPurify (needs a DOM, hence the + * jsdom environment) — the allow-list wiring is covered separately in + * Toast.test.ts with a stubbed DOMPurify. + */ + +import { describe, it, expect, vi, beforeEach } from 'vitest' +import type { VNode } from 'vue' + +const sonnerSpy = Object.assign(vi.fn(), { + success: vi.fn(), + error: vi.fn(), + warning: vi.fn(), + info: vi.fn(), +}) + +vi.mock('vue-sonner', () => ({ toast: sonnerSpy })) + +const { toast } = await import('./toast') + +// renderSafeHTML returns `() => h('span', { innerHTML })`. Pull that render +// function off the sonner spy, invoke it, and read the sanitized markup back. +function sanitizedHTML(): string { + const [message] = sonnerSpy.success.mock.calls[0]! + const vnode = (message as () => VNode)() + return (vnode.props as { innerHTML: string }).innerHTML +} + +beforeEach(() => sonnerSpy.success.mockClear()) + +describe('Toast v1 — DOMPurify stripping', () => { + it('strips tags outside the allow-list while keeping their text content', () => { + toast.success('safe
nested
') + const html = sanitizedHTML() + expect(html).toContain('safe') + expect(html).not.toContain('
') + expect(html).toContain('nested') + }) + + it('removes script and event-handler payloads to prevent XSS', () => { + toast.success('ok