# Security controls — HeavyHaul Agent

Last reviewed: 2026-09-22. Owner: Nash Turcan. This is the control map an
auditor (SOC 2 Security criteria / ISO 27001 Annex A) asks for, with where
each control lives and how it is operated. Keep it current when controls change.

## 1. Identity and access

| Control | Where | Notes |
|---|---|---|
| Accounts provisioned by the administrator only | `AUTH_USERS` env + `auth_accounts` overrides | No self-serve signup. Admin console at `/admin/users`. |
| Passwords hashed | `src/lib/auth/passwords.ts` (scrypt, N=2^15) | Admin-set passwords replace env passwords; env passwords must be rotated when a person leaves. |
| Sessions | `src/lib/auth/session.ts` — signed JWT, 7 days, `httpOnly`, `secure`, `sameSite=lax` | A password reset or role change sets `tokens_valid_after`, ending every earlier session. |
| Sign-in brute-force protection | `src/app/(auth)/actions.ts` + `src/lib/security/rate-limit.ts` | 10 failures per username or 30 per IP in 15 minutes → locked for the window; generic error either way. |
| Multi-factor authentication (TOTP) | `src/lib/security/totp.ts`, `/settings` Security card, `/login` second step | Required for admin accounts; optional for others; admins can reset it. |
| Authorization on the server | `src/lib/api-guard.ts`, `requireParticipant`, `canManageCarrierCompany`, `requireRoleRoute` | Every API op re-checks the caller's participant / membership / permission; UI never gates alone. |
| Role isolation | `src/lib/page-context.ts`, `src/lib/role-route.ts` | Each user type has its own page group; cross-role access is admin-only for testing. |
| Access reviews | `/admin/users`, `security_events` | Quarterly: list accounts, remove leavers from `AUTH_USERS`, revoke company memberships, review `role_switch` and `admin_*` events. |

## 2. Data protection

| Control | Where | Notes |
|---|---|---|
| Database access | Supabase Postgres, RLS enabled on every table with no policies | Only the server's service-role key can read or write; the browser never holds a database key. |
| Encryption in transit | HTTPS (Apache TLS) + `Strict-Transport-Security` + proxy redirect of `x-forwarded-proto: http` | See §5 for the Apache rule. |
| Encryption at rest | Supabase (AES-256 managed disks) and AWS S3 (SSE-S3) | Vendor-managed; confirm in each vendor's trust page during vendor review. |
| Files | Private buckets only; short-lived signed URLs (30 min) | `src/lib/data/document-urls.ts`, `src/lib/storage/s3.ts` (locked to one bucket and region). |
| Secrets | `.env` on the server only; never in the repository | `git log` contains no `.env`; `.env*` is ignored. Rotate `AUTH_SECRET`, Supabase service key, AWS keys, ZeptoMail token on any suspected exposure. |
| Emails to demo addresses | `src/lib/data/carrier-company.ts` (`isDemoAddress`) | `.local`, `.test`, `.example` addresses are recorded, never sent. |

## 3. Logging and monitoring

| Log | Table | What |
|---|---|---|
| Security events | `security_events` (migration 0026) | sign-in success/failure/lockout, sign-out, role switch, admin password reset / role change, MFA enrol / reset, API rate limits. |
| Trip audit | `trip_events` | every workspace action with actor and detail. |
| Company audit | `company_audit_log` | relationships, invitations, approvals, profile and official-data changes. |
| Verification emails | `verification_emails`, `email_events` | what was sent, to whom, which template version, result. |
| Unit photos | `pilot_unit_photo_log` | who uploaded / replaced / removed which photo. |

Operate: review `security_events` weekly for `login_locked`, `admin_*` and
`api_rate_limited`; keep 12 months of history (Postgres retention job — to do).
Server-side errors go to PM2 logs on the host (`pm2 logs heavyhaulgbt-app`);
central log shipping is not in place yet.

## 4. Change management

- Every push runs tests and lint (`.github/workflows/ci.yml`).
- Dependabot opens weekly update PRs (`.github/dependabot.yml`); `npm audit` is part of release checks.
- `CODEOWNERS` routes every change to the owner. **Enable branch protection on `main`** (GitHub → Settings → Branches → Add rule): require a pull request before merging, require the "Tests and lint" status check, block force pushes and deletions. Until the team has a second reviewer, "require approvals" can stay at 0 — the PR itself is the record.
- Deployments: `./deploy.sh` on the host or the cPanel bundle (`docs/CPANEL_DEPLOY.md`). Record each deploy (date, commit, who) in `docs/DEPLOY-LOG.md`.
- Database migrations live in `supabase/migrations/`; each has a `scripts/verify-migration-00xx.mjs` check. Apply in order, verify, then deploy the code that needs it.

## 5. Hosting (Apache + PM2 on cPanel)

Force HTTPS at the edge as well as in the app. In the site's `.htaccess`:

```apache
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
RequestHeader set X-Forwarded-Proto "https" env=HTTPS
```

The app adds `Strict-Transport-Security`, `Content-Security-Policy`,
`X-Frame-Options: DENY`, `X-Content-Type-Options: nosniff`,
`Referrer-Policy`, `Permissions-Policy` on every response (`next.config.ts`).

## 6. Backups and recovery

- Supabase: daily automated backups on the Pro plan; enable Point-in-Time
  Recovery for 7-day granularity. **Test a restore to a scratch project every
  quarter** and note the date here.
- S3: enable bucket versioning on `heavy-haul-agent` so an overwritten or
  deleted file can be recovered for 30 days.
- Code: GitHub is the source of truth; the server holds only a checkout.
- Last restore test: _not yet performed_.

## 7. Vendors (sub-processors)

| Vendor | Data | Agreement |
|---|---|---|
| Supabase | all application data, legacy files | DPA available in dashboard — sign |
| AWS S3 | uploaded documents, permits, photos | AWS DPA (automatic under the service terms) |
| ZeptoMail (Zoho) | outbound email, recipient addresses | Zoho DPA — sign |
| Synchron Permits | permit orders, carrier records (read-only pull) | contract in place; API GET/HEAD only |
| HeavyHaul GPT service | trip and permit data for AI features | internal service — document ownership and access |
| GitHub | source code | GitHub terms |

Review yearly; record the date of each review here.

## 8. Incident response (short form)

1. Contain: rotate the exposed secret (`AUTH_SECRET` ends all sessions), revoke
   the account (`/admin/users` → password reset), block the IP at Apache if needed.
2. Assess: pull `security_events`, `trip_events`, `company_audit_log` for the window.
3. Notify: affected customers within 72 hours if their data was involved; keep a
   written timeline.
4. Fix and record: root cause, change made, date, in `docs/INCIDENTS.md`.

## 9. Still to do for a SOC 2 Type I

- Central log shipping and alerting (PM2 → hosted logs).
- Branch protection turned on (see §4).
- Documented, tested backup restore (§6).
- Written policies: access control, acceptable use, onboarding / offboarding,
  vendor management, incident response (this file is the seed).
- Risk assessment and asset inventory.
