# Availability re-checked before commit

- **Pattern:** `ab-000544` (`booking-flow-lifecycle.double-booking.slot-availability-recheck`)
- **Severity:** high
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-000544
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-000544)

## Why it matters

In flows where availability is checked when the user loads the booking form and the final insert happens minutes later at payment time, the check and the commit are separated by an unbounded time window. CWE-367 (TOCTOU race condition) names this exact pattern: the condition checked and the action taken are not atomic. A second user who books the same slot during the payment flow will not be detected until the UNIQUE constraint fires — and by then the first user has already been charged or committed their card details. Re-checking inside the transaction collapses the window to zero.

## Severity rationale

High because the TOCTOU window between initial check and final insert is exploitable under normal concurrent usage, not just adversarial load.

## Remediation

Move the availability query inside the same transaction block as the insert, in your booking creation handler (e.g., `src/app/api/bookings/route.ts` or `src/lib/booking-service.ts`).

```typescript
await db.$transaction(async (tx) => {
  // recheck — inside the transaction, atomic with the insert
  const conflict = await tx.booking.findFirst({
    where: { slotId, startTime, status: { not: 'CANCELLED' } }
  });
  if (conflict) throw new ConflictError('Slot taken');

  await tx.booking.create({ data: { slotId, startTime, ...rest } });
});
```

## Detection

- **ID:** `slot-availability-recheck`
- **Label:** Availability re-checked before commit
- **Severity:** `high`
- **What to look for:** In flows where availability is checked early (e.g., at form load) then payment happens later, verify availability is re-checked *inside the final transaction* immediately before insertion. Before evaluating, extract and quote the transaction block that contains the final booking insert. Identify whether an availability query appears inside the same transaction scope (same `$transaction` callback, same `BEGIN...COMMIT` block).
- **Pass criteria:** Count all booking creation transaction blocks. At least 1 must contain an availability recheck query inside the same transaction scope. The check and insert happen atomically — both must appear within the same transaction callback or SQL transaction block. The recheck must query for conflicting bookings (not just check a boolean flag).
- **Fail criteria:** Availability is checked at the start of the request, but not verified again during the final database write. 0% of transaction blocks contain an availability recheck. A race condition window exists between check and insert. Also fails if the recheck is in a separate transaction from the insert.
- **Skip (N/A) when:** The entire operation is a single atomic SQL statement (e.g., `INSERT ... WHERE NOT EXISTS ...`) or the UNIQUE constraint guarantees atomicity without an explicit recheck.
- **Detail on fail:** `"Availability is verified when the user loads the payment form, but not re-verified before the final booking insert. Another user could book the slot in between."`
- **Cross-reference:** The conflict-check in Booking Creation verifies the initial availability check that this recheck reinforces.
- **Cross-reference:** The concurrent-request-handling check in this category covers the database constraint that provides ultimate protection.
- **Cross-reference:** The rescheduling-logic check in Lifecycle Management verifies atomic rechecks during reschedule operations.
- **Remediation:** Move the availability check into the transaction block in your booking creation handler.

  ```typescript
  await db.$transaction(async (tx) => {
    // Re-check availability inside transaction
    const conflict = await tx.booking.findFirst({
      where: { slotId, status: { not: 'CANCELLED' } }
    });
    if (conflict) throw new Error("Slot taken");

    // Only then insert
    await tx.booking.create({ data: {...} });
  });
  ```

## External references

- cwe CWE-367
- cwe CWE-362
- iso-25010 functional-suitability.functional-correctness

Taxons: data-integrity

HTML version: https://auditbuffet.com/patterns/ab-000544
