Description
The referrals/{uid} update rule in firestore.rules checks that:
- The
usedBy array grows by exactly one element.
- The new element is the current caller's UID.
totalEarned increases by exactly 100.
allow update: if isOwner(uid) || (
isAuthenticated()
&& request.resource.data.usedBy.size() == resource.data.usedBy.size() + 1
&& request.resource.data.usedBy[resource.data.usedBy.size()] == request.auth.uid
&& request.resource.data.totalEarned == resource.data.totalEarned + 100
);
The rule checks that the incoming usedBy array has exactly one more element than the current one, and that the last element matches the caller. However, it does NOT verify that the caller's UID is not already present in usedBy. Firestore arrays allow duplicate values. If usedBy currently contains ["user_A"], user A can submit another update with usedBy: ["user_A", "user_A"], which satisfies:
usedBy.size() == 1 + 1 (true)
usedBy[1] == request.auth.uid (true, because usedBy[1] = "user_A" = request.auth.uid)
totalEarned == current + 100 (true)
This means the same user can trigger the referral reward multiple times against the same referrer, earning 100 points per call without limit.
This vulnerability compounds with RULE 3 in the users/{uid} rules: each successful referrals write also triggers a corresponding users/{referrerUid} update that adds 100 referral points, because RULE 3 only checks the numeric increment, not uniqueness.
Steps to Reproduce
- Sign in as User A. User B refers User A with a referral code.
- User A redeems the referral:
usedBy becomes ["user_A"], totalEarned becomes 100.
- User A immediately sends another write:
usedBy: ["user_A", "user_A"], totalEarned: 200.
- Observe that step 3 succeeds and User B's
referralPoints increase to 200.
- Repeat to farm unlimited referral points for User B.
Expected Behavior
A user's UID should only appear once in usedBy. The rule should verify that the caller's UID is not already in the existing usedBy array before allowing the append.
Actual Behavior
The rule allows the same UID to be appended repeatedly because it only checks array size growth, not UID uniqueness.
Affected Code
firestore.rules, referrals/{uid} update rule:
&& request.resource.data.usedBy.size() == resource.data.usedBy.size() + 1
&& request.resource.data.usedBy[resource.data.usedBy.size()] == request.auth.uid
// missing: && !(request.auth.uid in resource.data.usedBy)
Suggested Fix
Add a uniqueness check:
allow update: if isOwner(uid) || (
isAuthenticated()
&& !(request.auth.uid in resource.data.usedBy) // prevent duplicate entries
&& request.resource.data.usedBy.size() == resource.data.usedBy.size() + 1
&& request.resource.data.usedBy[resource.data.usedBy.size()] == request.auth.uid
&& request.resource.data.totalEarned == resource.data.totalEarned + 100
);
Contribution Guidelines
I would like to work on this under NSoC '26. @indresh404, could you please assign/ this issue to me?
Description
The
referrals/{uid}update rule infirestore.ruleschecks that:usedByarray grows by exactly one element.totalEarnedincreases by exactly 100.The rule checks that the incoming
usedByarray has exactly one more element than the current one, and that the last element matches the caller. However, it does NOT verify that the caller's UID is not already present inusedBy. Firestore arrays allow duplicate values. IfusedBycurrently contains["user_A"], user A can submit another update withusedBy: ["user_A", "user_A"], which satisfies:usedBy.size() == 1 + 1(true)usedBy[1] == request.auth.uid(true, because usedBy[1] = "user_A" = request.auth.uid)totalEarned == current + 100(true)This means the same user can trigger the referral reward multiple times against the same referrer, earning 100 points per call without limit.
This vulnerability compounds with RULE 3 in the
users/{uid}rules: each successfulreferralswrite also triggers a correspondingusers/{referrerUid}update that adds 100 referral points, because RULE 3 only checks the numeric increment, not uniqueness.Steps to Reproduce
usedBybecomes["user_A"],totalEarnedbecomes 100.usedBy: ["user_A", "user_A"],totalEarned: 200.referralPointsincrease to 200.Expected Behavior
A user's UID should only appear once in
usedBy. The rule should verify that the caller's UID is not already in the existingusedByarray before allowing the append.Actual Behavior
The rule allows the same UID to be appended repeatedly because it only checks array size growth, not UID uniqueness.
Affected Code
firestore.rules,referrals/{uid}update rule:Suggested Fix
Add a uniqueness check:
Contribution Guidelines
I would like to work on this under NSoC '26. @indresh404, could you please assign/ this issue to me?