# Security rules Required security controls for TravelHub. These are not suggestions — a change that breaks one is rejected. They exist because this is a **multi-tenant** system holding traveller PII (names, passport numbers, contact details) where a single leaked `WHERE` clause exposes every tenant. Findings from the whitebox assessment and their status are in [`security/VAPT-REPORT.md`](security/VAPT-REPORT.md). ## 1. Tenant isolation - The tenant id is derived from the **authenticated identity**, never from the URL, query string, header value, or request body. A client cannot choose or forge the tenant it acts as. - Every query against a tenant-owned table (`users`, `flights`, `bookings`, `passengers`) filters on `tenant_id` as its first parameter. No exceptions. - A miss within the tenant returns **404**, never 403 — existence of another tenant's record must not be observable. - **No PostgreSQL row-level security.** Isolation is enforced in the repository layer by the rules above (see [CLAUDE.md](CLAUDE.md) for the rationale). At scale, prefer schema- or database-per-tenant, not RLS. ## 2. Secrets - **Never commit a secret.** `.env` and `*.env` are git-ignored; only `.env.example` (placeholders) is tracked. Credentials, tokens and DB passwords live in `.env` and are read at runtime. - The `pre-commit` hook scans staged content for credential patterns and blocks them. Do not bypass it (`--no-verify`) to commit a secret. - **Never print or log a credential.** The e2e API-capture redacts the `Authorization` header (it is the Basic-auth secret); keep it redacted in any report or log. - **If a secret is ever committed or exposed, rotate it immediately** — assume it is compromised. Scrubbing history is not enough on its own. ## 3. Authentication & credential storage - Passwords are hashed with **bcrypt in application code** at cost ≥ 12. - The real hash and the not-found timing-dummy hash must use the **same cost** — a mismatch creates a username-enumeration timing oracle (VAPT FINDING-2). - 401 responses omit `WWW-Authenticate` (so the browser dialog doesn't fight the SPA sign-in); a missing user still pays for a bcrypt comparison (timing). - Authentication endpoints must be **rate-limited** before any public exposure. ## 4. Input handling - **All SQL is parameterised** (`$1…$n`). Never concatenate a value into SQL. Anything that must reach the SQL text (e.g. a sort column) is whitelisted. - JSON request bodies use `DisallowUnknownFields` (blocks mass-assignment) and are size-capped (`http.MaxBytesReader`). - Validate and normalise free-text on input; treat the API as the trust boundary, not the client. ## 5. Transport & least privilege - Any non-local database connection uses **TLS** (`sslmode=verify-full`); `disable` is only for loopback dev. - The running service connects to PostgreSQL as a **least-privilege role** (DML only), never a superuser. Migrations run under a separate, higher privileged role the service never uses. ## 6. Dependencies & headers - Run dependency scanning in CI: `govulncheck` (Go) and `npm audit` (frontend). - Responses carry baseline security headers (`nosniff`, `X-Frame-Options`, `Referrer-Policy`, `Cache-Control: no-store` on authenticated JSON, HSTS behind TLS); the SPA ships a restrictive CSP. ## 7. Data handling - Treat passenger PII as sensitive. Do not log it; do not put real PII in seed or test data (use synthetic values). - Reports (evidence, test) may contain synthetic test data only. ## Reporting a vulnerability Raise security issues privately with the repository owner (do not open a public issue). Include steps to reproduce and the affected files.