Stop a legacy location map from blocking every write to a user document - #186
Merged
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
hasSafePublicLocation was written for the create path and used on the update
path too. On an update `request.resource.data` is the whole POST-WRITE
document, not the changed keys, so its
hasOnly(['city','state','updatedAt']) ran against a `location` map the write
may never have touched.
Documents written before the private-location split still carry precise
coordinates there. saveUserLocation writes with setDoc(..., {merge:true}),
which DEEP-merges nested maps, so the old lat/lng survived the write, failed
the allowlist, and the update was denied. getUserLocation swallows that with
.catch(() => undefined) and AuthContext retries it on every sign-in, so it
failed silently and forever.
The damage was not limited to location. Because the check applied to every
update, it blocked EVERY allowed write to those documents — even
{onboardingComplete: true}. For affected users "Update location" in Settings
failed with a generic toast and onboarding could not complete. That is live
functional breakage, not only a privacy leak. Both were confirmed against the
emulator before this change; the tests here are that check, kept.
The rule now expresses what was actually meant: a client may never introduce
or change lat/lng. It may drop them, and it may leave inherited ones
untouched — which is what lets these documents be written at all again. The
create path keeps the strict whole-document check, where it is correct: on a
create the post-write document IS the write.
This is the rules half only. The residual coordinates are still in those
world-readable documents and still readable by an unauthenticated stranger;
there is a test pinning that rather than implying otherwise. Removing them
needs production writes and is not in this PR.
Also documents settingsTypesOk, which receives the whole post-write document
and deliberately has no hasOnly. That looks like an oversight and is the
opposite: an allowlist there would constrain fields the write never touched
and retro-lock old settings documents — the same trap, one helper away. It
now says so, and points at this fix.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
renrenmimi
force-pushed
the
fix/legacy-location-blocks-writes
branch
from
September 4, 2026 07:16
54e4f54 to
08feeee
Compare
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.
hasSafePublicLocationwas written for the create path and used on the update path too. On an updaterequest.resource.datais the whole post-write document, not the changed keys — so itshasOnly(['city','state','updatedAt'])ran against alocationmap the write may never have touched.Why that broke real accounts
Documents written before the private-location split still carry precise coordinates in the world-readable user doc.
saveUserLocationwrites withsetDoc(..., {merge:true}), and merge deep-merges nested maps — so the oldlat/lngsurvive the write, fail the allowlist, and the update is denied.getUserLocationswallows that with.catch(() => undefined), andAuthContextretries on every sign-in. Silent, and forever.The damage was not limited to location. Because the check applied to every update, it blocked every allowed write to those documents:
Both fail on the current rules and pass on this branch. For affected users, "Update location" in Settings failed with a generic toast and onboarding could not complete. This is live functional breakage, not only a privacy leak.
What the rule says now
A client may never introduce or change
lat/lng. It may drop them, and it may leave inherited ones untouched — that last part is what lets these documents be written at all again.The create path keeps the strict whole-document check, where it is correct: on a create the post-write document is the write.
What this does NOT fix
The residual coordinates are still there and still readable by an unauthenticated stranger. There's a test pinning that exposure rather than letting the PR imply it's closed:
Removing them needs production writes. Not in this PR — waiting on your go-ahead.
scripts/migrate-user-locations-private.tsalready usesbatch.updatewith a whole map, which replaces rather than merges, so it does drop them; it has no record of ever having been run.Also in here: a comment you asked for
settingsTypesOkreceives the whole post-write document and deliberately has nohasOnly. That looks like an oversight and is the opposite — an allowlist there would constrain fields the write never touched and retro-lock old settings documents. Same trap, one helper away. It now says so in the code and points at this fix, so the next person doesn't "finish" it.Tests
tests/rules/users.test.ts, 20 → 26.Two fail on the old rules, pass on these (above). Four pass on both, as guardrails:
Local run: rules 63/63, functions
test:emulator73/73, lint /typecheck:testsclean.Deploy
🤖 Generated with Claude Code