fix: block script-bearing MIME types from inline attachment preview
This commit is contained in:
@@ -81,7 +81,7 @@ import { useTour } from "@/components/tour/tour-provider";
|
|||||||
import { SmimePassphraseDialog } from "@/components/settings/smime-passphrase-dialog";
|
import { SmimePassphraseDialog } from "@/components/settings/smime-passphrase-dialog";
|
||||||
import { findCalendarAttachment, isCalendarMimeType } from "@/lib/calendar-invitation";
|
import { findCalendarAttachment, isCalendarMimeType } from "@/lib/calendar-invitation";
|
||||||
import { RecipientPopover } from "./recipient-popover";
|
import { RecipientPopover } from "./recipient-popover";
|
||||||
import { isFilePreviewable } from "@/lib/file-preview";
|
import { isFilePreviewable, isMimeTypeSafeForInlinePreview } from "@/lib/file-preview";
|
||||||
import { SmimeStatusBanner } from "./smime-status-banner";
|
import { SmimeStatusBanner } from "./smime-status-banner";
|
||||||
import { detectSmime } from "@/lib/smime/smime-detect";
|
import { detectSmime } from "@/lib/smime/smime-detect";
|
||||||
import { smimeDecrypt, SmimeKeyLockedError, normalizeCmsBytes } from "@/lib/smime/smime-decrypt";
|
import { smimeDecrypt, SmimeKeyLockedError, normalizeCmsBytes } from "@/lib/smime/smime-decrypt";
|
||||||
@@ -2535,7 +2535,12 @@ export function EmailViewer({
|
|||||||
|
|
||||||
const handleEffectiveAttachmentOpen = useCallback(async (attachment: EffectiveAttachment) => {
|
const handleEffectiveAttachmentOpen = useCallback(async (attachment: EffectiveAttachment) => {
|
||||||
const isPreviewable = isFilePreviewable(attachment.name || undefined, attachment.type);
|
const isPreviewable = isFilePreviewable(attachment.name || undefined, attachment.type);
|
||||||
const opensPreview = isPreviewable && mailAttachmentAction === 'preview';
|
// Blob URLs inherit our origin; script-bearing MIME types (text/html,
|
||||||
|
// image/svg+xml, etc.) would execute as the webmail origin if opened
|
||||||
|
// top-level. Force the download path for anything not on the inert allowlist.
|
||||||
|
const opensPreview = isPreviewable
|
||||||
|
&& mailAttachmentAction === 'preview'
|
||||||
|
&& isMimeTypeSafeForInlinePreview(attachment.type);
|
||||||
|
|
||||||
const info: AttachmentInfo = {
|
const info: AttachmentInfo = {
|
||||||
name: attachment.name || '',
|
name: attachment.name || '',
|
||||||
|
|||||||
@@ -63,4 +63,24 @@ export function getFilePreviewKind(name?: string, type?: string): FilePreviewKin
|
|||||||
|
|
||||||
export function isFilePreviewable(name?: string, type?: string): boolean {
|
export function isFilePreviewable(name?: string, type?: string): boolean {
|
||||||
return getFilePreviewKind(name, type) !== 'unsupported';
|
return getFilePreviewKind(name, type) !== 'unsupported';
|
||||||
|
}
|
||||||
|
|
||||||
|
const INLINE_PREVIEW_SAFE_MIME_PREFIXES = ['image/', 'audio/', 'video/'];
|
||||||
|
const INLINE_PREVIEW_SAFE_MIME_TYPES = new Set(['application/pdf', 'text/plain']);
|
||||||
|
const INLINE_PREVIEW_UNSAFE_MIME_TYPES = new Set([
|
||||||
|
'image/svg+xml',
|
||||||
|
'image/svg',
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Whether a Blob with this MIME type is safe to open as a top-level navigation
|
||||||
|
// (e.g. window.open on a blob: URL). Blob URLs inherit the creator's origin, so
|
||||||
|
// script-bearing types like text/html, application/xhtml+xml, image/svg+xml, and
|
||||||
|
// XML variants would execute in our origin. Only an explicit allowlist of inert
|
||||||
|
// types is permitted; everything else must be downloaded.
|
||||||
|
export function isMimeTypeSafeForInlinePreview(type?: string): boolean {
|
||||||
|
const mimeType = type?.split(';')[0]?.trim().toLowerCase() || '';
|
||||||
|
if (!mimeType) return false;
|
||||||
|
if (INLINE_PREVIEW_UNSAFE_MIME_TYPES.has(mimeType)) return false;
|
||||||
|
if (INLINE_PREVIEW_SAFE_MIME_TYPES.has(mimeType)) return true;
|
||||||
|
return INLINE_PREVIEW_SAFE_MIME_PREFIXES.some((prefix) => mimeType.startsWith(prefix));
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user