Skip to content

idn-email format does not validate RFC 5321 address-literals; email rejects leading-zero Snum octets #904

Description

@mwadams

Summary

The idn-email format does not validate RFC 5321 §4.1.3 address-literals at all, and the email format validates IPv4 address-literals against the wrong grammar (RFC 3986 dec-octet instead of RFC 5321 Snum).

RFC 6531 (SMTPUTF8) extends only the local part of a mailbox with UTF8-non-ascii; the address-literal production is unchanged from RFC 5321, so idn-email should accept exactly the same literals that email does.

Reproduction

Schema:

{
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "format": "idn-email"
}

Verified with corvusjson validateDocument (V5 engine, current main):

Data Expected Actual
"δοκιμή@[192.0.2.1]" valid invalid
"δοκιμή@[IPv6:2001:db8::1]" valid valid (by accident, see below)
"user@[192.0.2.300]" invalid invalid (wrong reason, see below)
"user@[01.0.0.1]" valid invalid

Additional probes showing the passes are coincidental:

Data Expected Actual
"user@[IPv6:zzz]" (idn-email) invalid valid
"user@[]" (idn-email) invalid valid
"user@[01.0.0.1]" (email) valid invalid

Root causes

Both are in src/Corvus.Text.Json/Corvus/Text/Json/JsonSchema/Internal/JsonSchemaEvaluation.String.cs, the single implementation shared by the CLI, the runtime Corvus.Text.Json.Validator, and generated standalone evaluators.

  1. MatchIdnEmail has no address-literal branch. MatchEmail handles [...] domains explicitly (IPv4 / IPv6: literal detection), but MatchIdnEmail sends the domain straight to MatchIdnHostname. The observed behaviour falls out of MatchDecodedHostname quirks: it requires a letter after every dot, so every dotted-decimal literal is rejected; and it does not reject [, :, ] runes, so the dot-free [IPv6:...] string passes with no validation of the address whatsoever (hence user@[IPv6:zzz] and user@[] validate).

  2. The IPv4 literal check implements the wrong RFC. MatchEmail validates bracket contents with IPAddressParser.IsValidIPV4 in canonical mode, i.e. RFC 3986 dec-octet — leading zeros forbidden. RFC 5321 Snum is 1*3DIGIT valued 0–255 — decimal only, leading zeros permitted. The parser's non-canonical mode is not a substitute: it treats a leading 0 as octal and accepts 0x hex and shortened 1–3-part forms, all wrong for Snum.

Proposed fix

  1. Add a dedicated RFC 5321 Snum IPv4 matcher in JsonSchemaEvaluation.String.cs: exactly 4 dot-separated groups of 1–3 ASCII digits, each ≤ 255. Allocation-free span loop; keep it out of the dotnet/runtime-derived IPAddressParser/IPv4AddressHelper files tracked for upstream review.
  2. Add the address-literal branch to MatchIdnEmail, mirroring MatchEmail but hardened: a domain starting with [ must be a well-formed literal (ends with ], contents IPv6: + MatchIPV6 or the Snum matcher) and must never fall through to MatchIdnHostname.
  3. Switch MatchEmail's IPv4 literal branch from MatchIPV4 to the Snum matcher. Existing JSON-Schema-Test-Suite expectations are preserved (joe.bloggs@[127.0.0.1] valid, joe.bloggs@[127.0.0.300] invalid, joe.bloggs@[IPv6:::1] valid).
  4. Failing-first unit tests in tests/Corvus.Text.Json.Tests/JsonSchemaMatchingStringTests.cs (MatchEmail_ValidatesEmail / MatchIdnEmail_ValidatesIdnEmail data rows) covering the four cases above plus the negative probes.

Out of scope (adjacent findings)

  • The idn-hostname format itself accepts [, :, ] (e.g. "[]" validates) via the same MatchDecodedHostname permissiveness — separate latent bug.
  • V4 (src-v4/Corvus.Json.ExtendedTypes/Corvus.Json/Validate.cs, regex + IdnMapping) is an independent implementation and needs its own assessment if equivalent tests land in the test-suite submodule.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions