Skip to content

Idempotency key claiming doesn't actually prevent concurrent duplicate requests #452

Description

@Wilfred007

What

Fix claimKey() in idempotencyService.ts so it actually distinguishes "I am the original claimant of this key" from "I am a concurrent duplicate that just raced onto an in-progress row," per its own documented contract.

Why

The docstring for claimKey() (L19-21) states it "Throws if the key is in_progress (concurrent duplicate)". The actual implementation (L69-77) does a single INSERT ... ON CONFLICT DO UPDATE that resets the row's status to 'in_progress' regardless of which request "wins" the race, then unconditionally returns null ("let the caller proceed") whenever status is in_progress. This means when two requests race on the same idempotency key, both proceed to execute the underlying action the key was meant to deduplicate — the promised throw never fires. Given this key is used to guard payment-related actions, this defeats the entire purpose of idempotency protection under concurrent retries (e.g. a client double-submitting a payment request during a slow response).

Scope

In scope:

  • Rework claimKey()'s ON CONFLICT logic so exactly one concurrent request is recognized as the original claimant and any other racing request on the same key is correctly identified and rejected/throws
  • Add a concurrency test that fires two simultaneous claims on the same key and asserts only one succeeds
  • Audit call sites relying on claimKey() to confirm they handle the corrected throw behavior correctly

Out of scope:

  • Changes to how idempotency keys are generated by callers
  • The HTTP-level idempotencyMiddleware wiring itself (only the underlying service logic)

Acceptance Criteria

  • Two concurrent claimKey() calls on the same key: exactly one succeeds, the other throws/is rejected as a duplicate
  • New test reproduces the race (e.g. Promise.all of two claims) and fails against the old code, passes against the fix
  • Existing idempotency-dependent tests still pass

Technical Context

  • backend/src/services/idempotencyService.ts — docstring L19-21, claimKey() implementation L69-77 (INSERT ... ON CONFLICT DO UPDATE)

Activity

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

Metadata

Metadata

Assignees

Labels

GrantFox OSSIssue tracked in GrantFox OSSMaybe RewardedIssue may be eligible for a GrantFox rewardThird CampaignCampaign: Third CampaignbackendBackend developmenthardComplex taskssecurityIssues related to application security and audits

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions