Skip to content

fix: prevent sybil bypass via delete then re claim and fix status code mismatch - #25

Open
Sertug17 wants to merge 1 commit into
base:masterfrom
Sertug17:fix/sybil-protection-bypass
Open

fix: prevent sybil bypass via delete then re claim and fix status code mismatch#25
Sertug17 wants to merge 1 commit into
base:masterfrom
Sertug17:fix/sybil-protection-bypass

Conversation

@Sertug17

@Sertug17 Sertug17 commented Jun 14, 2026

Copy link
Copy Markdown
There was a bug in the airdrop claim flow someone could delete their claim via delete-airdrop, then re-claim with a different wallet using the same social account. The delete was removing the verification token entirely, so the re verification check had nothing to find.

Added a DeletedToken model. When someone deletes their claim, the token gets saved there instead of being thrown away. On re verification, we check both the active records and the deleted token table.

Also fixed a smaller issue: the frontend was checking for HTTP 400 but the backend returns 412 for "traits not satisfied". Dead code basically that error path never worked. Changed the frontend check to 412.

…e mismatch

Bug 1 (Critical): delete-airdrop removes user record but does not track
the verification token. A user can claim multiple times with different
wallets using the same verified social account. Fixed by adding a
DeletedToken model that tracks tokens from deleted claims, checked on
re-verification.

Bug 2 (Minor): Frontend checks HTTP 400 but backend returns HTTP 412
for traits-not-satisfied. Fixed frontend to match backend status code.

Closes base#24
@vercel

vercel Bot commented Jun 14, 2026

Copy link
Copy Markdown

@Sertug17 is attempting to deploy a commit to the Coinbase Team on Vercel.

A member of the Team first needs to authorize it.

@Sertug17

Copy link
Copy Markdown
Author

Would appreciate a review on this when either of you gets a chance. Fixes a sybil bypass in the claim flow (delete + re-claim reuses the same verification token) and corrects a status code mismatch between frontend and backend for the traits-not-satisfied path.

@patnir @kushagras481

@osr21

osr21 commented Aug 15, 2026

Copy link
Copy Markdown

Reviewed the diff (context: cross-posted an assessment of the underlying bug on #24). The fix closes the reported bypass on the X verification path and the 412 frontend fix is the right direction. Three things worth addressing before merge — the first two are correctness, the third is scope:

1. The .catch(() => {}) swallows every error, not just duplicates

await prisma.deletedToken.create({ data: { token: existingUser.baseVerifyToken } })
  .catch(() => { /* Token may already be in deleted_tokens (idempotent) */ });

If create fails for any reason other than a unique-constraint violation (connection drop, table missing after a bad migration), the error is silently discarded and execution proceeds to delete the user record — recreating the exact bypass this PR fixes, but now intermittently and invisibly. Catch only the duplicate case:

.catch((e) => {
  if (e?.code !== 'P2002') throw e; // P2002 = Prisma unique constraint violation
});

2. Tombstone + delete should be atomic

If the process dies between deletedToken.create and verifiedUser.delete, you get a tombstoned token with a live claim (user locked out, support burden). The reverse order would risk the bypass instead. prisma.$transaction([...]) removes both failure modes:

await prisma.$transaction([
  prisma.deletedToken.create({ data: { token: existingUser.baseVerifyToken } }),
  prisma.verifiedUser.delete({ where: { address: walletAddress } }),
]);

(with the P2002 handling moved to a pre-check or upsert inside the transaction).

3. Does the Coinbase verification path have the same bypass?

The schema has a parallel CoinbaseVerifiedUser table with its own baseVerifyToken, and there's a separate coinbase claim route (referenced in #28). This PR guards verifiedUser re-claims only. If the delete flow (or any future delete flow) touches coinbase_verified_users, the same delete-then-reclaim sequence works there and the sybil invariant is still broken for that provider. Worth either extending the DeletedToken check to the coinbase claim route in this PR, or filing it as a known follow-up so it doesn't get lost.

Minor: the new frontend check matches the exact backend error string ('X account does not satisfy verification requirements.'). Matching on response.status === 412 alone would survive copy edits to the message — the string equality is how the original 400-vs-412 dead code happened.

With #1 and #2 addressed this looks mergeable to me; #3 can be a follow-up if the coinbase route is confirmed unaffected.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants