Skip to content

Prevent corrupted SMTP headers when MailAddress.DisplayName contains CR/LF - #128980

Open
MihaZupan with Copilot wants to merge 6 commits into
mainfrom
copilot/fix-corrupted-email-headers
Open

Prevent corrupted SMTP headers when MailAddress.DisplayName contains CR/LF#128980
MihaZupan with Copilot wants to merge 6 commits into
mainfrom
copilot/fix-corrupted-email-headers

Conversation

Copilot AI commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

MailAddress accepted display names containing \r/\n and SmtpClient embedded them verbatim into From:/To: headers, breaking header structure and SMTP framing. Typical source: a trailing newline left over from parsing upstream input.

Changes in MailAddress.TryParse (src/libraries/System.Net.Mail/src/System/Net/Mail/MailAddress.cs):

  • Caller-supplied display name: reject CR/LF by throwing FormatException, matching the existing convention for MailMessage.Subject (MailBnfHelper.HasCROrLFMailSubjectInvalidFormat). TryCreate returns false.
  • Display name parsed out of the address string: unfold per RFC 5322 §3.2.4 by stripping CR/LF from the stored value. This preserves the previously-accepted folded input "display \r\n name" <"folding"@domain.com> while ensuring re-encoding produces a clean header.
  • New StripCRLF helper (guarded by an HasCROrLF pre-check, so allocation only occurs when needed).
// Now throws FormatException instead of producing a broken From: header
new MailAddress("user@example.com", "Display Name\r\n");

Test coverage added in MailAddressDisplayNameTest.MailAddress_Ctor_DisplayNameContainsCRLF_Throws for CR, LF, and CRLF in leading/middle/trailing positions and inside bounding quotes, against both the constructor and TryCreate.

Copilot AI requested review from Copilot and removed request for Copilot June 4, 2026 11:51
Copilot AI linked an issue Jun 4, 2026 that may be closed by this pull request
@MihaZupan MihaZupan assigned mrek-msft and unassigned MihaZupan Jun 4, 2026
…TP headers

Co-authored-by: MihaZupan <25307628+MihaZupan@users.noreply.github.com>
Copilot AI requested review from Copilot and removed request for Copilot June 4, 2026 12:21
Copilot AI changed the title [WIP] Fix corrupted email headers due to new line in DisplayName Prevent corrupted SMTP headers when MailAddress.DisplayName contains CR/LF Jun 4, 2026
Copilot AI requested a review from MihaZupan June 4, 2026 12:22
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @karelz, @dotnet/ncl
See info in area-owners.md if you want to be subscribed.

Comment thread src/libraries/System.Net.Mail/src/System/Net/Mail/MailAddress.cs Outdated
…edDisplayName flag

Co-authored-by: mrek-msft <188900745+mrek-msft@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 7, 2026 09:56

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens System.Net.Mail against CR/LF injection/corruption via MailAddress.DisplayName, preventing malformed From:/To: header output when addresses are serialized for SMTP.

Changes:

  • Add CR/LF rejection for caller-supplied displayName in MailAddress.TryParse (throwing for constructors; TryCreate returns false).
  • Strip CR/LF from display names parsed out of the full address string (unfolding) via a new StripCRLF helper.
  • Add unit tests covering CR, LF, and CRLF in caller-supplied display names for both MailAddress constructors and TryCreate.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
src/libraries/System.Net.Mail/src/System/Net/Mail/MailAddress.cs Reject CR/LF in caller-supplied display names; strip CR/LF from parsed display names to avoid header corruption.
src/libraries/System.Net.Mail/tests/Unit/MailAddressTests/MailAddressDisplayNameTest.cs Add unit tests asserting CR/LF in caller-supplied display names throws / fails TryCreate.

