Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
Adds runtime UID validation to protect settings account-cache keys from prototype-chain access.
Changes:
- Validates account and URL UIDs as 32-character lowercase hex strings.
- Adds regression coverage for malicious UIDs and session-token pollution.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
packages/fxa-settings/src/lib/cache.ts |
Adds UID validation around account-cache access. |
packages/fxa-settings/src/lib/cache.test.ts |
Tests valid, invalid, and malicious UID behavior. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+92
to
+94
| if (!isValidUid(account.uid)) { | ||
| return undefined; | ||
| } |
## Because - A uid is typed `hexstring`. That is a TypeScript assertion, not a runtime check. The `?uid=` query parameter and the stored `currentAccountUid` both reach `cache.ts` unvalidated. - `cache.ts` uses the uid as a key into the accounts object, a plain object parsed from localStorage. `?uid=constructor` passes the old `all[forceUid]` truthiness test, because `all['constructor']` resolves to `Object`. The value gets persisted. A later `sessionToken(newToken)` call then writes `Object.prototype.sessionToken`. - The scan reported this against `Account.ts`, on `legacyLocalStorageAccount.email = email`. That is where the tainted value enters the object, but the key there is a literal, so it cannot pollute anything. The sink is one call deeper. ## This pull request - Adds `isValidUid()` to `cache.ts`. It checks for 32 hex characters, matching `DEVICE_ID_REGEX` in `fxa-shared`. - Guards the writes: the `?uid=` query parameter, and `account.uid` before it becomes a key. - Guards the reads: `currentAccount()`, `getAccountByUid()`, and the delete in `clearSignedInAccountUid()`. - Adds `currentAccount` and `getAccountByUid` tests to `cache.test.ts`. ## Issue that this pull request solves Closes: https://mozilla-hub.atlassian.net/browse/FXA-12450
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.
Because
legacyLocalStorageAccount.email = emailinAccount.ts. That line is not the sink. Its key is a literal, so it can only ever write a property calledemail. It is where the tainted value enters the object.currentAccount()incache.ts.allis the accounts object parsed from localStorage.uidis only asserted to be ahexstring(as hexstring), never checked at run time.?uid=constructor,all['constructor']is truthy through the prototype chain, soconstructorbecomes the stored account uid.currentAccount()then returns the globalObject, and the next call tosessionToken(newToken)writes the session token onto it.This pull request
isValidUidguard tocache.ts. It tests a uid against/^[0-9a-f]{32}$/.uidquery parameter thatcurrentAccount()reads from the URL.currentAccount()returnundefinedand write nothing when the uid of the given account fails the check. That keeps the existingStoredAccountData | undefinedreturn type, so no caller changes.cache.test.tsfor the rejected uids, for the round trip of a valid uid, and for the?uid=constructorchain above.Issue that this pull request solves
Closes: https://mozilla-hub.atlassian.net/browse/FXA-12450
Checklist
Put an
xin the boxes that applyHow to review (Optional)
currentAccount()inpackages/fxa-settings/src/lib/cache.ts.cache.ts, thencache.test.ts.currentAccount(account).Screenshots (Optional)
No user interface change.
Other information (Optional)
allwithObject.create(null). That makes the whole class of key impossible instead of rejecting one shape. Ask for it if you prefer that trade.getAccountByUid, the finalreturn all[uid]), and the sibling writers of the sameaccountskey instorage-utils.tsandaccount-storage.ts. Say the word and I will file a follow up ticket.npx jest src/lib/cache.test.ts(43 passed, 0 failed) andnpx nx lint fxa-settings(0 errors).