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.
-
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).
-
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
- 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.
- 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.
- 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).
- 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.
Summary
The
idn-emailformat does not validate RFC 5321 §4.1.3 address-literals at all, and theemailformat validates IPv4 address-literals against the wrong grammar (RFC 3986dec-octetinstead of RFC 5321Snum).RFC 6531 (SMTPUTF8) extends only the local part of a mailbox with
UTF8-non-ascii; theaddress-literalproduction is unchanged from RFC 5321, soidn-emailshould accept exactly the same literals thatemaildoes.Reproduction
Schema:
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "format": "idn-email" }Verified with
corvusjson validateDocument(V5 engine, currentmain):"δοκιμή@[192.0.2.1]""δοκιμή@[IPv6:2001:db8::1]""user@[192.0.2.300]""user@[01.0.0.1]"Additional probes showing the passes are coincidental:
"user@[IPv6:zzz]"(idn-email)"user@[]"(idn-email)"user@[01.0.0.1]"(email)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 runtimeCorvus.Text.Json.Validator, and generated standalone evaluators.MatchIdnEmailhas no address-literal branch.MatchEmailhandles[...]domains explicitly (IPv4 /IPv6:literal detection), butMatchIdnEmailsends the domain straight toMatchIdnHostname. The observed behaviour falls out ofMatchDecodedHostnamequirks: 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 (henceuser@[IPv6:zzz]anduser@[]validate).The IPv4 literal check implements the wrong RFC.
MatchEmailvalidates bracket contents withIPAddressParser.IsValidIPV4in canonical mode, i.e. RFC 3986dec-octet— leading zeros forbidden. RFC 5321Snumis1*3DIGITvalued 0–255 — decimal only, leading zeros permitted. The parser's non-canonical mode is not a substitute: it treats a leading0as octal and accepts0xhex and shortened 1–3-part forms, all wrong forSnum.Proposed fix
SnumIPv4 matcher inJsonSchemaEvaluation.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-derivedIPAddressParser/IPv4AddressHelperfiles tracked for upstream review.MatchIdnEmail, mirroringMatchEmailbut hardened: a domain starting with[must be a well-formed literal (ends with], contentsIPv6:+MatchIPV6or theSnummatcher) and must never fall through toMatchIdnHostname.MatchEmail's IPv4 literal branch fromMatchIPV4to theSnummatcher. 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).tests/Corvus.Text.Json.Tests/JsonSchemaMatchingStringTests.cs(MatchEmail_ValidatesEmail/MatchIdnEmail_ValidatesIdnEmaildata rows) covering the four cases above plus the negative probes.Out of scope (adjacent findings)
idn-hostnameformat itself accepts[,:,](e.g."[]"validates) via the sameMatchDecodedHostnamepermissiveness — separate latent bug.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.