SNOW-3784415: fix token cache key collisions — versioned SHA256 canonical-JSON key (v2)#1455
Open
sfc-gh-mhofman wants to merge 6 commits into
Open
SNOW-3784415: fix token cache key collisions — versioned SHA256 canonical-JSON key (v2)#1455sfc-gh-mhofman wants to merge 6 commits into
sfc-gh-mhofman wants to merge 6 commits into
Conversation
…ical-JSON key (v2)
Replace the legacy {HOST}:{USERNAME}:{TYPE} cache key with a versioned,
uniformly-hashed canonical-JSON key: SnowflakeTokenCache.v2.<sha256hex>.
The new key includes five dimensions (idp, snowflake, username, role,
token_type) to fix multi-account (shared IdP) and multi-role collisions.
Hashing now happens exactly once when the key is built; the JSON file
backend's secondary hashKey() is removed so both backends and custom
credential managers receive the final key verbatim.
Co-authored-by: Cursor <cursoragent@cursor.com>
…ments Co-authored-by: Cursor <cursoragent@cursor.com>
… imports - Restrict userinfo @ stripping to the URL authority (before the first '/'), so an '@' in the path (e.g. IdP tenant paths) is preserved verbatim — matching the cross-driver reference implementation. - Make scheme stripping case-insensitive (HTTPS://, Http://, etc.). - Add tests for @ in path, mixed userinfo+path-@, and uppercase scheme. - Restore missing authUtil import in four integration test files that still call authUtil.removeFromCache / authUtil.readCache. Co-authored-by: Cursor <cursoragent@cursor.com>
buildCacheKey throws on empty username (per spec), but username is optional for some OAuth authorization-code flows. Guard the key construction so both accessTokenKey and refreshTokenKey are null when username is missing, preserving the original no-cache-without-username behaviour. Guard all four writeToCache call sites to skip the write when the key is null. Co-authored-by: Cursor <cursoragent@cursor.com>
…call sites Apply the same null-username guard to connection.js (connectAsync ID-token and MFA-token reads) and sf.js (post-login MFA/ID-token writes), and auth_idtoken.js (reauthenticate eviction). When username is absent the cache operations are skipped silently, matching the original buildCredentialCacheKey null-return behaviour. Co-authored-by: Cursor <cursoragent@cursor.com>
An empty idp (e.g. from a malformed token-endpoint URL) would silently build a key that ignores the IdP dimension, re-introducing the very cross-IdP collision the v2 design prevents. Treat it as an error symmetrically with the existing snowflake and username guards. Co-authored-by: Cursor <cursoragent@cursor.com>
sfc-gh-mhofman
marked this pull request as ready for review
July 17, 2026 10:34
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
{HOST}:{USERNAME}:{TYPE}token cache key with a versioned, uniformly SHA256-hashed canonical-JSON key (SnowflakeTokenCache.v2.<sha256hex>).idp,snowflake,username,role,token_type) to eliminate multi-account (shared IdP) and multi-role collisions.hashKey()so both OS-keystore and file backends receive the final key verbatim.buildCredentialCacheKey; removebuildOauthAccessTokenCacheKey/buildOauthRefreshTokenCacheKey.Details
New module:
lib/authentication/cache_key_builder.tsnormalizeUrl(url)— strips scheme, userinfo, query/fragment, trailing slash; uppercases.normalizeIdentifier(id)— uppercases outside double-quoted segments; preserves quoted content verbatim.buildCacheKey({ tokenType, idp, snowflake, username, role })— produces canonical JSON with sorted keys, SHA256 hashes it, and returnsSnowflakeTokenCache.v2.<hex>.CacheTokenTypes— canonical string constants forID_TOKEN,MFA_TOKEN,OAUTH_ACCESS_TOKEN,OAUTH_REFRESH_TOKEN.Migrated call sites
auth_oauth_authorization_code.js— access/refresh token keysconnection.js— ID token and MFA token keyssf.js— post-login MFA/ID token write keysauth_idtoken.js— ID token eviction keyTests
cache_key_builder_test.jscovering normalization, golden hash cross-driver parity, edge cases, and validation.json_credential_manager_test.jsto assert verbatim (non-double-hashed) keys.custom_credential_manager_test.jsand all integration/auth tests to use the new API.Test plan
npx mocha test/unit/authentication/cache_key_builder_test.js— 24 passingnpx mocha test/unit/authentication/json_credential_manager_test.js— passingnpx mocha test/unit/authentication/custom_credential_manager_test.js— passing75ff2ad6c41531b3b22ae217b2bf8b2e869bdb8f22a1fe0fcb0640ba76dba8ddMade with Cursor