Comment on lines +171 to +180
@@ -158,13 +172,34 @@ private static bool TryParse([NotNullWhen(true)] string? address, string? displa
if (string.IsNullOrEmpty(displayName))
{
displayName = info.DisplayName;

// The display name parsed out of 'address' may contain folding white space
// (CRLF inside a quoted string, RFC 5322 §3.2.4). Unfold by stripping any
// CR/LF characters so re-encoding into a header does not corrupt the output.
if (!string.IsNullOrEmpty(displayName) && MailBnfHelper.HasCROrLF(displayName))
{

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot consider fixing on other places or reusing logic. Please take in account recently merged fix #130175.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in f12da96. The branch now aligns with #130175 by rejecting CR/LF at the parser level for address strings (covering ParseMultipleAddresses/MailAddressCollection.Add and MailMessage construction), while keeping the separate rejection for caller-supplied displayName. I also removed the earlier unfold path and updated the affected unit/functional tests accordingly.

Comment on lines +65 to 80
[Theory]
[InlineData("Display\rName")]
[InlineData("Display\nName")]
[InlineData("Display\r\nName")]
[InlineData("DisplayName\r")]
[InlineData("DisplayName\n")]
[InlineData("DisplayName\r\n")]
[InlineData("\rDisplayName")]
[InlineData("\nDisplayName")]
[InlineData("\"Display\r\nName\"")]
public void MailAddress_Ctor_DisplayNameContainsCRLF_Throws(string displayName)
{
Assert.Throws<FormatException>(() => new MailAddress(Address, displayName));
Assert.False(MailAddress.TryCreate(Address, displayName, out _));
}
}
Copilot AI and others added 2 commits July 7, 2026 15:22
Co-authored-by: mrek-msft <188900745+mrek-msft@users.noreply.github.com>
Co-authored-by: mrek-msft <188900745+mrek-msft@users.noreply.github.com>
Copilot AI requested review from Copilot and removed request for Copilot July 7, 2026 15:23
Co-authored-by: mrek-msft <188900745+mrek-msft@users.noreply.github.com>
Copilot AI requested review from Copilot and removed request for Copilot July 7, 2026 15:24
@github-actions

github-actions Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Workflow state for the Holistic Review Orchestrator.

{
  "version": 5,
  "last_dispatched_commit": "f12da9684842c6d245ceaa43a90a394ae6908023",
  "last_dispatched_base_ref": "main",
  "last_dispatched_base_sha": "58ecafbefc41532278f7e98e8accabffa1040a56",
  "last_reviewed_commit": "f12da9684842c6d245ceaa43a90a394ae6908023",
  "last_reviewed_base_ref": "main",
  "last_reviewed_base_sha": "58ecafbefc41532278f7e98e8accabffa1040a56",
  "last_recorded_worker_run_id": "29680697041",
  "review_attempt_commit": "",
  "review_attempt_base_ref": "",
  "review_attempt_count": 0,
  "max_review_attempts": 5,
  "review_history_format": "holistic-review-disclosure-v1",
  "review_history": [
    {
      "commit": "f12da9684842c6d245ceaa43a90a394ae6908023",
      "review_id": 4730523109
    }
  ]
}

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Holistic Review

Motivation: MailAddress accepted display names and address strings containing bare CR/LF characters, which SmtpClient then embedded verbatim into From:/To: headers. This corrupts header structure and enables SMTP header/command injection (CRLF injection). Rejecting these inputs is a well-motivated correctness and security fix, and it aligns MailAddress with the existing MailBnfHelper.HasCROrLF convention already used for MailMessage.Subject, ContentType, and ContentDisposition.

Approach: Two guard points are added. (1) In MailAddress.TryParse, a caller-supplied displayName containing CR/LF now throws FormatException (or returns false from TryCreate). (2) In MailAddressParser.TryParseAddress, the full input string is scanned once for CR/LF on the initial reverse-parse call (guarded by index == data.Length - 1 so ParseMultipleAddresses keeps the scan O(n) overall). Tests are updated to move previously-valid folded inputs into the invalid set and to add focused CR/LF coverage for the constructor, TryCreate, TryParseAddress, and ParseMultipleAddresses.

The implementation is clean and the placement is sensible: the parser-level scan gives a single choke point covering all public entry points, and the HasCROrLF span check is cheap. Test coverage is thorough (leading/middle/trailing/quoted positions, both throwing and non-throwing overloads, and multi-address lists).

Summary: The change is correct and improves security. Two points warrant attention before merge, both non-blocking:

  1. Behavior/breaking change: This is a deliberate behavioral change. Inputs that were previously accepted via RFC 5322 folding — e.g. "display \r\n name" <"folding"@Domain.com>, "test\r\n test"@mail.com, and folded domain literals like testuser@[mail.com \r\n ] — now throw FormatException (the test data moves them from the valid set to the invalid set). Rejecting them is defensible for security, but this is an observable compatibility change for callers that parse already-folded header values. It should be flagged for a breaking-change note / API review, and consider whether unfolding (rather than rejecting) folded whitespace originating from parsed input is preferable for round-tripping scenarios.

  2. PR description is stale relative to the code: The description states the parsed-out display name is unfolded by a new StripCRLF helper that strips CR/LF, preserving folded input. The committed code contains no StripCRLF helper and instead rejects CR/LF at the parser level. The description should be updated to match the actual reject-based implementation to avoid confusion for reviewers and future readers.

No functional defects were found in the diff itself; the logic, guard conditions, and tests are sound. Verdict: approve with the above follow-ups.

Detailed Findings

  • Behavior change surfacing previously-valid folded addresses as errors (MailAddressParser.cs, MailAddressParsingTest.cs): See Summary point 1. Cross-cutting rather than tied to a single changed line; recommend a breaking-change doc entry and confirmation that rejecting (vs. unfolding) is the intended contract.
  • Stale PR description (StripCRLF/unfold vs. reject): See Summary point 2. Documentation-only.

Note

This review was generated by this repository's Holistic Review agentic workflow to complement the built-in Copilot review.

Generated by Holistic Review · 71.6 AIC · ⌖ 10.7 AIC · ⊞ 10K

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Corrupted email headers when DisplayName contains new line

4 participants