Fix cache key hash collision: length-prefix extended cache key components#1048
Open
4gust wants to merge 2 commits into
Open
Fix cache key hash collision: length-prefix extended cache key components#10484gust wants to merge 2 commits into
4gust wants to merge 2 commits into
Conversation
…ents
computeExtCacheKeyHash concatenated sorted key+value with no delimiters, so
distinct component sets could serialize to the same string and hash to the
same cache key (e.g. {fmi_path:"value"} and {fmi_pat:"hvalue"} both ->
"fmi_pathvalue"), causing cache-slot collisions and redundant token re-fetches.
Serialize each sorted entry as <utf8ByteLen(key)>:<key><utf8ByteLen(value)>:<value>
(length-prefix/netstring), matching the merged MSAL Go fix so the SDK family is
byte-identical. Uses UTF-8 byte length, not String.length() (UTF-16 units).
Adds injectivity/fuzz, order-independence, UTF-8 byte-length, edge-case, and
cross-SDK golden-vector tests; updates the two hard-coded FmiTest expected
hashes to the new scheme.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: f8d44485-b8f1-489d-98cb-c5148ab413f2
The integration test FmiIT hard-coded the two fmi_path cache-key hashes computed with the old delimiter-less algorithm, so it failed after the length-prefix fix. Update them to the new byte-identical-to-Go values: SomeFmiPath/FmiCredentialPath and SomeFmiPath/Path. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: f8d44485-b8f1-489d-98cb-c5148ab413f2
Avery-Dunn
approved these changes
Jul 22, 2026
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.
Problem
StringHelper.computeExtCacheKeyHash(SortedMap<String,String>)builds the extended cache-key hash by concatenating the sortedkey + valuepairs with no delimiters before hashing. That serialization is not injective, so semantically different component sets can produce the same string and hash to the same cache key.For example, both of these serialize to
"fmi_pathvalue":{ fmi_path: "value" }{ fmi_pat: "hvalue" }The FMI / agent-identity and
client_claimsfeatures feed such component maps into the cache key, so this is a real cache-slot collision: one entry overwrites/evicts another, causing redundant token re-fetches.Fix
Replace the delimiter-less concatenation with a length-prefix (netstring) encoding. For each entry (iterated in the map's sorted-key order) we append:
then SHA-256 → Base64URL (no padding), exactly as before.
str.getBytes(StandardCharsets.UTF_8).length) is used, notString.length()(UTF-16 code units). This keeps it byte-identical to the matching fixes in MSAL Go/.NET/Python/JS, so the whole SDK family converges on the same cache keys.This mirrors the already-merged MSAL Go implementation:
Tests
Added to
FmiTest(directStringHelper.computeExtCacheKeyHashcalls)::,|,\, empty string, 2-byteé, ane+ combining accent sequence, and a 4-byte emoji) — asserts no two distinct component maps collide.{fmi_path:"value"}vs{fmi_pat:"hvalue"}and{a:"b",cd:"e"}vs{ab:"c",d:"e"}hash differently.é(U+00E9) vse+U+0301 must not collide, proving byte length (notString.length()) is used.The two previously hard-coded
SomeFmiPath/...expected hashes inFmiTestwere recomputed for the new scheme.Validated with
mvn -pl msal4j-sdk -am test -Dtest=FmiTest,ClientClaimsTest,UserFederatedIdentityCredentialTest— all pass (25 / 15 / 25, 0 failures).Compatibility note
This changes the cache-key hash for existing FMI /
client_claimscache entries. On upgrade, previously cached entries hash to a different key and are missed once, triggering a single token re-fetch that repopulates the cache under the new key. This is a one-time, self-healing cache miss and matches the behavior of the parallel fixes in the other MSAL SDKs. No public API changes.