# Same-day cutoff rules applied server-side at booking creation

- **Pattern:** `ab-000527` (`booking-calendar-availability.availability-logic.same-day-cutoff`)
- **Severity:** high
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-000527
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-000527)

## Why it matters

Same-day cutoff rules that live only in the slot-filter query are display controls, not booking controls. A direct POST to the booking API bypasses the hidden slots entirely. OWASP A03 (Injection) and CWE-20 (Improper Input Validation) apply whenever a server accepts client-supplied timestamps without verifying them against business rules. Without server-side cutoff enforcement, a customer can schedule a same-day appointment that the host has no time to prepare for, or an automated client can flood the schedule with last-minute bookings.

## Severity rationale

High because the gap between UI slot-filtering and API validation means any direct API caller can book inside the cutoff window, bypassing the host's advance-notice requirement entirely.

## Remediation

Add a cutoff constant and validate it against the request timestamp at the booking API handler, not only when generating the list of available slots.

```ts
// src/app/api/book/route.ts
const ADVANCE_BOOKING_HOURS = 24

export async function POST(req: Request) {
  const { startTime } = await req.json()
  const requested = new Date(startTime)
  const earliest = new Date(Date.now() + ADVANCE_BOOKING_HOURS * 3_600_000)

  if (requested < earliest) {
    return Response.json(
      {
        error: 'Too soon',
        detail: `Bookings require at least ${ADVANCE_BOOKING_HOURS}h notice`,
      },
      { status: 400 }
    )
  }
  // ... create booking
}
```

Co-locate this check with duration and buffer validation so all server-side time boundaries are auditable in one place.

## Detection

- **ID:** `same-day-cutoff`
- **Severity:** `high`
- **What to look for:** Search for advance booking cutoff configuration (e.g., `ADVANCE_BOOKING_HOURS = 24`, `MIN_NOTICE_HOURS`, `cutoffHours`). Count all booking creation API endpoints. For each, verify the server compares the requested time against `now + cutoff` and rejects requests that violate the rule. Check if this validation is in the API route handler or only in the calendar UI filter. Examine files matching `src/app/api/book*/route.ts`, `src/app/api/appointment*/route.ts`, `src/lib/*booking*`, `src/lib/*config*`.
- **Pass criteria:** Count all booking API endpoints. At least 1 advance booking cutoff constant must be defined server-side (e.g., `ADVANCE_BOOKING_HOURS`). 100% of booking creation endpoints must compare `requestedTime` against `now + cutoffHours` and return HTTP 400/409 when violated. Report: "Cutoff hours: [value]. X of Y endpoints enforce server-side."
- **Fail criteria:** Cutoff is only applied when filtering available slots for display, not when validating a booking request. Or no cutoff constant exists in server-side code.
- **Skip (N/A) when:** Platform explicitly does not enforce advance booking cutoffs (accepts last-minute bookings by design).
- **Detail on fail:** Example: `"Calendar hides same-day slots, but API does not check. A direct POST with a same-day time succeeds."`
- **Cross-reference:** Check `duration-validation` — cutoff and duration validations should be co-located in the same API middleware.
- **Cross-reference:** Check `advance-booking-limits` — same-day cutoff guards the near end; advance limits guard the far end.
- **Cross-reference:** Check `past-date-protection` — client-side past-date guard complements server-side cutoff validation.
- **Remediation:** Validate cutoff server-side:

  ```ts
  // pages/api/appointments.ts
  const ADVANCE_BOOKING_HOURS = 24

  export async function POST(req: Request) {
    const { startTime } = await req.json()

    const now = new Date()
    const requestedTime = new Date(startTime)
    const minimumAllowedTime = new Date(
      now.getTime() + ADVANCE_BOOKING_HOURS * 60 * 60 * 1000
    )

    if (requestedTime < minimumAllowedTime) {
      return Response.json(
        {
          error: 'Too late to book',
          detail: `Bookings must be made at least ${ADVANCE_BOOKING_HOURS} hours in advance`,
        },
        { status: 400 }
      )
    }

    // ... create booking
  }
  ```

## External references

- cwe CWE-20
- owasp A03

Taxons: injection-and-input-trust

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