Skip to content

fix: validate base64-prefixed chunked cookies decode to valid JSON#210

Merged
mandarini merged 1 commit into
mainfrom
fix/chunked-cookie-json-validation
May 7, 2026
Merged

fix: validate base64-prefixed chunked cookies decode to valid JSON#210
mandarini merged 1 commit into
mainfrom
fix/chunked-cookie-json-validation

Conversation

@mandarini
Copy link
Copy Markdown
Contributor

@mandarini mandarini commented May 4, 2026

Summary

Adds defensive validation to chunked cookie decoding so corrupted or mid-write cookie state no longer propagates a malformed string into @supabase/auth-js. When a value is read via the chunked cookie path and carries the base64- prefix written by this module, the decoded payload is now verified to be valid JSON (which it always must be, since setItemAsync in auth-js writes via JSON.stringify). On decode or parse failure we log a console warning and return null, signalling the entry is absent so the SDK can recover cleanly instead of crashing or re-saving garbage.

What changed

  • New decodeChunkedCookieValue helper in src/cookies.ts, used by both the browser and server getItem paths inside createStorageFromOptions.
  • The helper:
    • Returns the value unchanged when there is no base64- prefix (raw cookies and PKCE code verifier paths are unaffected).
    • Catches throws from stringFromBase64URL and warns: "could not base64url-decode chunked cookie value..."
    • Validates the decoded value parses as JSON; on failure warns: "chunked cookie decoded to invalid JSON..."
    • Returns null in either failure case.
  • Both getItem paths in createStorageFromOptions now delegate to this helper.
  • Four new tests in src/cookies.spec.ts covering: invalid JSON in decoded chunks, valid JSON in decoded chunks, undecodable base64url payload, and that raw (non-base64) cookies are passed through without JSON validation.

Why

Reported in #169 with a complete root cause analysis from the original reporter. When a session is large enough to span 3+ cookie chunks (sb-...auth-token.0/.1/.2/...) and a server side refresh writes only some of the new chunks (e.g. response committed before all Set-Cookie headers go out, partial set/remove during SSR, browser racing concurrent navigations), the browser ends up holding chunks from two different generations. combineChunks joins them blindly and there are two failure modes downstream:

  1. Combined bytes happen to all fall in the base64url alphabet. stringFromBase64URL succeeds, but the decoded result is garbage that fails JSON.parse later in auth-js. Before the auth-js companion fix, this produced a TypeError: Cannot create property 'user' on string ... in _recoverAndRefresh and embedded the access token JSON in the error message (token leaked into application error logs).
  2. Combined bytes contain a character that is invalid in base64url, e.g. a . from a raw JWT segment that landed in a chunk. stringFromBase64URL throws synchronously: Invalid Base64-URL character "." at position N. This surfaces as an unhandled rejection in production and was reported separately by another customer hitting the same root cause class.

This change catches both failure modes at the source so they never reach auth-js. The corresponding auth-js change (supabase/supabase-js#2312) is the primary fix for the user visible crash from variant 1; this PR is defense in depth and is the actual fix for variant 2.

Notes for reviewers

  • The JSON validation is intentionally narrow: it only runs when the value carries the base64- prefix that this module itself writes. Raw cookies (cookieEncoding: "raw"), and any cookie value that does not carry the prefix, are returned unchanged. This keeps the change safe for users storing non-JSON values via the storage interface and for the PKCE code verifier path.
  • The helper is reused by both the browser and server getItem. The server path retained its existing typeof chunkedCookie !== "string" defensive shortcut for cases where combineChunks might return a non-string at runtime.

Tracking

@mandarini mandarini linked an issue May 4, 2026 that may be closed by this pull request
2 tasks
@mandarini mandarini self-assigned this May 4, 2026
@mandarini mandarini merged commit 302cc0e into main May 7, 2026
8 checks passed
@mandarini mandarini deleted the fix/chunked-cookie-json-validation branch May 7, 2026 07:58
mandarini pushed a commit that referenced this pull request May 7, 2026
🤖 I have created a release *beep* *boop*
---


## [0.10.3](v0.10.2...v0.10.3)
(2026-05-07)


### Bug Fixes

* allow cookies encode without getAll/setAll on browser client
([#213](#213))
([89f3f28](89f3f28)),
closes [#170](#170)
* enable tree-shaking for browser bundles
([#216](#216))
([f009d71](f009d71))
* **tsconfig:** set explicit rootDir to silence TS6059 in consumer IDEs
([#211](#211))
([a77ee8a](a77ee8a)),
closes [#209](#209)
* validate base64-prefixed chunked cookies decode to valid JSON
([#210](#210))
([302cc0e](302cc0e))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: supabase-releaser[bot] <223506987+supabase-releaser[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

TypeError in _recoverAndRefresh when large session cookie chunks become corrupted Invalid Base64 characters in auth-token crashes server

2 participants