Skip to content

fix(#10241): prevent race condition in unique() validation allowing duplicates#11069

Open
shivv23 wants to merge 1 commit into
medic:masterfrom
shivv23:pr-10241-unique-race
Open

fix(#10241): prevent race condition in unique() validation allowing duplicates#11069
shivv23 wants to merge 1 commit into
medic:masterfrom
shivv23:pr-10241-unique-race

Conversation

@shivv23
Copy link
Copy Markdown

@shivv23 shivv23 commented May 14, 2026

Summary

The unique() validation uses a read-then-write pattern that is vulnerable to concurrent requests. When two SMS messages arrive within seconds, both pass the unique() check before either document is committed, resulting in duplicate records.

Changes

  • Added an in-memory Set-based lock keyed on sorted unique field value pairs.
  • Before querying the database, exists() checks the lock set. If another concurrent call holds the same lock, exists() immediately returns true (not unique), preventing the duplicate.
  • The lock is released in a finally block after the DB query completes.

Why this works

Node.js single-threaded execution ensures the lock check-and-set is atomic: two async functions cannot interleave their synchronous operations at the lock-acquisition point. The race window between the DB query and save is protected because the lock is held during the entire exists() execution.

Testing

  • All 132 validation_utils unit tests pass.
  • Regression: existing tests for non-race-condition scenarios continue to work as expected.

…ing duplicates

The unique() validation uses a read-then-write pattern that is
vulnerable to concurrent requests. When two SMS messages arrive
within seconds, both pass the unique() check before either document
is committed, resulting in duplicate records.

Added an in-memory lock (Set) keyed on the sorted unique field values.
Before querying the database, the exists() function acquires the lock.
If another concurrent call holds the same lock, exists() returns true
(not unique), preventing the duplicate. The lock is released in a
finally block after the DB query completes.

Node.js single-threaded execution ensures the check-and-set is atomic -
two async functions cannot interleave their synchronous operations at
the lock-aquisition point. The race window between the DB query and
save is protected by the lock held during the entire exists() duration.
@shivv23 shivv23 force-pushed the pr-10241-unique-race branch from 224aaa2 to f99f359 Compare May 14, 2026 09:22
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.

1 participant