Commit 9d5b20e
authored
fix(v4): restrict the first ULID character to [0-7] (#6095)
A ULID is 128 bits in 26 Crockford base32 characters, so the encoding carries two more bits than the value does. Those bits are discarded on decode rather than rejected, which is why `0…`, `8…`, `G…` and `R…` all decode to the same 128-bit value. The spec caps the largest ULID at `7ZZZZZZZZZZZZZZZZZZZZZZZZZ` for that reason, and oklog/ulid added its own `v[0] > '7'` guard after the collision was found in production.
The regex accepted the full alphabet in the first position, so `z.ulid()` validated strings the reference implementation refuses to decode — `ulid.decodeTime("8AAAAAAAAAAAAAAAAAAAAAAAAA")` throws "timestamp too large" on a string that just passed validation. `validator.js` ships the identical `[0-7]` class, and Go's oklog/ulid and python-ulid enforce the same ceiling.
`7ZZZZZZZZZZZZZZZZZZZZZZZZZ` still validates, and a generated ULID does not reach a first character of `1` until the year 3084, so nothing a conforming generator emits is affected. Strings that previously passed and were never ULIDs now fail — that includes every letter first character, not only `8` and `9`, which is what the tests pin.
Note that this changes the `pattern` emitted by `z.toJSONSchema(z.ulid())`.
Zod 3 is in maintenance mode and is intentionally left untouched.1 parent 1cf9cd0 commit 9d5b20e
5 files changed
Lines changed: 11 additions & 5 deletions
File tree
- packages/zod/src/v4
- classic/tests
- core
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
318 | 318 | | |
319 | 319 | | |
320 | 320 | | |
321 | | - | |
| 321 | + | |
322 | 322 | | |
323 | 323 | | |
324 | 324 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
730 | 730 | | |
731 | 731 | | |
732 | 732 | | |
| 733 | + | |
| 734 | + | |
| 735 | + | |
| 736 | + | |
| 737 | + | |
| 738 | + | |
733 | 739 | | |
734 | 740 | | |
735 | 741 | | |
736 | 742 | | |
737 | 743 | | |
738 | 744 | | |
739 | 745 | | |
740 | | - | |
| 746 | + | |
741 | 747 | | |
742 | 748 | | |
743 | 749 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
579 | 579 | | |
580 | 580 | | |
581 | 581 | | |
582 | | - | |
| 582 | + | |
583 | 583 | | |
584 | 584 | | |
585 | 585 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
255 | 255 | | |
256 | 256 | | |
257 | 257 | | |
258 | | - | |
| 258 | + | |
259 | 259 | | |
260 | 260 | | |
261 | 261 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
7 | 7 | | |
8 | 8 | | |
9 | 9 | | |
10 | | - | |
| 10 | + | |
11 | 11 | | |
12 | 12 | | |
13 | 13 | | |
| |||
0 commit comments