fix: prevent partial state mutation in Session#refresh#471
fix: prevent partial state mutation in Session#refresh#471gjtorikian merged 1 commit intoseven-sealsfrom
Conversation
Move `decode_jwt` before `@seal_data`/`@cookie_password` assignment so that a malformed access_token from the API doesn't leave the Session object half-updated. Also rescue `JWT::DecodeError` so callers get a `RefreshError` instead of an unhandled exception. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Greptile SummaryThis PR fixes a state-mutation ordering bug in Confidence Score: 4/5Safe to merge; the fix is correct and well-tested, with only a pre-existing P2 rule deviation acknowledged in the PR description. The core fix is logically correct — moving decode_jwt before state mutation prevents half-updated state, and the new rescue prevents an unhandled exception from leaking. The single P2 finding (missing iss/aud claim validation) is a pre-existing, intentional design choice documented in the PR description. No P0 or P1 issues are present. No files require special attention beyond the noted iss/aud validation caveat in lib/workos/session.rb. Important Files Changed
Reviews (1): Last reviewed commit: "fix: prevent partial state mutation in S..." | Re-trigger Greptile |
|
|
||
| # Decode before mutating session state so a malformed access_token | ||
| # doesn't leave the Session half-updated. | ||
| decoded = @manager.decode_jwt(auth_response["access_token"]) |
There was a problem hiding this comment.
iss/aud claims not validated on decode
decode_jwt is called here without validating the iss or aud claims. The PR description explicitly notes this is intentional (sealed cookie already authenticates the data, and all WorkOS SDKs share this behaviour), but the team's engineering rule requires both claims to be verified. If the cross-SDK consistency constraint ever relaxes, the missing validation here would allow a JWT signed for a different audience or issuer to be accepted.
Rule Used: JWTs should always be validated before use and the... (source)
Summary
Follow-up to #470, addressing Greptile's P2 finding about state mutation ordering.
decode_jwtcall before@seal_data/@cookie_passwordassignment, so a malformedaccess_tokenfrom the API doesn't leave theSessionobject half-updated.rescue JWT::DecodeErrorso callers get aRefreshErrorinstead of an unhandled exception.seal_datais unchanged after a failed decode.Re: Greptile's P2 about
iss/audvalidation — intentionally not addressed here. Every WorkOS SDK (Ruby, Python, Node, Go, .NET) deliberately skipsiss/audverification because the JWT lives inside an AES-256-GCM sealed cookie; the unseal step already authenticates the data. Adding claim verification would break cross-SDK consistency without meaningful security benefit.Test plan
test_refresh_returns_error_on_malformed_access_token_without_mutating_state— API returns garbageaccess_token, session state is preserved, caller getsRefreshError🤖 Generated with Claude Code