# Security

## Design summary

**Tenant isolation**
- Each business has its own database and its own database user, with rights on that database only.
- The POS picks the business from the sub-domain before any code runs.
- Database passwords are AES-256-GCM encrypted in the registry and encrypted at rest in the central database. They are never shown in any page.

**Passwords**
- Staff: bcrypt.
- POS: legacy MD5 is upgraded to bcrypt at each user's next sign-in. Temporary passwords are shown once and must be changed at first sign-in.

**Sessions**
- HttpOnly and Secure cookies with SameSite=Lax.
- A new session ID on sign-in; rotation every 5 minutes (POS).
- 30-minute idle timeout for staff; 2FA required for Super Admin, Operations and Billing roles.

**CSRF**
- On all Super Admin forms, and on every POS form and AJAX request.
- Exclusions: the key-authenticated API, payment-processor callbacks and cron.

**Authorisation**
- Every Super Admin route checks a permission on the server.
- POS plan gates are enforced in the backend: permission checks, model saves and the REST API.

**API**
- Per-business keys (stored as SHA-1); the Kaabe platform key is recognised by a fingerprint the business cannot change.
- Module and limit checks on every request.

**Audit and secrets**
- Every staff and provisioning action is recorded with time, user, IP and business; passwords and keys are never logged.
- Error messages pass through a secret sanitiser.

**Production database protection**
- `KAABE_PROTECTED_DATABASES` (default `ssgplatforms_deeq`) can never be created, changed or dropped by provisioning.
- Adoption of it requires the exact approval phrase.

## Verified in 1.0.0 (executed, see TESTING-REPORT.md)

| Control | Super Admin | Business POS |
|---|---|---|
| HTTPS only | 301 HTTP→HTTPS, HSTS | 301 HTTP→HTTPS, HSTS |
| Cookies | Secure, HttpOnly, SameSite=Lax | Secure, HttpOnly, SameSite (CSRF cookie Strict) |
| Session id rotated at login | yes | yes |
| CSRF | every POST (419 without token) | every POST/AJAX (403 without token); state-changing GET links need a confirmed POST |
| Headers | CSP, HSTS, X-Frame-Options DENY, nosniff, Referrer-Policy | HSTS, X-Frame-Options SAMEORIGIN, nosniff, Referrer-Policy, Permissions-Policy (no CSP: the vendor UI uses inline scripts) |
| Passwords | bcrypt, min 12 chars mixed case + digits | bcrypt; legacy MD5 upgraded at next sign-in; temporary passwords must be changed at first sign-in |
| Brute force | 10 logins/min, 6 2FA codes/min | 5 failures per username (20 per IP) / 15 min → 15 min lock |
| 2FA | TOTP, required for Super Admin | – |
| Authorization | role permissions (tested: Support Agent gets 403 on settings, staff, roles, plans, provisioning, suspension) | POS roles; plan module gates in backend, menu and API |
| Tenant isolation | – | host → own database + own DB user; API keys valid only in their business; unknown/spoofed hosts 404 |
| Suspension | – | 403 immediately for pages, sessions and API |
| Secrets | `.env` 640; registry encrypted (AES-256-GCM); cPanel token and passwords never in errors/logs | same |

Accepted residual items: the POS UI has no Content-Security-Policy (vendor inline scripts); cart state and UI "dismiss" flags still change on GET (affect only the user's own screen).

## Checklist (before going live)
- [ ] `APP_ENV=production`, `APP_DEBUG=false`, `CI_ENV=production`
- [ ] `APP_KEY`, `KAABE_REGISTRY_KEY` (64 hex) and `KAABE_ENCRYPTION_KEY` are unique random values, stored in your password manager (losing `APP_KEY` makes the encrypted database passwords unreadable)
- [ ] `.env` files at 640; `private/` at 750; nothing from `kaabe/` inside `public_html`
- [ ] HTTPS on all domains; *Force HTTPS Redirect* on
- [ ] All staff have 2FA; the demo accounts don't exist (`KAABE_DEMO=false`, the default)
- [ ] The cPanel API token is stored only in `platform/.env`; revoke it if the server is compromised
- [ ] **The old `ssgplatforms_deeq` database password (exposed in the old code) has been changed** before adoption
- [ ] The backup cron runs, and backups are copied off the server
- [ ] The PHP POS files are not writable by the web server except `application/cache` and `application/logs`
- [ ] `https://<business>.<your-domain>/kaabe/.env` and `/database/database.sql` return 403
