google / google/ground-platform
Security: Data Visibility Policy Bypass via Firestore allow list Rule Exposes Other Users' Submissions
- Dominant language
- TypeScript
- Stars
- 249
- Forks
- 112
- Avg merge
- 10h 7m
- Merged PRs (30d)
- 4
Description
## Summary
The Firestore security rules enforce per-user data visibility on single document reads (`allow get`) but **not on collection queries** (`allow list`). This allows any authenticated survey participant to retrieve all submissions and locations of interest (LOIs) from all other users, even when the survey's `dataVisibility` setting is configured to restrict users to only their own data.
## Vulnerable Code
In `firestore/firestore.rules`, the survey has a `dataVisibility` setting (field `"9"`) that controls whether data collectors can see each other's data:
```javascript
function canViewDataCollectedByOthers(survey) {
return survey["9"] == 2 /* ALL_SURVEY_PARTICIPANTS */
}
function canViewSubmission(survey, submission) {
return canViewSurvey(survey) &&
(isSubmissionOwner(submission) || canViewDataCollectedByOthers(survey));
}
```
The rules for submissions split `get` and `list` into separate permissions:
```javascript
match /surveys/{surveyId}/submissions/{submissionId} {
// Single document read — correctly enforces data visibility
allow get: if canViewSubmission(getSurvey(surveyId), resource.data);
// Collection query — ONLY checks canViewSurvey(), ignores data visibility
allow list: if canViewSurvey(getSurvey(surveyId));
}
```
The same flaw exists for LOIs:
```javascript
match /surveys/{surveyId}/lois/{loiId} {
allow get: if canViewLoi(getSurvey(surveyId), resource.data);
allow list: if canViewSurvey(getSurvey(surveyId));
}
```
In Firestore, collection queries (e.g., `collection('submissions').get()`) are evaluated against `allow list`, **not** `allow get`. Because `allow list` only checks `canViewSurvey()`, it bypasses the `canViewSubmission()` / `canViewLoi()` ownership check entirely.
## Proof of Concept
Tested on live staging (`ground-dev-sig.web.app`):
- **Survey ID**: `3qEE8P2vdTUWRhlGEyQW`
- **Survey `dataVisibility`**: `1` (= `SURVEY_DATA_COLLECTOR_ONLY`)
- **Test user**: authenticated user with no submissions on this survey
A simple Firestore REST API call to list all submissions:
```
GET https://firestore.googleapis.com/v1/projects/ground-dev-sig/databases/(default)/documents/surveys/3qEE8P2vdTUWRhlGEyQW/submissions?pageSize=20
Authorization: Bearer {TOKEN}
```
**Result**: Returned **7 submissions from 4 different users**, none belonging to the test user. Also returned **6 LOIs from 3 different users**.
Despite `dataVisibility = 1` (users should only see their own data), all other users' submissions and locations were accessible.
## Impact
- Any survey participant can access **all other participants' submissions and GPS locations**, bypassing the survey organizer's intended data visibility policy
- Exposed data includes GPS coordinates of data collection sites, survey answers, photos, and contributor identifiers
- Ground is used for humanitarian aid, farmer mapping, and climate research — this data is sensitive
- The `dataVisibility` setting exists specifically to protect field workers' data from each other
## Suggested Fix
The `allow list` rule should enforce the same ownership check. Since Firestore `list` rules evaluate against query constraints, one approach:
```javascript
// Option 1: Combine into single read rule
match /surveys/{surveyId}/submissions/{submissionId} {
allow read: if canViewSubmission(getSurvey(surveyId), resource.data);
}
// Option 2: Require owner filter in list queries when data visibility is restricted
match /surveys/{surveyId}/submissions/{submissionId} {
allow get: if canViewSubmission(getSurvey(surveyId), resource.data);
allow list: if canViewSurvey(getSurvey(surveyId)) &&
(canViewDataCollectedByOthers(getSurvey(surveyId)) ||
resource.data["5"] == request.auth.uid);
}
```
Contributor guide
Assessment
This issue has not been assessed yet.