Skip to content

SNOW-3784415: fix token cache key collisions — versioned SHA256 canonical-JSON key (v2)#1455

Open
sfc-gh-mhofman wants to merge 6 commits into
masterfrom
michal.hofman/SNOW-3784415-token-cache-key-v2
Open

SNOW-3784415: fix token cache key collisions — versioned SHA256 canonical-JSON key (v2)#1455
sfc-gh-mhofman wants to merge 6 commits into
masterfrom
michal.hofman/SNOW-3784415-token-cache-key-v2

Conversation

@sfc-gh-mhofman

Copy link
Copy Markdown
Collaborator

Summary

  • Replace the legacy {HOST}:{USERNAME}:{TYPE} token cache key with a versioned, uniformly SHA256-hashed canonical-JSON key (SnowflakeTokenCache.v2.<sha256hex>).
  • The new key includes five dimensions (idp, snowflake, username, role, token_type) to eliminate multi-account (shared IdP) and multi-role collisions.
  • Remove the JSON file backend's secondary hashKey() so both OS-keystore and file backends receive the final key verbatim.
  • Deprecate buildCredentialCacheKey; remove buildOauthAccessTokenCacheKey / buildOauthRefreshTokenCacheKey.

Details

New module: lib/authentication/cache_key_builder.ts

  • normalizeUrl(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 returns SnowflakeTokenCache.v2.<hex>.
  • CacheTokenTypes — canonical string constants for ID_TOKEN, MFA_TOKEN, OAUTH_ACCESS_TOKEN, OAUTH_REFRESH_TOKEN.

Migrated call sites

  • auth_oauth_authorization_code.js — access/refresh token keys
  • connection.js — ID token and MFA token keys
  • sf.js — post-login MFA/ID token write keys
  • auth_idtoken.js — ID token eviction key

Tests

  • 24 unit tests in cache_key_builder_test.js covering normalization, golden hash cross-driver parity, edge cases, and validation.
  • Updated json_credential_manager_test.js to assert verbatim (non-double-hashed) keys.
  • Updated custom_credential_manager_test.js and all integration/auth tests to use the new API.

Test plan

  • npx mocha test/unit/authentication/cache_key_builder_test.js — 24 passing
  • npx mocha test/unit/authentication/json_credential_manager_test.js — passing
  • npx mocha test/unit/authentication/custom_credential_manager_test.js — passing
  • Golden hash matches cross-driver spec: 75ff2ad6c41531b3b22ae217b2bf8b2e869bdb8f22a1fe0fcb0640ba76dba8dd
  • CI integration tests (OAuth, external browser, MFA)

Made with Cursor

sfc-gh-mhofman and others added 6 commits July 15, 2026 15:17
…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
sfc-gh-mhofman marked this pull request as ready for review July 17, 2026 10:34
@sfc-gh-mhofman
sfc-gh-mhofman requested a review from a team as a code owner July 17, 2026 10:34
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.

1 participant