Repository navigation
fix(runtime): atob returns a binary string, not UTF-8-mangled high bytes (Auth.js/jose session decrypt) - #6673
Merged
proggeramlug merged 1 commit intoJul 19, 2026
Conversation
atob decoded base64 to raw bytes and handed them straight to
js_string_from_bytes, which treats the buffer as UTF-8. Any decoded
byte >= 0x80 is invalid UTF-8 on its own, so charCodeAt then mis-decoded
it: atob("+vv8yIB/") returned char codes [55983, NaN, 127] instead of
the six bytes [250,251,252,200,128,127].
Per the WHATWG spec, atob returns a 'binary string' whose i-th UTF-16
code unit equals decoded byte i (0-255). Map each byte to its Latin-1
code point (U+0000..U+00FF) and encode that as UTF-8 so charCodeAt
recovers the exact byte. This is the precise inverse of btoa, which
already reads char code points and rejects > 0xff.
Found while verifying Auth.js credentials login on a Next.js 16 app:
jose/Auth.js decode a JWE session token's random binary IV, ciphertext,
and HMAC tag through atob (the fallback path taken when
Uint8Array.fromBase64 is unavailable). High bytes silently corrupted,
so the token failed to decrypt on read even though it encrypted and
was written correctly (Node could read Perry's cookie; Perry could
not read its own) -> JWTSessionError -> session never restored. With
this fix the full login round-trip restores the session end to end.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthrough
Changesatob binary string behavior
Estimated code review effort: 2 (Simple) | ~10 minutes 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
atobdecoded base64 to raw bytes and passed them straight tojs_string_from_bytes, which treats the buffer as UTF-8. Any decoded byte ≥0x80is invalid UTF-8 on its own, socharCodeAtthen mis-decoded it:Per the WHATWG spec,
atobreturns a "binary string" whose i-th UTF-16 code unit equals decoded bytei(0-255). The fix maps each byte to its Latin-1 code point (U+0000..U+00FF) and encodes that as UTF-8, socharCodeAtrecovers the exact byte. This is the precise inverse ofbtoa, which already reads char code points and rejects> 0xff.How it was found
Verifying Auth.js credentials login on a Next.js 16 App Router app. The session token is a JWE (jose,
dir/A256CBC-HS512); jose decodes its random binary IV, ciphertext, and HMAC tag throughatob(the fallback taken whenUint8Array.fromBase64is unavailable). High bytes silently corrupted, so the token that Perry encrypted and wrote correctly (Node could decrypt Perry's cookie) failed to decrypt on read (Perry could not read its own cookie) →JWTSessionError→ the session was never restored, so every authenticated page redirected back to login.With this fix the full login round-trip restores the session end-to-end (
session.userpopulated, authenticated routes reached).Verification
atob("+vv8yIB/")→250,251,252,200,128,127; fullbtoa/atobround-trip over all 256 byte values; single high byteatob("gA==")→[128]; ASCII unchanged — all byte-identical tonode --experimental-strip-types.test_gap_atob_binary_high_bytes.ts.A256CBC-HS512encrypt→decrypt round-trip (vianode:crypto, the exact backend calls) now round-trips through the base64 fallback; the real Auth.js login restores the session.Summary by CodeRabbit
Bug Fixes
atobdecoding for binary data containing bytes above0x7F.Tests
0x80and all 256 byte values.