From edd11ac27b6d074afcbbf943c23560a15445ab8f Mon Sep 17 00:00:00 2001 From: Aaron Guise Date: Wed, 29 Jul 2026 11:41:34 +1200 Subject: [PATCH] fix: strip from/to names from forward-as-attachment filenames buildForwardAsAttachmentPayload named the synthetic .eml attachment using the user's own emailDownloadTemplate, which by default embeds sender/recipient display names. Since this attachment can go to an external recipient (e.g. an upstream spam gateway, or anyone else), the filename now always renders as "{date}-{subject}.eml" regardless of the user's configured template, while still honoring their space/case/diacritics preferences. --- lib/__tests__/forward-as-attachment.test.ts | 24 +++++++++++++++++++-- lib/forward-as-attachment.ts | 16 ++++++++------ 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/lib/__tests__/forward-as-attachment.test.ts b/lib/__tests__/forward-as-attachment.test.ts index 4dc92cf4..9f98ca28 100644 --- a/lib/__tests__/forward-as-attachment.test.ts +++ b/lib/__tests__/forward-as-attachment.test.ts @@ -1,3 +1,6 @@ +// Pin TZ so the local-time date rendering in the filename test is deterministic. +process.env.TZ = 'UTC'; + import { describe, it, expect } from 'vitest'; import { buildForwardAsAttachmentPayload } from '@/lib/forward-as-attachment'; import type { Email } from '@/lib/jmap/types'; @@ -52,13 +55,30 @@ describe('buildForwardAsAttachmentPayload', () => { expect(payload?.subject).toBe(''); }); - it('honors a custom filename template, matching "Export as .eml" naming instead of always using the default', () => { + it('applies user space/case transforms but ignores a custom filename template, unlike "Export as .eml"', () => { const email = makeEmail({ subject: 'Missed spam example' }); const payload = buildForwardAsAttachmentPayload(email, 'Fwd:', { template: 'custom-{subject}', lowercase: true, spaceReplacement: 'dash', }); - expect(payload?.attachment.name).toBe('custom-missed-spam-example.eml'); + expect(payload?.attachment.name).toBe('2026-07-26-22.25.22-missed-spam-example.eml'); + }); + + it('uses a dash between date and subject by default', () => { + const email = makeEmail({ subject: 'Missed spam example' }); + const payload = buildForwardAsAttachmentPayload(email, 'Fwd:'); + expect(payload?.attachment.name).toBe('2026-07-26 22.25.22-Missed spam example.eml'); + }); + + it('never includes from/to in the filename, even with the default template, to avoid leaking names to the recipient', () => { + const email = makeEmail({ + subject: 'Missed spam example', + from: [{ name: 'Alice Sender', email: 'alice@example.com' }], + to: [{ name: "'Bobby'", email: 'bob@example.com' }], + }); + const payload = buildForwardAsAttachmentPayload(email, 'Fwd:'); + expect(payload?.attachment.name).not.toContain('Alice'); + expect(payload?.attachment.name).not.toContain('Bobby'); }); }); diff --git a/lib/forward-as-attachment.ts b/lib/forward-as-attachment.ts index 42c4bde8..d0feb48d 100644 --- a/lib/forward-as-attachment.ts +++ b/lib/forward-as-attachment.ts @@ -24,12 +24,14 @@ export interface ForwardAsAttachmentPayload { * not per-email, so the same blobId a message already has can be attached * to a brand new outgoing email directly. * - * `filenameOptions`, when passed, should be the same options the caller - * uses for "Export as .eml" / drag-out (the user's configured filename - * template, space/case/diacritics transforms - see - * useSettingsStore's emailDownloadTemplate and friends), so the two - * actions produce consistent filenames for the same message. Falls back - * to emailExportFilename's own default template when omitted. + * `filenameOptions`, when passed, carries the user's configured space/case/ + * diacritics transforms (see useSettingsStore's filenameSpaceReplacement + * and friends) for consistency with "Export as .eml" / drag-out. Its + * `template`, if any, is ignored: this attachment goes out to a possibly + * external recipient (spam gateway, another person), so the filename is + * always just "{date}-{subject}.eml" - never the user's own from/to naming + * template, which could otherwise leak sender/recipient names into an + * attachment filename visible to that recipient. * * Returns null when the email has no blobId (nothing to reference). */ @@ -48,7 +50,7 @@ export function buildForwardAsAttachmentPayload( subject: email.subject ? buildForwardSubject(email.subject, forwardPrefix) : "", attachment: { blobId: email.blobId, - name: emailExportFilename(email, filenameOptions), + name: emailExportFilename(email, { ...filenameOptions, template: "{date}-{subject}" }), type: "message/rfc822", size: email.size, },