security: EntitlementService usage recording has race condition under concurrency #42

Open
opened 2026-02-20 16:44:03 +00:00 by Clotho · 0 comments
Member

Problem

Services/EntitlementService.php usage recording does not use database-level locking. Concurrent requests can read the same current usage value and both increment it, resulting in under-counting.

Example scenario:

  1. Request A reads usage = 99 (limit = 100)
  2. Request B reads usage = 99 (limit = 100)
  3. Request A writes usage = 100 ✓
  4. Request B writes usage = 100 ✓ (should be 101, limit exceeded)

Similarly, provisionBoost() and package provisioning do not prevent duplicate active records within a transaction.

Impact

  • Usage limits can be exceeded silently
  • Boosts could be provisioned twice
  • Billing could be affected if usage-based pricing is implemented

Acceptance Criteria

  • Add DB::transaction() with lockForUpdate() around usage reads/writes in recordUsage() and recordNamespaceUsage()
  • Use atomic increment (DB::raw('usage_count + 1')) rather than read-then-write
  • Add a unique constraint or whereDate guard to prevent duplicate package provisioning
  • Add concurrency test using parallel job dispatch

Discovered during automated scan (issue #3)

## Problem `Services/EntitlementService.php` usage recording does not use database-level locking. Concurrent requests can read the same current usage value and both increment it, resulting in under-counting. Example scenario: 1. Request A reads usage = 99 (limit = 100) 2. Request B reads usage = 99 (limit = 100) 3. Request A writes usage = 100 ✓ 4. Request B writes usage = 100 ✓ (should be 101, limit exceeded) Similarly, `provisionBoost()` and package provisioning do not prevent duplicate active records within a transaction. ## Impact - Usage limits can be exceeded silently - Boosts could be provisioned twice - Billing could be affected if usage-based pricing is implemented ## Acceptance Criteria - Add `DB::transaction()` with `lockForUpdate()` around usage reads/writes in `recordUsage()` and `recordNamespaceUsage()` - Use atomic increment (`DB::raw('usage_count + 1')`) rather than read-then-write - Add a unique constraint or `whereDate` guard to prevent duplicate package provisioning - Add concurrency test using parallel job dispatch _Discovered during automated scan (issue #3)_
Clotho added the
review
discovery
security
P2
labels 2026-02-20 16:44:03 +00:00
Clotho was assigned by Charon 2026-02-20 23:46:47 +00:00
Charon added the
agent-ready
label 2026-02-21 01:31:48 +00:00
Sign in to join this conversation.
No description provided.