You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Iban accepts an invalid IBAN ending in "\n" and rejects a valid one with the same newline (regex $ matches before a trailing newline) #311
IsIbanAttribute's validator checks ^[A-Z]{2}[0-9]{2}[A-Z0-9]+$ (Semantics.Strings.Identifiers/IsIbanAttribute.cs:24). In .NET, $ without RegexOptions.Multiline also matches just before a final \n, so a value with a trailing newline passes the structural check.
Iban.MakeCanonical (Semantics.Strings.Identifiers/Iban.cs:21) strips only ' ', so the newline reaches the validator. The length gate (15–34) allows the extra character. PassesMod97 then feeds '\n' through ApplyChar as if it were a letter: c - 'A' + 10 gives −45. Whether the value passes then depends on the check digits, not on whether the IBAN is valid.
Reproduction (run against the built assembly)
Iban.Create<Iban>("GB67WEST12345698765432\n") -> ACCEPTED, stored as "GB67WEST12345698765432\n" (length 23)
Iban.Create<Iban>("GB67WEST12345698765432") -> ArgumentException (GB67 is not a valid IBAN)
Iban.Create<Iban>("GB82WEST12345698765432") -> ACCEPTED (the valid reference IBAN)
Iban.Create<Iban>("GB82WEST12345698765432\n") -> ArgumentException (valid IBAN rejected)
Why it matters
A value with wrong check digits validates, and its stored canonical form contains a control character.
A valid IBAN pasted with a trailing newline, which is common from text areas and files, is rejected with a misleading "mod-97 checksum failed".
The identifiers spec (docs/superpowers/specs/2026-07-01-concrete-semantic-strings-design.md) says canonicalization strips whitespace, but only spaces are stripped.
The same $ anchor appears in IsUuidAttribute.cs:23 and IsUlidAttribute.cs:23. Uuid and Ulid are protected today only because their MakeCanonical calls Trim(). The attributes are public, so a consumer type that uses them without trimming would inherit the hole.
Suggested fix / acceptance criteria
Anchor the identifier patterns with \z (or \A…\z) instead of $.
Make Iban.MakeCanonical strip all whitespace (char.IsWhiteSpace), as the spec says, so that "GB82 WEST 1234 5698 7654 32\n" canonicalizes to GB82WEST12345698765432 and validates.
Tests:
an invalid IBAN with a trailing \n is rejected;
a valid IBAN with a trailing \n or \t canonicalizes and validates;
[IsUuid] / [IsUlid] on a type that doesn't trim reject a trailing \n.
Priority: Medium. A validated semantic string accepts an IBAN whose check digits are wrong, and stores a control character in its canonical form. A valid IBAN pasted with a trailing newline is rejected with a misleading checksum error. The $ anchor is also in the public [IsUuid] and [IsUlid] attributes.
Area / suggested assignment:Semantics.Strings.Identifiers (IsIbanAttribute, Iban.MakeCanonical, IsUuidAttribute, IsUlidAttribute). @matt-edmondson
Notes: It's worth grepping the whole repository for $" and $' regex anchors in validation attributes while making this fix, so the \z change is applied consistently rather than to these three files only.
Activity
matt-edmondson commentedon Sep 28, 2026
Triage
$anchor is also in the public[IsUuid]and[IsUlid]attributes.Semantics.Strings.Identifiers(IsIbanAttribute,Iban.MakeCanonical,IsUuidAttribute,IsUlidAttribute). @matt-edmondsonNotes: It's worth grepping the whole repository for
$"and$'regex anchors in validation attributes while making this fix, so the\zchange is applied consistently rather than to these three files only.Generated by Claude Code