# Buffer time between appointments enforced at the data layer

- **Pattern:** `ab-000518` (`booking-calendar-availability.calendar-display.buffer-enforcement`)
- **Severity:** critical
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-000518
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-000518)

## Why it matters

Buffer time enforced only in the UI is not enforced. Any client that posts directly to the booking API — a script, a browser extension, a race condition — bypasses the disabled slots entirely and books into the buffer window. This violates CWE-362 (race condition) and CWE-667 (improper locking), and the ISO 25010 functional-correctness attribute. The practical result: a host has back-to-back appointments with no travel or prep time, they run late, the second client has a bad experience, and a refund is likely. Server-side enforcement is the only enforcement that matters.

## Severity rationale

Critical because UI-only buffer checks are trivially bypassed with a direct API call, making double-booking into the buffer window mechanically possible without any exploit skills.

## Remediation

Re-validate buffer time inside the booking API route, not just when filtering available slots for display. Define `BUFFER_MINUTES` as a server-side constant and query for conflicts that include the buffer window before confirming.

```ts
// src/app/api/book/route.ts
const BUFFER_MINUTES = 15

export async function POST(req: Request) {
  const { startTime, duration, resourceId } = await req.json()
  const start = new Date(startTime)
  const end = new Date(start.getTime() + duration * 60_000)

  const conflict = await db.bookings.findFirst({
    where: {
      resourceId,
      status: 'confirmed',
      startTime: { lt: new Date(end.getTime() + BUFFER_MINUTES * 60_000) },
      endTime:   { gt: new Date(start.getTime() - BUFFER_MINUTES * 60_000) },
    },
  })

  if (conflict) {
    return Response.json({ error: 'Buffer conflict' }, { status: 409 })
  }
  // ... create booking
}
```

## Detection

- **ID:** `buffer-enforcement`
- **Severity:** `critical`
- **What to look for:** Before evaluating, extract and quote the exact buffer time constant or configuration value (e.g., `BUFFER_TIME = 15`, `bufferMinutes: 15`) from the codebase. Find the logic that computes or filters available slots. Count all booking API endpoints (e.g., `POST /api/book`, `POST /api/appointments`). For each, verify that buffer time is validated server-side — in a database constraint, trigger, API validation function, or transaction. UI-only enforcement is not sufficient — a direct API call bypasses it. Examine files matching `src/app/api/book*`, `src/app/api/appointment*`, `src/lib/*availability*`, `prisma/schema.prisma`, `drizzle/*`.
- **Pass criteria:** Count all booking creation API endpoints. 100% of booking endpoints must validate buffer time server-side before confirming a booking. A database constraint, API middleware, or transaction must prevent booking a slot if it would violate the buffer time requirement. Example: if buffer is 15 minutes and a meeting ends at 2:00 PM, the next available slot cannot start before 2:15 PM. Report: "X of Y booking endpoints enforce buffer time at the data layer."
- **Fail criteria:** Buffer time is only checked in the UI (e.g., a disabled slot), or a direct API call can bypass the buffer check.
- **Do NOT pass when:** Buffer time logic exists only in a client-side component (e.g., `CalendarSlot.tsx`) but not in any API route or database constraint. Client-side-only validation is insufficient regardless of how thorough it appears.
- **Skip (N/A) when:** Never — buffer time enforcement is essential to prevent scheduling conflicts.
- **Detail on fail:** Example: `"UI shows blocked slots 15 minutes after each appointment, but the booking API does not validate buffer time. A direct POST to /api/book bypasses the restriction."`
- **Cross-reference:** Check `data-freshness` — stale availability data means the client does not know about recent bookings, making buffer violations more likely.
- **Cross-reference:** Check `duration-validation` — buffer time and duration validation often share the same API middleware.
- **Cross-reference:** Check `same-day-cutoff` — all booking time validations should occur at the same API layer for consistency.
- **Remediation:** Enforce buffer time at the API layer:

  ```ts
  // pages/api/availability.ts
  const BUFFER_MINUTES = 15

  export async function getAvailableSlots(
    date: Date,
    duration: number,
    resourceId: string
  ) {
    const bookings = await db.bookings.findMany({
      where: {
        resourceId,
        startTime: { gte: new Date(date.getTime() - 24 * 60 * 60 * 1000) },
        status: 'confirmed',
      },
    })

    const slots = generateSlots(date, 30) // 30-minute increments
    return slots.filter((slot) => {
      const slotEnd = new Date(slot.time.getTime() + duration * 60 * 1000)
      const bufferStart = new Date(slot.time.getTime() - BUFFER_MINUTES * 60 * 1000)
      const bufferEnd = new Date(slotEnd.getTime() + BUFFER_MINUTES * 60 * 1000)

      return !bookings.some(
        (booking) =>
          booking.startTime < bufferEnd && booking.endTime > bufferStart
      )
    })
  }

  export async function POST(req: Request) {
    const { slotTime, duration, resourceId } = await req.json()

    // Re-validate buffer time at booking time
    const conflicts = await db.bookings.findMany({
      where: {
        resourceId,
        startTime: {
          lt: new Date(slotTime.getTime() + (duration + BUFFER_MINUTES) * 60 * 1000),
        },
        endTime: {
          gt: new Date(slotTime.getTime() - BUFFER_MINUTES * 60 * 1000),
        },
        status: 'confirmed',
      },
    })

    if (conflicts.length > 0) {
      return Response.json(
        { error: 'Slot no longer available due to buffer time' },
        { status: 409 }
      )
    }
    // ... create booking
  }
  ```

## External references

- cwe CWE-362
- cwe CWE-667
- iso-25010 functional-correctness

Taxons: data-integrity

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