Email() uses an unauditable mega-regexp; prefer net/mail #7

Closed
opened 2026-09-07 12:22:51 +00:00 by claude · 0 comments
Member

email.go validates with the well-known ~450-character RFC 5322 regexp from a StackOverflow answer. It's effectively unauditable, and it diverges from the RFC in both directions (rejects valid quoted local parts, accepts some odd domains).

net/mail.ParseAddress is the standard library's address parser and is the better tool for "is this a syntactically valid address".

PR #8 swaps to it:

addr, err := mail.ParseAddress(value)
if err != nil || addr.Name != "" || addr.Address != value {
    return ErrInvalidEmail
}

The Name/Address checks reject display names (Alice <a@b.com>), bare angle brackets, trailing content, and address lists — so only a plain a@b.com passes.

Behaviour change

ParseAddress does not require the domain to contain a dot, so alice@localhost now validates. If a public-looking domain is required, that should be a separate, explicit check (a Match on the domain, or an MX lookup) rather than baked into Email.

The PR also rewrites FuzzEmail, which currently asserts that every input is invalid (it only passes because the seed corpus is empty) — now it checks that Email never returns an unexpected error type, never panics, and gives a stable verdict. Ran clean for 15s of fuzzing locally.

`email.go` validates with the well-known ~450-character RFC 5322 regexp from a StackOverflow answer. It's effectively unauditable, and it diverges from the RFC in both directions (rejects valid quoted local parts, accepts some odd domains). `net/mail.ParseAddress` is the standard library's address parser and is the better tool for "is this a syntactically valid address". **PR #8** swaps to it: ```go addr, err := mail.ParseAddress(value) if err != nil || addr.Name != "" || addr.Address != value { return ErrInvalidEmail } ``` The `Name`/`Address` checks reject display names (`Alice <a@b.com>`), bare angle brackets, trailing content, and address lists — so only a plain `a@b.com` passes. ### Behaviour change `ParseAddress` does **not** require the domain to contain a dot, so `alice@localhost` now validates. If a public-looking domain is required, that should be a separate, explicit check (a `Match` on the domain, or an MX lookup) rather than baked into `Email`. The PR also rewrites `FuzzEmail`, which currently asserts that *every* input is invalid (it only passes because the seed corpus is empty) — now it checks that `Email` never returns an unexpected error type, never panics, and gives a stable verdict. Ran clean for 15s of fuzzing locally.
Sign in to join this conversation.