mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-10-31 00:12:39 +00:00
fix: sanitise imported and synced HTML templates before the composer previews them
This commit is contained in:
@@ -7,6 +7,7 @@ import { cn } from '@/lib/utils';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { substitutePlaceholders, isBuiltInPlaceholder } from '@/lib/template-utils';
|
||||
import { sanitizeEmailHtml } from '@/lib/email-sanitization';
|
||||
import { useFocusTrap } from '@/hooks/use-focus-trap';
|
||||
import type { EmailTemplate } from '@/lib/template-types';
|
||||
|
||||
@@ -94,7 +95,8 @@ export function PlaceholderFillModal({
|
||||
<div className="mt-4 pt-4 border-t border-border">
|
||||
<p className="text-xs font-medium text-muted-foreground mb-2">{t('preview')}</p>
|
||||
<div className="text-sm text-foreground whitespace-pre-wrap p-3 rounded-md bg-muted/50 border border-border max-h-32 overflow-y-auto">
|
||||
{template.isHTML ? <div dangerouslySetInnerHTML={{ __html: preview }}></div> : preview}
|
||||
{/* The body may come from an imported file or a synced blob; it is markup in the app document here. */}
|
||||
{template.isHTML ? <div dangerouslySetInnerHTML={{ __html: sanitizeEmailHtml(preview) }}></div> : preview}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -221,6 +221,20 @@ describe('importTemplates', () => {
|
||||
expect(result.templates[0].id).not.toBe('test-id');
|
||||
});
|
||||
|
||||
it('strips script and event handlers from an imported HTML body', () => {
|
||||
// The body reaches the composer's placeholder preview as markup in the
|
||||
// app document; it used to be kept exactly as the file had it.
|
||||
const json = JSON.stringify({
|
||||
type: 'webmail-templates',
|
||||
version: 1,
|
||||
templates: [{ name: 'Evil', isHTML: true, body: '<p>Hi {{name}}</p><img src=x onerror="alert(1)"><script>alert(2)</script>' }],
|
||||
});
|
||||
const body = importTemplates(json).templates[0].body;
|
||||
expect(body).toContain('Hi {{name}}');
|
||||
expect(body).not.toContain('onerror');
|
||||
expect(body).not.toContain('<script');
|
||||
});
|
||||
|
||||
it('returns error for invalid JSON', () => {
|
||||
const result = importTemplates('not json');
|
||||
expect(result.templates).toHaveLength(0);
|
||||
|
||||
+13
-2
@@ -1,4 +1,5 @@
|
||||
import DOMPurify from 'dompurify';
|
||||
import { sanitizeEmailHtml } from './email-sanitization';
|
||||
import type { EmailTemplate } from './template-types';
|
||||
import { BUILT_IN_PLACEHOLDERS } from './template-types';
|
||||
import { generateUUID } from './utils';
|
||||
@@ -138,6 +139,16 @@ function sanitizeText(value: unknown): string {
|
||||
return DOMPurify.sanitize(String(value || ''), STRIP_HTML_CONFIG);
|
||||
}
|
||||
|
||||
/**
|
||||
* An HTML template body from outside the app (a templates file, a synced
|
||||
* settings blob written by another client) as the store may hold it. Bodies
|
||||
* the plugin API creates are sanitised at creation; these were kept raw and
|
||||
* reached the composer's placeholder preview as markup in the app document.
|
||||
*/
|
||||
function sanitizeTemplateHtml(value: unknown): string {
|
||||
return sanitizeEmailHtml(String(value || ''));
|
||||
}
|
||||
|
||||
interface ExportData {
|
||||
version: 1;
|
||||
type: 'webmail-templates';
|
||||
@@ -199,7 +210,7 @@ export function parseSyncedTemplates(value: unknown): EmailTemplate[] | null {
|
||||
id: t.id,
|
||||
name: sanitizeText(t.name),
|
||||
subject: sanitizeText(t.subject),
|
||||
body: t.isHTML ? String(t.body || '') : sanitizeText(t.body),
|
||||
body: t.isHTML ? sanitizeTemplateHtml(t.body) : sanitizeText(t.body),
|
||||
isHTML: Boolean(t.isHTML),
|
||||
category: sanitizeText(t.category),
|
||||
defaultRecipients: recipients && typeof recipients === 'object'
|
||||
@@ -277,7 +288,7 @@ export function importTemplates(json: string): ImportResult {
|
||||
id: generateUUID(),
|
||||
name: sanitizeText(t.name),
|
||||
subject: sanitizeText(t.subject),
|
||||
body: t.isHTML ? String(t.body || '') : sanitizeText(t.body),
|
||||
body: t.isHTML ? sanitizeTemplateHtml(t.body) : sanitizeText(t.body),
|
||||
isHTML: Boolean(t.isHTML),
|
||||
category: sanitizeText(t.category),
|
||||
defaultRecipients: recipients && typeof recipients === 'object'
|
||||
|
||||
Reference in New Issue
Block a user