An email header is a line-oriented format. Everything up to the first end-of-line is the header field name and value; the next line is a new header. So any user input that reaches a header without newline filtering can, in principle, become a second header. This is the entire vulnerability class, and it predates web forms by decades.
The shape of the attack
A contact form takes a name and puts it into a Subject. If the name is submitted as Hello, the header is Subject: Hello. If it is submitted with an embedded line break followed by Bcc: [email protected], a naive mailer produces two headers, and the message goes to an extra recipient without the sender seeing anything unusual in the UI.
What an attacker gains depends on which header they can reach:
- Additional recipients — turning your transactional mailer into a relay for their traffic, billed to your domain's reputation.
- Replaced
FromorReply-To— redirecting a support reply, or making a phishing message appear to originate from your system. - Injected body — a blank line followed by content separates headers from body in the RFC format, so a newline can terminate the header block early.
- MIME structure changes — adding a
Content-Typeline, changing how the recipient renders everything after it.
Why this is still live in 2026
Most mature mail libraries validate or encode header values, so the naive version is largely closed in the mainstream stacks. The live variants are the ones that skip the library:
- Hand-built raw messages. Concatenating a string to produce an RFC 822 message, then piping it into an SMTP transaction. No library, no validation.
- Template-rendered headers. A subject rendered from a template engine gets the engine's escaping, which is HTML escaping, not newline stripping. Those are different problems.
- Relay and gateway code. Intermediate systems that copy a value from one header to another, or from a query parameter into a header, often do so without re-running validation that the original entry point had.
The third one is why "we use a safe library" is not a complete answer. The safe library protects the calls that go through it.
The one rule that closes it
Reject or strip carriage return and line feed in every value that becomes a header. Concretely: reject \r, \n, and the NUL byte in any user-controlled header value, and reject rather than silently strip for values where the shape matters. Length-cap the result as well, because unbounded header values are their own denial-of-service surface.
Two adjacent details, both from the SMTP spec, matter when you are building messages by hand:
- Line length. RFC 5321 caps a text line at 1000 octets including CRLF, and a command line at 512. A header value that produces a longer line is non-conforming, and some receivers reject it.
- Dot-stuffing. In the DATA phase, a line consisting of a single period terminates the message. Any content line starting with a period must have an extra period prepended on transmission, and removed on receipt. Hand-built messages that skip dot-stuffing can truncate silently at the first line beginning with a dot.
Why a test inbox is the right place to verify
Header injection is one of the few classes of bug where you can verify the exact bytes that left your system, because you control the receiving side. Send a payload containing a newline, then read the raw message and assert on the header set: is there exactly one Subject, is there an unexpected Bcc, did a blank line terminate headers early, is the body what you intended.
Asserting on parsed output is weaker than asserting on raw headers, because a lenient parser may normalize exactly the anomaly you are looking for. The check you want is on the wire format.
The takeaway
A header is a line, so a newline in user input is a header injection. Validate and reject CR, LF and NUL at every point a value becomes a header, including relays and gateways that copy values between headers. Then verify on the raw message rather than the parsed one, because the anomaly you are testing for is exactly what a forgiving parser hides.