Skip to content

Security: rcadavos/ticketing

Security

SECURITY.md

Security Policy

This document describes the security posture of the BonVoyage ticketing system. It complements the security conventions in CLAUDE.md §8.

Reporting a vulnerability

Please report suspected vulnerabilities privately to the maintainers (do not open a public issue). Include steps to reproduce and impact. We aim to acknowledge within 72 hours. Do not access, modify, or exfiltrate other users' data while testing.

Architecture at a glance

  • Next.js 15 (App Router) on Vercel; Supabase (Postgres + Auth + RLS) backend.
  • Customer-facing pages are currently disabled; the live surface is the staff app at /admin/*.
  • Payment is over-the-counter cash — the system stores no card/financial instrument data, so it is out of PCI-DSS scope.

Authentication & authorization

  • AuthN: Supabase Auth (email + password). Sessions are cookie-based and refreshed in middleware.ts. There is no public sign-up — staff accounts are provisioned only by a superadmin (/admin/users) or the scripts/create-superadmin.mjs bootstrap.
  • AuthZ: role-based, enforced in two layers:
    1. Database RLS — every table has Row Level Security enabled. Role checks go through has_staff_role(...); superadmin satisfies every check (wildcard).
    2. App route guardsrequireStaff, requireManifestAccess, requireSuperadmin (lib/auth/roles.ts) gate server components and actions.
  • Roles: cashier (booth agent), purser (manifests), ops, admin, superadmin. Least privilege: a cashier can sell tickets and view the manifest of a voyage via a SECURITY DEFINER RPC, but has no blanket read of the passenger PII table.

Data protection & privacy

  • Passenger PII (manifest data in booking_passengers) is regulated under the Philippine Data Privacy Act of 2012 (RA 10173). Direct table reads are restricted by RLS to purser/ops/admin/superadmin; booth agents reach only per-voyage data through a scoped RPC. Minimize collection, and define a retention/disposal policy before go-live.
  • No PII in URLs or logs. The customer booking-lookup flow keeps the contact email in an httpOnly cookie rather than the query string.
  • Secrets. SUPABASE_SERVICE_ROLE_KEY and TICKET_SIGNING_SECRET are server-only and must never be prefixed NEXT_PUBLIC_ or imported into client code. Only the Supabase URL and anon key are public. Secrets live in environment variables (Vercel / .env.local, which is git-ignored).

Transport & HTTP security headers

Targets securityheaders.com Grade A. Set in next.config.ts + middleware.ts:

Header Value
Strict-Transport-Security max-age=63072000; includeSubDomains; preload
Content-Security-Policy strict, per-request nonce, no unsafe-inline
X-Content-Type-Options nosniff
X-Frame-Options DENY (+ CSP frame-ancestors 'none')
Permissions-Policy camera/mic/geo/payment/topics disabled

Accepted exception: Referrer-Policy is intentionally omitted per project requirement. This is a deliberate, documented deviation (CLAUDE.md §8); add strict-origin-when-cross-origin if a stricter grade is later required.

Application integrity

  • No overselling. All seat holds go through an atomic, conditional UPDATE ... WHERE remaining >= n inside SECURITY DEFINER SQL functions (create_reservation, create_walkin_booking) in a single transaction — never a read-then-write in app code.
  • Input validation. Every server action and route handler validates input with Zod before use. Treat all client input as hostile.
  • SQL safety. Data access goes through the Supabase client / parameterized RPCs; no string-concatenated SQL.
  • Payments. Over-the-counter payments are recorded with the cashier's id and the OR (Official Receipt) number for auditability.

Operational guidance

  • Rate limiting. Apply rate limits to auth and ticket-selling endpoints (e.g. Upstash Redis) before production.
  • Service-role usage. The service-role client (lib/supabase/admin.ts) bypasses RLS and is used only for trusted server tasks (user provisioning, scheduled jobs). Keep its blast radius small and audited.
  • Dependencies. Keep next, @supabase/*, and other deps patched; review pnpm audit output regularly. Note: @supabase/ssr must stay compatible with the installed @supabase/supabase-js (typed-client generics).
  • Backups & least privilege. Use Supabase's automated backups; grant database/admin access on a need-to-have basis; rotate keys if exposure is suspected.

Supported versions

The main branch receives security fixes. Pin and update dependencies via the lockfile.

There aren't any published security advisories