Skip to content

fix: stop leaking raw internal errors to clients in RefreshTokenGrant - #2813

Open
Rakshit-gen wants to merge 1 commit into
supabase:masterfrom
Rakshit-gen:fix/refresh-token-error-message-leak
Open

Rakshit-gen wants to merge 1 commit into
supabase:masterfrom
Rakshit-gen:fix/refresh-token-error-message-leak

Conversation

@Rakshit-gen

Copy link
Copy Markdown

What kind of change does this PR introduce?

Bug fix.

What is the current behavior?

RefreshTokenGrant in internal/tokens/service.go has five places where an internal error is built like this:

return apierrors.NewInternalServerError("%s", err.Error())

This puts the raw Go error text directly into the msg field of the JSON response sent back to the API caller, since HTTPError.Message is tagged json:"msg". It also leaves InternalError nil, so the real cause never makes it into server side logs either, Cause() only returns InternalError when it is set, otherwise it falls back to the same generic looking error.

I checked the rest of the codebase, there are 303 calls to NewInternalServerError, and only these five do this. Everywhere else follows the convention of a generic client facing message plus .WithInternalError(err).

Fixes #2812

What is the new behavior?

All five call sites now use a generic, descriptive message plus .WithInternalError(err), matching the convention used everywhere else in the codebase:

  • Looking up the refresh token (two places, before and inside the transaction)
  • Finding a user's other sessions when SinglePerUser is enabled
  • Finding the currently active refresh token when handling a revoked token
  • Revoking a token family

The client still gets a plain, generic 500 message. The real error is now available through InternalError for logging, the same way it already is for every other internal error in this codebase.

I verified this against a real, freshly migrated Postgres database, not just a mock. I corrupted a session's refresh_token_hmac_key so it can no longer be decoded, then called the refresh grant with that token. Before the fix, the response's msg field is literally "illegal base64 data at input byte 3". After the fix, msg is a generic message and the real decode error is available through InternalError instead.

Additional context

  • internal/tokens/service.go: five call sites updated to stop leaking raw error text into the client facing message.
  • internal/tokens/service_test.go: new regression test TestCorruptHmacKeyDoesNotLeakInternalErrorToClient that corrupts a session's refresh token HMAC key and asserts the client facing message does not contain the raw decode error, and that the real error is available through InternalError.

Test plan

  • go vet ./... passes
  • gofmt -l reports no files
  • go test ./internal/tokens/... -run TestRefreshTokenV2 -v passes, including the new regression test
  • go test ./internal/api/... -run "TestToken|TestRefresh" -v passes with no regressions
  • Manually reproduced the leak against a real migrated Postgres database and confirmed the new test fails on the old code and passes on the new code

RefreshTokenGrant had five places that built the response error as
apierrors.NewInternalServerError("%s", err.Error()), which puts the
raw Go error text directly into the msg field of the response sent
back to the client, and leaves InternalError nil so the real cause
is also lost from server side logs.

Every other call site in the codebase (298 of them) follows the
convention of a generic message plus WithInternalError(err), which
keeps the client facing message safe and the real error available
for logging through Cause(). Bring these five in line with that.

Fixes supabase#2812
@Rakshit-gen
Rakshit-gen requested a review from a team as a code owner September 15, 2026 20:45
@Rakshit-gen

Copy link
Copy Markdown
Author

cc @cemalkilic @hf, tagging you both since you have the most recent commits touching internal/tokens/service.go. Would appreciate a look when you have time.

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.

RefreshTokenGrant leaks raw internal error text to API clients in the msg field

1 participant