Skip to content

fix: reject replayed MPC session initiations from the coordinator (#241) - #474

Merged
Marvy247 merged 1 commit into
HitEmPoka:mainfrom
Davidemulo:fix/241-mpc-session-replay-protection
Aug 29, 2026
Merged

fix: reject replayed MPC session initiations from the coordinator (#241)#474
Marvy247 merged 1 commit into
HitEmPoka:mainfrom
Davidemulo:fix/241-mpc-session-replay-protection

Conversation

@Davidemulo

Copy link
Copy Markdown

Summary

services/node's sessions map is never pruned, so a coordinator (or an attacker replaying a captured request) could POST /session/:id/shares again for a session_id that had already reached Complete. Because receive_share_fragment() unconditionally set status back to SharesReceived, this silently reopened a finished session: a subsequent /generate call could then overwrite the already-delivered proof for that session_id with a different one — a real tampering vector for a poker MPC system, not just wasted work.

Fix

  • Added NodeState.finalized_sessions: a HashSet<String> recording every session_id that has reached SessionStatus::Complete, populated in post_generate's background task at the same point status is set to Complete. Kept independent of the sessions map itself so the check still holds even if sessions entries were ever pruned in the future.
  • post_shares now rejects (409 Conflict) any share submission whose session_id is in finalized_sessions, before touching the sessions map at all.
  • post_shares also now rejects (409 Conflict) a share submission for a session that exists but has moved past SessionStatus::SharesReceived (WitnessGenerating/ProofGenerating/Failed) — this catches an in-flight replay attempt that the finalized_sessions check alone wouldn't, since that check only covers sessions that reached Complete.

Tests

services/node/src/api.rs, replay_protection_tests module:

  • a fresh session_id is still accepted (no false positive)
  • a session_id already in finalized_sessions is rejected with 409 and the expected message
  • a session that's been moved to WitnessGenerating (simulating what post_generate does) rejects a further share submission with 409
  • marking one session_id finalized doesn't affect an unrelated session_id

Verification

No local Rust toolchain is available in this environment (link.exe fails compiling proc-macro2/quote build scripts). cargo test -p mpc-node could not be run here. Verified by manual review: the new guards are two early-return checks with no interaction with the rest of post_shares's logic, and the existing post_shares/post_generate control flow (circuit-name mismatch check, capacity check, background proof generation) is otherwise untouched. Would appreciate CI/a reviewer confirming cargo test -p mpc-node locally.

Closes #241

…tEmPoka#241)

services/node's sessions map is never pruned, so a coordinator (or an
attacker replaying a captured request) could POST /session/:id/shares
again for a session_id that had already reached Complete. Because
receive_share_fragment() unconditionally set status back to
SharesReceived, this silently reopened a finished session: a subsequent
/generate call could then overwrite the already-delivered proof for that
session_id with a different one — a real tampering vector for a poker
MPC system, not just wasted work.

- Added NodeState.finalized_sessions: a HashSet<String> recording every
  session_id that has reached SessionStatus::Complete, populated in
  post_generate's background task at the same point status is set to
  Complete. Kept independent of the `sessions` map itself so the check
  still holds even if `sessions` entries were ever pruned in the future.
- post_shares now rejects (409 Conflict) any share submission whose
  session_id is in finalized_sessions, before touching the sessions map
  at all.
- post_shares also now rejects (409 Conflict) a share submission for a
  session that exists but has moved past SessionStatus::SharesReceived
  (WitnessGenerating/ProofGenerating/Failed) — this catches an in-flight
  replay attempt that the finalized_sessions check alone wouldn't, since
  that check only covers sessions that reached Complete.

Tests (services/node/src/api.rs, replay_protection_tests module):
- a fresh session_id is still accepted (no false positive)
- a session_id already in finalized_sessions is rejected with 409 and the
  expected message
- a session that's been moved to WitnessGenerating (simulating what
  post_generate does) rejects a further share submission with 409
- marking one session_id finalized doesn't affect an unrelated session_id

Verification: no local Rust toolchain available in this environment
(link.exe fails compiling proc-macro2/quote build scripts — the same
limitation hit on other Rust work this session), so `cargo test -p
mpc-node` could not be run here. Verified by manual review: the new
guards are two early-return checks with no interaction with the rest of
post_shares's logic, and the existing post_shares/post_generate control
flow (circuit-name mismatch check, capacity check, background proof
generation) is otherwise untouched.
@drips-wave

drips-wave Bot commented Aug 29, 2026

Copy link
Copy Markdown

@Davidemulo Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@Marvy247
Marvy247 merged commit 03fe182 into HitEmPoka:main Aug 29, 2026
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.

Implement MPC session replay protection against coordinator replay

3 participants