CAN-SPAM §5(a)(1) prohibits false or misleading header information. Header injection (CWE-93, OWASP A03 Injection) occurs when user-supplied values — a display name from a profile, a subject template, a reply-to address — are interpolated into email headers without stripping \r\n\0 characters. A malicious value like Alice\r\nBcc: attacker@evil.com added to the From display name injects a Bcc header, silently copying every email to an attacker-controlled address. This is both a CAN-SPAM violation (falsified headers) and a data exfiltration vector for transactional emails containing sensitive content (receipts, password resets, order confirmations).
Low because exploiting header injection requires a user to control a profile field that reaches an email header, but the potential impact includes bulk email exfiltration and strict-liability CAN-SPAM violations for every affected message.
Sanitize every user-supplied value before interpolating it into an email header field by stripping newlines, carriage returns, and null bytes.
// lib/email.ts — header sanitization
function sanitizeHeader(value: string): string {
return value.replace(/[\r\n\0]/g, ' ').trim().slice(0, 998) // RFC 2822 line limit
}
// Apply before every header value derived from user input
const fromName = sanitizeHeader(user.displayName)
headers['From'] = `${fromName} <${FROM_EMAIL}>`
For Reply-To, only set it when you have a legitimate fixed alternate address (e.g., a support alias on your domain). Never set Reply-To to a value derived from user input without sanitization, and do not set it to a completely different organization's domain.
ID: email-sms-compliance.content-delivery.no-misleading-headers
Severity: low
What to look for: Enumerate every relevant item. CAN-SPAM Section 5(a)(1) prohibits false or misleading header information. This check — distinct from accurate-from which covers domain ownership — specifically examines two failure modes: (1) Header injection: user-supplied values (display name from user profile, subject preferences, reply-to address set by form input) that are interpolated directly into email headers without sanitization. A value like Alice\r\nBcc: attacker@evil.com injected into the From display name would add a Bcc header, redirecting copies of emails without the user's knowledge. (2) Misleading routing: Reply-To set to a domain completely different from From without disclosure, or Return-Path/Sender headers that misrepresent the sending infrastructure. Look at every email-sending call: (a) are any header values derived from user-supplied fields (name, display name, subject template, reply address)? (b) if so, are those values sanitized (stripping \r, \n, \0 characters before inclusion)? (c) are Reply-To, Sender, or custom X-* headers added that could mislead recipients about origin?
Pass criteria: At least 1 of the following conditions is met. No user-supplied values are interpolated into email headers without sanitization (stripping newline characters). Reply-To either matches From or is a legitimate fixed alternate address. No routing headers are manipulated to falsify origin.
Fail criteria: A user-supplied value (display name from user profile, name from form submission, custom subject template field) is string-interpolated into a From, Reply-To, Subject, or other header without stripping \r\n\0 characters — this is a header injection vulnerability. Or: Reply-To is set to a completely different organization's domain with no disclosure.
Skip (N/A) when: The application sends no email.
Detail on fail: Specify the injection vector clearly. Example: "sendEmail() accepts a displayName parameter and interpolates it directly into the From header as '\${displayName} <noreply@example.com>' with no sanitization. A newline in displayName would inject arbitrary headers." or "Reply-To header is set to a different company domain (replies@third-party.com) while From shows the application's domain — misleading routing without disclosure.".
Remediation: Sanitize any user-supplied values before including them in headers:
// Sanitize user-supplied values before placing in email headers
function sanitizeHeader(value: string): string {
// Remove newlines, carriage returns, and null bytes — these are header injection vectors
return value.replace(/[\r\n\0]/g, ' ').trim().slice(0, 998) // RFC 2822 header line limit
}
// Usage:
const fromName = sanitizeHeader(user.displayName)
headers['From'] = `${fromName} <${FROM_EMAIL}>`
For Reply-To, only set it if you have a specific legitimate need (e.g., a support alias). Do not set it to a different company's domain.