Skip to content

fix: serialize balance-check-and-transfer to close TOCTOU race (CWE-367)#343

Open
Matthew-Selvam wants to merge 1 commit into
Conway-Research:mainfrom
Matthew-Selvam:fix/177-transfer-credits-toctou
Open

fix: serialize balance-check-and-transfer to close TOCTOU race (CWE-367)#343
Matthew-Selvam wants to merge 1 commit into
Conway-Research:mainfrom
Matthew-Selvam:fix/177-transfer-credits-toctou

Conversation

@Matthew-Selvam

Copy link
Copy Markdown

Summary

Fixes #177. transfer_credits (and the identical pattern in fund_child) check getCreditsBalance() and call transferCredits() as two separate awaits with no atomicity between them:

T1: balance = $100, amount = $40 → passes guard (40 < 50) ✓
T2: balance = $100, amount = $40 → passes guard (40 < 50) ✓
T1: transfers $40 → balance now $60
T2: transfers $40 → balance now $20 (violated 50% limit!)

Fix

Adds a per-agent-process credit transfer mutex (withCreditTransferLock, a simple serialized promise chain) around the balance-check-and-transfer critical section in both transfer_credits and fund_child, so a second concurrent call re-checks the guard against the post-transfer balance instead of a stale one. A failed transfer inside the lock doesn't wedge it for later callers.

As the issue notes, a server-side atomic transfer would be the ideal long-term fix — that's out of scope here since it lives in Conway's backend, not this client. This closes the client-side race in the meantime.

Testing

  • tsc --noEmit passes.
  • Added a regression test: two concurrent $40 transfers against a $100 balance. Exactly one now succeeds and the other is correctly blocked once the balance drops to $60 (where $40 exceeds the new $30 half-balance limit) — previously both would have gone through.
  • Full tools-security.test.ts (72/72) and authority-rules.test.ts (36/36, covers fund_child) pass.

transfer_credits and fund_child both check getCreditsBalance() and then
call transferCredits() as two separate awaits, with no atomicity between
them. Two concurrent calls can each read the same starting balance, both
pass the 'no more than half' guard, and together transfer more than the
policy intends.

Adds a per-process credit transfer mutex (a simple promise chain) that
serializes the balance-check-and-transfer critical section across both
tools, so a second concurrent call re-checks against the post-transfer
balance rather than a stale one. A failed transfer doesn't wedge the
lock for subsequent callers.

Server-side atomic transfers (noted in the issue as the ideal long-term
fix) are out of scope here since that lives in Conway's backend, not
this client.

Adds a regression test simulating two concurrent $40 transfers against
a $100 balance: exactly one should succeed and the other should be
blocked once the balance drops to $60 (where $40 exceeds the new half-
balance limit of $30).

Fixes Conway-Research#177
@Matthew-Selvam

Copy link
Copy Markdown
Author

This is ready for maintainer review whenever you get a chance — happy to make any changes requested.

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.

fix: CWE-367 — TOCTOU race condition in transfer_credits balance check

1 participant