Why Go 1.27 UUIDv7 Generates Predictable “7000” Values in Browsers
In Go 1.27 the standard uuid.NewV7() function produces UUIDs with a constant “7000” segment when compiled to WebAssembly, because browsers deliberately reduce high‑precision timers for Spectre‑style mitigations, causing the 12‑bit random field to collapse to zero and dramatically lowering entropy and collision resistance.
UUID v7 implementation in Go 1.27
Go 1.27 adds a native uuid package that implements RFC 9562 UUID v7. The 128‑bit format consists of:
48‑bit unix_ts_ms: millisecond Unix timestamp
4‑bit version field 0111 (hex 7)
12‑bit rand_a: sub‑millisecond fraction or monotonic counter
62‑bit rand_b: cryptographically secure random bits
Observed “7000” pattern in WebAssembly
Running uuid.NewV7() in a browser (GOOS=js GOARCH=wasm) produced UUIDs where the third group is always 7000, e.g.
019ee60f-29b3-7000-a12b-f817e25db8f4 019ee610-29c7-7000-bc34-f04bc09150bb 019ee610-2eb4-7000-884a-dfcad78e47d9Because the 12‑bit rand_a field collapsed to zero, the version nibble (7) followed by three zero nibbles yields the invariant 7000 segment.
Root cause: timer precision throttling
Modern browsers deliberately round high‑resolution timers ( performance.now(), Date.now()) to mitigate Spectre/Meltdown side‑channel attacks. Rounding granularity is typically 2 ms (Firefox) or up to 100 ms when “Resist Fingerprinting” is enabled.
In a WASM module, Go’s time.Now() obtains the time from the host JavaScript environment. The rounded timestamp provides no sub‑millisecond component, so the value used for rand_a is always zero.
Security impact
On systems with full precision, rand_a contributes 12 bits of entropy, raising the effective random portion of a UUID v7 from 62 bits to 74 bits. In browsers the loss of those 12 bits reduces entropy back to 62 bits, increasing collision probability for high‑throughput, distributed generation (e.g., edge workers or client‑side UUID generation).
Go team fix (CL 792820)
The runtime now detects when the wall‑clock cannot populate rand_a (or when the target platform is GOOS=js) and falls back to crypto/rand to generate the missing 12 bits. The fallback also serves as a monotonic counter when many UUIDs are generated within the same millisecond, preserving ordering.
Implementation steps:
Avoid repeated time.Now() probes that would slow NewV7().
If precision is insufficient, use crypto/rand to fill rand_a.
When multiple UUIDs share a millisecond, treat rand_a as a counter to maintain monotonicity.
Reference
Issue discussion: https://github.com/golang/go/issues/80084
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
TonyBai
Tony Bai's tech world (tonybai.com). Not satisfied with just "knowing how", we strive for mastery. Focused on Go language internals, high-quality engineering practices, and cloud‑native architecture, exploring cutting‑edge intersections of Go and AI. Gophers who pursue technology are welcome—follow me and evolve with Go.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
