Fix race condition in voting system#2291
Conversation
- Added unique constraint to prevent duplicate votes on same card - Added unique index on votes(player_id, dealt_card_id) - Updatet toggle_vote to use Repo.get_by with on_conflict handling - Updated toggle_continue_vote to use DB queries instead of memory - Added race condition tests for duplicate vote prevention - Replaced IO.puts with Logger.debug/warning - Use Integer.parse for safe string to integer conversion Fixes OWASP#2288
There was a problem hiding this comment.
Pull request overview
This PR addresses a critical race condition in the voting system that could lead to duplicate votes when multiple concurrent requests are processed. The fix implements defense-in-depth by adding database-level unique constraints and updating the application layer to use atomic operations with conflict handling.
Changes:
- Added unique index on
votes(player_id, dealt_card_id)to enforce database-level constraint - Refactored vote toggle handlers to use
Repo.get_bywithon_conflict: :nothingfor safer concurrent operations - Replaced unsafe
String.to_integerwithInteger.parsefor input validation - Replaced
IO.putswith properLogger.debug/Logger.warningcalls
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 7 comments.
| File | Description |
|---|---|
copi.owasp.org/priv/repo/migrations/20260218120000_add_unique_constraint_to_votes.exs |
Adds database unique constraint on votes table to prevent duplicate (player_id, dealt_card_id) pairs |
copi.owasp.org/lib/copi_web/live/player_live/show.ex |
Refactors voting handlers to query database directly, use on_conflict handling, safe input parsing, and proper logging |
copi.owasp.org/test/copi_web/live/player_live_test.exs |
Adds tests verifying database constraints prevent duplicate votes and continue votes |
Comments suppressed due to low confidence (1)
copi.owasp.org/lib/copi_web/live/player_live/show.ex:17
- The Vote schema's changeset should include a unique_constraint to match the database constraint and provide meaningful error messages. Currently, ContinueVote has this constraint in its changeset (line 17 of continue_vote.ex) but Vote does not. Add:
|> unique_constraint([:player_id, :dealt_card_id], name: :votes_player_dealt_card_unique_index)to the changeset function.
alias Copi.Cornucopia.ContinueVote
@impl true
def mount(_params, _session, socket) do
{:ok, socket}
end
copi.owasp.org/priv/repo/migrations/20260218120000_add_unique_constraint_to_votes.exs
Show resolved
Hide resolved
- Test toggling vote off after creating it - Test toggling continue vote off after creating it - Improves code coverage to reach 80% threshold
- Prefix unused 'html' and 'other_player' variables with underscore - Resolves compilation warnings that cause CI failure
- Fixed unused conn variables in race condition tests - Remove invalid dealt_card_id test that expects non-existent DOM elements - All tests now pass without warnings
…rrency tests Based on Copilot's security review: 1. Migration now handles existing duplicates before adding constraint - Removes duplicate votes keeping only oldest (MIN(id)) - Prevents migration failure in production 2. Eliminated TOCTOU race on delete operations - Use atomic delete_all instead of get_by + delete - Try delete first (returns count), insert only if count=0 - Prevents concurrent delete failures - Accurate logging based on actual operation performed 3. Improved race condition test simulation - Use Task.async for true concurrent insertions - Better validates database constraint under real concurrent load - Tests actual race condition, not sequential operations Reference: Copilot review comments on PR OWASP#2291
|
I have implemented copilot suggestions, @sydseter is this good to go ? |
|
@immortal71 I am not sure. It’s a will take me some time to look deeper into this. I am going to leave this open for now and come back to it sometime next week. |
|
Besides this pull-request, I haven’t had any reports about this. That doesn’t mean it doesn’t or can’t happen. It’s just that I want to be extra careful making changes here. |
|
@sydseter so should i further work on this or not or wait for your review ? |
|
@sydseter do u have any issues I can work on ? ~ something big heavy task which I can do and keep me bg ? |
Description
Fixes #2288
This PR addresses a critical race condition in the voting system that could lead to duplicate votes and data corruption when multiple requests are processed concurrently.
Problem
The current voting implementation has a check-then-act pattern that is vulnerable to race conditions:
When concurrent requests are processed, both can pass the check simultaneously and create duplicate votes, violating data integrity.
Solution
Database Layer
votes(player_id, dealt_card_id)to enforce constraint at database levelcontinue_votes(player_id, game_id)(already existed)Application Layer
toggle_voteto useRepo.get_bywithon_conflict: :nothingfor atomic insert operationstoggle_continue_voteto query database instead of relying on preloaded dataString.to_integerwith safeInteger.parseto prevent crashes on invalid inputIO.putswith properLogger.debug/Logger.warningcallsTesting
Changes
priv/repo/migrations/20260218120000_add_unique_constraint_to_votes.exs- New migrationlib/copi_web/live/player_live/show.ex- Fixed race conditions in event handlerstest/copi_web/live/player_live_test.exs- Added race condition testsTesting
All tests pass with proper database constraints ensuring data integrity even under concurrent load.