# Advance booking limits enforced server-side with a structured error response

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

## Why it matters

Advance booking limits that exist only as a UI filter are not limits at all. A direct POST to the booking API skips the date picker entirely. OWASP A03 (Injection) and CWE-20 (Improper Input Validation) apply to any business rule that can be bypassed by sending a raw HTTP request. Without a server-side ceiling, a misbehaving client can book a slot 5 years in the future, creating junk data in the schedule, blocking the resource for phantom appointments, and polluting analytics.

## Severity rationale

Low because far-future junk bookings are annoying and pollute analytics but do not expose sensitive data or create immediate revenue loss, making it lower urgency than buffer or duration violations.

## Remediation

Define `MAX_ADVANCE_DAYS` as a server-side constant and validate it in the booking API handler. The check belongs next to duration and cutoff validation so all time-boundary rules are visible in one file.

```ts
// src/app/api/book/route.ts
const MAX_ADVANCE_DAYS = 90

export async function POST(req: Request) {
  const { startTime } = await req.json()
  const requested = new Date(startTime)
  const ceiling = new Date(Date.now() + MAX_ADVANCE_DAYS * 86_400_000)

  if (requested > ceiling) {
    return Response.json(
      {
        error: 'Booking too far in advance',
        detail: `Bookings cannot exceed ${MAX_ADVANCE_DAYS} days from today`,
      },
      { status: 400 }
    )
  }
  // ... create booking
}
```

## Detection

- **ID:** `advance-booking-limits`
- **Severity:** `low`
- **What to look for:** Search for max advance booking configuration (e.g., `MAX_ADVANCE_DAYS = 90`, `maxBookingDays`, `advanceLimitDays`). Count all booking creation API endpoints. For each, verify the server compares the requested time against `now + maxAdvanceDays` and rejects requests that exceed the limit with a structured JSON error (HTTP 400). Examine files matching `src/app/api/book*/route.ts`, `src/app/api/appointment*/route.ts`, `src/lib/*config*`, `src/lib/*booking*`.
- **Pass criteria:** Count all booking API endpoints. At least 1 max advance booking constant must be defined server-side (e.g., `MAX_ADVANCE_DAYS`). 100% of booking creation endpoints must compare `requestedTime` against `now + maxAdvanceDays` and return HTTP 400 with a structured JSON error when exceeded. Report: "Max advance days: [value]. X of Y endpoints enforce server-side."
- **Fail criteria:** Max advance limit is only applied when filtering available slots for display, not when validating a booking request. Or no advance limit constant exists in server-side code.
- **Skip (N/A) when:** Platform explicitly does not enforce max advance booking limits (allows booking arbitrarily far in the future by design).
- **Detail on fail:** Example: `"Calendar hides slots beyond 90 days, but API does not check. A direct POST with a date 120 days in the future succeeds."`
- **Cross-reference:** Check `same-day-cutoff` — advance limits guard the far end; cutoff guards the near end. Both should be in the same validation layer.
- **Cross-reference:** Check `duration-validation` — all booking time boundaries (duration, cutoff, advance) should be validated together.
- **Cross-reference:** Check `buffer-enforcement` — advance limits and buffer time are both server-side time validations.
- **Remediation:** Validate max advance booking server-side:

  ```ts
  // pages/api/appointments.ts
  const MAX_ADVANCE_DAYS = 90

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

    const now = new Date()
    const requestedTime = new Date(startTime)
    const maxAllowedTime = new Date(
      now.getTime() + MAX_ADVANCE_DAYS * 24 * 60 * 60 * 1000
    )

    if (requestedTime > maxAllowedTime) {
      return Response.json(
        {
          error: 'Booking too far in advance',
          detail: `Bookings cannot be made more than ${MAX_ADVANCE_DAYS} days 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-000532
