From fa9119bea74513f9e6279763e0eb27ceb4ae0b70 Mon Sep 17 00:00:00 2001 From: Albrecht Degering Date: Tue, 21 Jul 2026 17:52:02 +0200 Subject: [PATCH] security(webui): block remote mail preview content --- webui/src/components/MessageDisplayPanel.tsx | 98 +++++++++++++++++++- webui/tests/mail-components.test.tsx | 43 ++++++++- 2 files changed, 139 insertions(+), 2 deletions(-) diff --git a/webui/src/components/MessageDisplayPanel.tsx b/webui/src/components/MessageDisplayPanel.tsx index abc1680..b2be77d 100644 --- a/webui/src/components/MessageDisplayPanel.tsx +++ b/webui/src/components/MessageDisplayPanel.tsx @@ -23,6 +23,32 @@ export type MessageDisplayAttachment = { type MessageDisplayBodyMode = "text" | "html"; +const MESSAGE_PREVIEW_CONTENT_SECURITY_POLICY = [ + "default-src 'none'", + "base-uri 'none'", + "connect-src 'none'", + "font-src 'none'", + "form-action 'none'", + "frame-src 'none'", + "img-src data: cid:", + "manifest-src 'none'", + "media-src 'none'", + "object-src 'none'", + "prefetch-src 'none'", + "script-src 'none'", + "style-src 'unsafe-inline'", + "worker-src 'none'" +].join("; "); + +const URL_ATTRIBUTE_PATTERN = /(\s)(src|href|xlink:href|poster|background|action|formaction|cite|longdesc|ping|data)\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+))/gi; +const SOURCE_SET_ATTRIBUTE_PATTERN = /\s(?:srcset|imagesrcset)\s*=\s*(?:"[^"]*"|'[^']*'|[^\s"'=<>`]+)/gi; +const EVENT_HANDLER_ATTRIBUTE_PATTERN = /\son[a-z][\w:-]*\s*=\s*(?:"[^"]*"|'[^']*'|[^\s"'=<>`]+)/gi; +const CSS_IMPORT_PATTERN = /@import\s+(?:url\(\s*(?:"[^"]*"|'[^']*'|[^)]*)\s*\)|"[^"]*"|'[^']*')[^;]*;?/gi; +const CSS_URL_PATTERN = /url\(\s*(?:"([^"]*)"|'([^']*)'|([^)]*))\s*\)/gi; +const UNSAFE_DOCUMENT_ELEMENT_PATTERN = /<\s*(?:base|link|meta)\b[^>]*>/gi; +const SCRIPT_BLOCK_PATTERN = /<\s*script\b[^>]*>[\s\S]*?<\s*\/\s*script\s*>/gi; +const SCRIPT_TAG_PATTERN = /<\s*\/?\s*script\b[^>]*>/gi; + type MessageDisplayPanelProps = { title?: string | null; fields?: MessageDisplayField[]; @@ -71,6 +97,7 @@ export default function MessageDisplayPanel({ const showBodySwitch = hasHtml && hasText; const htmlBodyFallback = translateText("i18n:govoplan-core.p_no_html_body_content_p.c305bfc6"); const textBodyFallback = translateText("i18n:govoplan-core.no_readable_body_content.37643a01"); + const safeHtmlDocument = buildSafeMessageHtmlDocument(bodyHtml || htmlBodyFallback); return (
@@ -104,7 +131,12 @@ export default function MessageDisplayPanel({ }
{activeBodyMode === "html" ? -