diff --git a/components/email/__tests__/thread-conversation-view-remote-block.test.tsx b/components/email/__tests__/thread-conversation-view-remote-block.test.tsx
new file mode 100644
index 000000000..5b4ed7ea5
--- /dev/null
+++ b/components/email/__tests__/thread-conversation-view-remote-block.test.tsx
@@ -0,0 +1,54 @@
+// The mobile conversation view built its own sanitizer hook, which caught
+// only
and inline style url(), and a CSP that always allowed
+// http:/https: images - so with remote content blocked (default policy
+// 'ask') srcset, background attributes, posters and still loaded.
+import { describe, expect, it, vi } from 'vitest';
+import { render } from '@testing-library/react';
+
+const t = Object.assign((k: string) => k, { raw: (k: string) => k, rich: (k: string) => k });
+vi.mock('next-intl', () => ({
+ useTranslations: () => t,
+ useLocale: () => 'en',
+}));
+vi.mock('@/hooks/use-wopi-status', () => ({ useWopiStatus: () => ({ enabled: false }), canWopiOpen: () => false }));
+vi.mock('@/hooks/use-own-domain-address', () => ({ useOwnDomainAddress: () => () => false }));
+
+const TRACKERS = ['upper.gif', 'srcset.gif', 'bgattr.gif', 'poster.gif', 'source.gif'];
+const HTML = `
+hello
+
+
+
+
+
+`;
+
+describe('conversation view honours the remote-content block', () => {
+ it('emits no live tracker URL and a strict img-src while blocked', async () => {
+ const { useSettingsStore } = await import('@/stores/settings-store');
+ expect(useSettingsStore.getState().externalContentPolicy).toBe('ask');
+ const { ThreadConversationView } = await import('@/components/email/thread-conversation-view');
+ const email = {
+ id: 'e1', threadId: 't1', mailboxIds: { a: true }, keywords: { $seen: true },
+ from: [{ name: 'Sender', email: 'sender@t.example' }], to: [{ email: 'me@example.org' }],
+ subject: 's', receivedAt: '2026-09-26T10:00:00Z', size: 1, preview: 'hello', hasAttachment: false,
+ htmlBody: [{ partId: '1', type: 'text/html' }], textBody: [], attachments: [],
+ bodyValues: { '1': { value: HTML } },
+ };
+ const { container } = render(
+ {}}
+ />,
+ );
+ const iframe = container.querySelector('iframe');
+ expect(iframe).not.toBeNull();
+ const srcdoc = iframe!.getAttribute('srcdoc') ?? '';
+ // A live attribute, not the inert data-blocked-* copy the blocker keeps.
+ const live = TRACKERS.filter((name) => new RegExp(`\\s(src|srcset|background|poster)="[^"]*${name.replace('.', '\\.')}`, 'i').test(srcdoc));
+ const csp = /Content-Security-Policy" content="([^"]*)"/.exec(srcdoc)?.[1] ?? '';
+ expect(live).toEqual([]);
+ expect(csp).not.toMatch(/img-src[^;]*https?:/);
+ });
+});
diff --git a/components/email/email-viewer.tsx b/components/email/email-viewer.tsx
index 686b1526a..8609d379f 100644
--- a/components/email/email-viewer.tsx
+++ b/components/email/email-viewer.tsx
@@ -4,7 +4,7 @@ import { useState, useEffect, useLayoutEffect, useMemo, useRef, useCallback, use
import { Email, ContactCard, Mailbox } from "@/lib/jmap/types";
import { emailExportFilename, attachmentDownloadFilename, attachmentsBundleFilename, DEFAULT_EMAIL_TEMPLATE, DEFAULT_ATTACHMENT_TEMPLATE } from "@/lib/download-filename";
import { EML_IMPORT_ACCEPT, expandImportableEmails } from "@/lib/eml-import";
-import { applyNewTabToAnchor, escapeHtml, isOpenableLinkHref, plainTextToSafeHtml, sanitizeEmailBodyForIframe, sanitizeEmailHtml, sanitizePlainTextRenderedHtml } from "@/lib/email-sanitization";
+import { applyNewTabToAnchor, emailIframeCsp, escapeHtml, isOpenableLinkHref, plainTextToSafeHtml, sanitizeEmailBodyForIframe, sanitizeEmailHtml, sanitizePlainTextRenderedHtml } from "@/lib/email-sanitization";
import { getRenderableHtmlBody } from "@/lib/email-body-selection";
import { collectReferencedCids, isEmbeddedInBody } from "@/lib/attachment-visibility";
import { collapsePlainTextQuotes, setupQuoteCollapse } from "@/lib/quote-collapse";
@@ -2290,19 +2290,10 @@ export function EmailViewer({
p.MsoNormal, li.MsoNormal, div.MsoNormal { margin: 0 0 6px; }
` : '';
- // Defense-in-depth CSP inside srcDoc. default-src 'none' forbids script
- // execution even if the sanitizer ever lets a