Skip to content

fix(smime): never break the header block when folding In-Reply-To/References - #31

Merged
rathlinus merged 1 commit into
bulwarkmail:mainfrom
kah-ja:fix/smime-header-folding
Oct 28, 2026
Merged

rathlinus merged 1 commit into
bulwarkmail:mainfrom
kah-ja:fix/smime-header-folding

Conversation

@kah-ja

@kah-ja kah-ja commented Oct 26, 2026

Copy link
Copy Markdown
Contributor

Summary

Signed replies built by the S/MIME plugin can arrive as raw source at the recipient. formatHeader in smime/src/mime-builder.js folds at 76 characters and takes In-Reply-To/References from the host as-is. When those values carry CR/LF (seen when replying to a message that was itself sent through the plugin), folding can produce empty or whitespace-only lines inside References. An empty line ends the header block, so MIME-Version/Content-Type and everything after it become body text.

The same code path also writes message ids without angle brackets (the host passes bare ids), splits long ids (Outlook's ...@....PROD.OUTLOOK.COM) in the middle, folds long non-ASCII subjects inside an RFC 2047 encoded-word (#7), writes non-ASCII display names as raw 8-bit (#6), generates Message-IDs with @smime.local, and puts only the address into From because onComposeSend ignores fromName.

Changes

  • Collapse CR/LF/TAB in header values before folding.
  • Normalise In-Reply-To/References to <id> lists (strip existing brackets, split on whitespace).
  • Fold at whitespace only and never split a word; an over-long id gets its own line.
  • Encode subjects as several Q encoded-words split on character boundaries, each at most 60 characters, so folded lines stay within 78 characters.
  • RFC 2047-encode non-ASCII display names.
  • Use the sender's domain for the generated Message-ID, falling back to smime.local.
  • smime/src/index.js: take fromName from the compose request into From when the address carries no name.
  • New smime/test/mime-builder.test.mjs.

Related issues

Fixes #6
Fixes #7

Related to #19 (signed attributes for Outlook). Independent of this change; we also run it and recommend merging.

Type of change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update
  • Refactor / code quality improvement
  • Chore / dependency update / CI change

Checklist

  • I have read the Contributing Guide
  • My code follows the project's code style and conventions
  • I have run npm run typecheck && npm run lint and there are no errors (this repo has no typecheck or lint script; ran npm run check instead: ok)
  • The build passes (node scripts/build-isolated.mjs smime)
  • I have tested my changes locally
  • I have added or updated documentation if needed (n/a, no documented behaviour changes)
  • I have updated translations (locales/) if my changes affect user-facing text (n/a, no user-facing text)
  • I have included screenshots or a screen recording for UI changes (n/a, no UI change)

Screenshots / demo

n/a

Notes for reviewers

Testing:

  • New smime/test/mime-builder.test.mjs with 6 cases: 5 fail on main, all pass with this change. The From display-name case passes on both, since the fromName change lives in index.js.
  • npx vitest run smime: 106 passed (100 on main).
  • npm test: 1293 passed, 1 skipped.
  • Running in production: signed replies in a thread, umlauts in subject and recipient name, signature and chain checked with OpenSSL.

On the wire, non-ASCII subjects and display names become encoded-words, In-Reply-To/References carry <id> brackets and the generated Message-ID uses the sender's domain. Runs of whitespace in subjects and display names collapse to one space, and tokens without @ are dropped from In-Reply-To/References. Attachment filename parameters keep the old encodeHeaderValue on purpose, so nothing changes there.

smime/manifest.json and smime.zip are untouched; the release is left to you. npm run build:check reports smime.zip as stale, which it already does on main.

Found during penetration test by turingpoint.

Header values from the host can carry CR/LF; folding them produced empty
lines inside References, which ended the header block so recipients saw
the raw MIME source. Fold at whitespace only, normalise message ids to
<id>, split RFC 2047 words on character boundaries, encode non-ASCII
display names, use the sender's domain for Message-ID and keep the
identity's display name in From.

Fixes #6
Fixes #7
@rathlinus
rathlinus merged commit 9262a03 into bulwarkmail:main Oct 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants