Skip to content

Sybil protection bypass via delete then re claim + frontend/backend status code mismatch #24

Description

@Sertug17

Summary

Two bugs found in the sybil protection and verification flow:

Bug 1 (Critical): Delete-then-re-claim bypasses anti-sybil protection

The delete-airdrop endpoint removes a user's record from the database, but does not track the verification token afterwards. This allows a user to claim the airdrop multiple times with different wallets using the same verified social account.

Steps to reproduce:

  1. Connect Wallet A → verify X account → get token abc123 → claim airdrop ✅
  2. Call POST /api/delete-airdrop with Wallet A's signature → record deleted ✅
  3. Connect Wallet B → verify the same X account → get the same token abc123
  4. POST /api/verify-token → findUnique({ where: { baseVerifyToken: "abc123" } }) returns null (old record was deleted)
  5. Upsert creates a new record with Wallet B's address and token abc123 ✅
  6. User claims the airdrop again with a different wallet 🎉

Affected file: pages/api/verify-token.ts (lines 83-103) — the token uniqueness check only looks at existing records, not deleted ones.
Affected file: pages/api/delete-airdrop.ts (whole file) — deletes without tracking the token.

Impact: The core anti-sybil mechanism described in the README ("Same provider account always produces same token → Database rejects duplicate tokens → prevents multi-wallet abuse") is completely bypassable. A user can claim the airdrop N times with N different wallets using the same social account.

Suggested fix: Add a DeletedToken table (or a deleted_tokens set) that tracks tokens from deleted claims. On re-verification, check both the existing records AND the deleted-token list before allowing a new claim.


Bug 2 (Minor): Frontend checks for HTTP 400, backend returns HTTP 412

In pages/index.tsx, the frontend checks for response.status === 400 to show a "traits not satisfied" error message:

typescript
// pages/index.tsx (frontend)
if (response.status === 400 && errorData.message === 'verification_traits_not_satisfied') {
setVerificationError('Sorry, your X account does not have a blue checkmark...')
}

However, in pages/api/verify-token.ts, when the Base Verify API returns a 400 with verification_traits_not_satisfied, the backend maps it to HTTP 412 (Precondition Failed):

typescript
// pages/api/verify-token.ts (backend)
if (verifyResponse.status === 400) {
const errorData = JSON.parse(responseBody);
if (errorData.message === 'verification_traits_not_satisfied') {
return res.status(412).json({ // <-- 412, not 400
error: 'X account does not satisfy verification requirements.'
});
}
}

Impact: The error handling branch for "traits not satisfied" is dead code on the frontend. Users who don't meet trait requirements see a generic error message instead of the helpful "no blue checkmark" message.

Suggested fix: Change the frontend check from response.status === 400 to response.status === 412, or change the backend to return 400 instead of 412.


Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions