Application-level availability checks followed by unguarded inserts are the leading cause of double bookings in production systems. Under concurrent load, two requests can both read "slot available," both pass the availability check, and both insert a booking record — all before either transaction commits. CWE-362 (race condition) and OWASP A04 (Insecure Design) both apply. A database-level UNIQUE constraint is the only mechanism that enforces the invariant atomically: the database serializes concurrent writes and rejects the duplicate at the storage layer, regardless of application concurrency.
Critical because application-level checks without database constraints provably fail under concurrent load, producing confirmed double-bookings that require manual remediation and refunds.
Add a UNIQUE constraint to your schema so the database enforces the invariant even when application-level checks race. Add this in prisma/schema.prisma (or the equivalent SQL migration).
model Booking {
id String @id @default(cuid())
slotId String
startTime DateTime
status String
@@unique([slotId, startTime]) // database-level enforcement
}
Then handle the unique constraint violation in your handler and return a structured 409 Conflict response.
ID: booking-flow-lifecycle.double-booking.concurrent-request-handling
Label: Concurrent requests are handled safely
Severity: critical
What to look for: Count all database-level concurrency controls protecting booking creation: UNIQUE indexes on slot + time columns (check schema.prisma, migration files, or CREATE TABLE statements), serializable transaction isolation (isolation: 'Serializable', SET TRANSACTION ISOLATION LEVEL SERIALIZABLE), pessimistic locking (FOR UPDATE), or advisory locks. Also check for application-level-only patterns (find-then-insert without constraints). Enumerate: "X concurrency control mechanisms found: [list types]."
Pass criteria: At least 1 database-level concurrency control must exist. The schema must include a UNIQUE constraint preventing overlapping bookings (e.g., @@unique([slotId, startTime]) in Prisma, UNIQUE(slot_id, start_time) in SQL), OR transactions use SERIALIZABLE isolation level, OR row-level locking (FOR UPDATE) is used. Report: "X concurrency controls found: [UNIQUE constraint on (slotId, startTime) in schema.prisma | SERIALIZABLE isolation | FOR UPDATE locking]."
Fail criteria: Application-level checks only (check availability then insert without db constraints or locking). This is the number 1 cause of double bookings in production. Do NOT pass when the only protection is a findFirst/count check followed by a create — without a database constraint or transaction isolation, two concurrent requests can both pass the check and both insert.
Skip (N/A) when: Never — this is critical for any booking system.
Detail on fail: "Booking creation relies on application-level 'findFirst' check followed by 'create' without database constraints or transactions. Vulnerable to race conditions under concurrent load."
Cross-reference: The conflict-check in Booking Creation verifies the application-level availability check that this database constraint backs up.
Cross-reference: The transaction-atomicity check in this category verifies multi-table writes are atomic alongside this constraint.
Cross-reference: The pessimistic-locking check in this category covers row-level locking as an additional concurrency layer.
Remediation: Add a database-level UNIQUE constraint in your schema file (e.g., prisma/schema.prisma or migration SQL).
// In schema.prisma
model Booking {
id String @id @default(cuid())
slotId String
startTime DateTime
// ... other fields
@@unique([slotId, startTime]) // Prevents overlaps at DB level
}