google / google/ground-platform

Security: Data Visibility Policy Bypass via Firestore allow list Rule Exposes Other Users' Submissions

Open
#2,593 0 comments 0 reactions 1 assignee Claimed by @rfontanarosa View on GitHub
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.