Skip to content

fix(email): Guard reply-one auto-reply against literal null/undefined signature leak - #56

Merged
tylermenezes merged 1 commit into
mainfrom
detail/bug-fix/fix-email-guard-reply-one-auto-reply-against-liter-d4d730
Sep 18, 2026
Merged

tylermenezes merged 1 commit into
mainfrom
detail/bug-fix/fix-email-guard-reply-one-auto-reply-against-liter-d4d730

Conversation

@detail-app

@detail-app detail-app Bot commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

Detail bug report: View on Detail

Bug

The reply-one auto-reply branch in processPostmarkInboundEmail (src/email/postmark.ts) built its HTML body by raw-interpolating project?.event?.emailSignature into a JS template literal. Event.emailSignature is nullable (DB default NULL), the Project → Event relation is optional, and the branch runs before the !project guard — so when the signature is null (or the project was deleted and the lookup returns null), the literal words null / undefined were rendered into the user-visible email body, between the "you forgot to reply-all" line and the quoted reply. The same line hardened the sender name with || 'CodeDay' but left the signature unguarded.

Fix

Append || '' to the signature interpolation, mirroring the || 'CodeDay' guard already on the from field two lines above and matching how every other email template renders the signature (Handlebars triple-stache {{{ event.emailSignature }}}, which also emits empty string for null/undefined). src/email/postmark.ts was the only site that interpolated emailSignature raw into HTML.

Testing

  • Added tests/testPostmarkReplyOneLeak.ts (uses the repo's node:test convention, like tests/testSlackReporting.ts, with typedi mocks): asserts a null signature doesn't leak "null", a deleted project doesn't leak "undefined", and a real signature still renders verbatim. Verified the suite catches the bug — reverting the one-line fix turns 3/3 pass into 1/3 fail (the two leak cases fail).
  • Typecheck (tsc --noEmit --skipLibCheck, the CI gate) is clean.
  • Lint shows only a pre-existing @typescript-eslint/parser deprecation banner that fires identically on untouched files (e.g. tests/testSlackReporting.ts); no new violations from this change.
  • An end-to-end check was also run against a temporary PostgreSQL container (stood up via Docker, schema synced with prisma db push) seeding real Event/Project rows with emailSignature = NULL and a deleted project, invoking the real processPostmarkInboundEmail with only the mail transport mocked: States A and C produced leak-free bodies, the sender name fell back to CodeDay, and a reply-all correctly skipped the auto-reply and persisted a ProjectEmail row. This E2E script was not versioned (it requires a live DB); the committed unit test is the maintained regression guard.

Automatic Fixes PRs can be configured here.

@detail-app
detail-app Bot requested a review from tylermenezes September 18, 2026 02:54
@tylermenezes
tylermenezes merged commit c9a5bae into main Sep 18, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant