@@ -73,15 +73,24 @@ service cloud.firestore {
7373 )
7474 );
7575
76- // RULE 3: Referrer update - Allow another user to atomically increment a referrer's referralPoints by exactly 100 points
77- // as part of the onboarding referral transaction.
78- allow update : if isAuthenticated ()
79- && request .auth.uid != uid // must be the referred user performing the write
76+ // RULE 3: Referrer update - Allow the referred user to atomically increment a referrer's
77+ // referralPoints by exactly 100 points as part of the onboarding referral transaction.
78+ // Two additional guards close the exploit described in issue #81:
79+ // 1. exists(...) confirms the referrals index document for the target user exists,
80+ // which is only created when the referrer generates a referral code.
81+ // 2. request.auth.uid in get(...).data.usedBy confirms the caller is already recorded
82+ // in the referrer's usedBy list, meaning they legitimately redeemed the referral
83+ // code before this point update fires. This prevents any arbitrary authenticated
84+ // user from granting unlimited referral points to any other user.
85+ allow update : if isAuthenticated ()
86+ && request .auth.uid != uid
8087 && request .resource.data.points.referralPoints == resource.data.points.referralPoints + 100
8188 && request .resource.data.points.totalPoints == resource.data.points.totalPoints + 100
8289 && request .resource.data.points.gitRankPoints == resource.data.points.gitRankPoints
8390 && request .resource.data.points.codingVersePoints == resource.data.points.codingVersePoints
84- && request .resource.data.points.streakPoints == resource.data.points.streakPoints ;
91+ && request .resource.data.points.streakPoints == resource.data.points.streakPoints
92+ && exists (/ databases/ $(database )/ documents/ referrals/ $(uid ))
93+ && request .auth.uid in get (/ databases/ $(database )/ documents/ referrals/ $(uid )).data.usedBy ;
8594 }
8695
8796 match / referrals/ {uid } {
0 commit comments