Multi-tenant isolation
In plain English: every brokerage on HatchOS is a separate tenant, and the system is engineered so that no query, no API endpoint, and no business process can ever return data from one tenant into another's session — even if a UI bug exposes a foreign record ID. The audit closed 9 isolation items in Jun–Jul 2026.
153 tenant-scoped tables
Every entity (shipments, carriers, invoices, commissions, emails, accounts, dispatch bids) carries tenant_id and every query filters on it. The middleware-injected scope is enforced server-side — not derived from the session alone.
withTenantClient — RLS-ready
The withTenantClient() helper in middleware/tenant-context.js sets app.current_tenant_id so Phase-1 Postgres row-level security policies fire even on raw pool calls. Every protected route passes through it.
Tenant-scoped stats & KPIs
CRM dashboards, dispatch KPIs, commission totals, carrier scorecards — every aggregate query threads the request tenant_id through SQL parameters. No global counter leak. Audit item #14 verified zero cross-tenant joins during closeout.
Dispatch bid tenant verification
The dispatch bid path (server.js) re-verifies the bid's tenant_id against the request session before accept or counter — even if the bid record was created under a different tenant context. Audit item #22.
WHERE tenant_id = $ clause in the codebase.
Authentication & access control
Login is hardened at every layer. Weak defaults are absent — not just discouraged. Audit items #1–#11 covered role enforcement, 2FA, session revocation, and cookie security.
Cannot impersonate other tenants (God Mode only).
Cannot access other agents' accounts, billing, or admin settings unless explicitly granted.
Cannot view other carriers' loads, broker financials, or end-customer data.
Cannot view other customers' data, broker financials, or carrier operations.
-
2FA — email OTP, TOTP, recovery codes. Three methods backed by
routes/two-factor-auth.js. Enforced for God Mode platform access. Recovery codes are bcrypt-hashed at rest. Audit items #4–#5. -
Session-version revocation. Each
users.session_versionINT (added in migration 1788300000) is checked on every authenticated request — bump it and every active session for that user dies immediately. Used after forced logout, password reset, and account suspension. Audit item #3. -
Secure cookies + 7-day session TTL. Express-session configured with
maxAge: 7 * 24 * 60 * 60 * 1000. Cookies areHttpOnly,Secure,SameSite=Laxin production. Audit items #7–#8. -
Fail-closed auth middleware. On database errors during session lookup,
auth-context.jsreturns 401 rather than degrading to anon access. We do not serve protected routes when the auth backend is unreachable. Audit item #11. -
Rate-limited authentication endpoints. Login, signup, OTP verify, and forgot-password each have a sliding-window rate limiter (
middleware/rate-limit.js). Lockouts write tosecurity_events. New-IP logins flagauth_anomaly_events. Audit items #9–#10.
Payments & financial security
Money in motion is the highest-risk surface. HatchOS uses Stripe Connect Standard with broker-controlled sub-accounts — HatchOS itself never holds broker funds, and every inbound Stripe event is signature-verified before it touches a single database row. Audit items #12–#17.
-
Stripe Connect Standard for carrier payouts. Each brokerage onboards its own Stripe Connected Account. HatchOS originates transfers into those accounts; we never pool broker funds, never hold escrow, and never become the merchant of record for carrier settlements.
STRIPE_SECRET_KEYis required at boot. -
Stripe webhook signature validation. The server rejects
POST /api/webhooks/stripewithout a valid HMAC againstSTRIPE_WEBHOOK_SECRET. Missing secret fails the process at boot (server.js:127–129) so a misconfigured deploy can't accept forged events. Audit item #12. -
Event-ID dedup. Every Stripe event is recorded in
stripe_webhook_eventsbefore downstream side-effects. Re-deliveries are detected by Stripe event ID and short-circuited — a stuck Stripe retry loop cannot double-pay a settlement. Audit item #13. - No freight-invoice payment-link exposure. Customer-facing invoice PDFs include a Stripe-hosted payment link, not raw card fields. We never see card numbers. PCI scope is minimized to SAQ-A burden for both HatchOS and the brokerage.
-
Subscription & reconciliation webhooks are separate, both verified. Subscription lifecycle events hit
/api/stripe/subscription-webhookwith its ownSTRIPE_SUBSCRIPTION_WEBHOOK_SECRET. A daily reconcile job rolls forward any missed events.
Data protection
Data at rest, data in motion, data being verified. Three different controls — each enforced independently.
-
Postgres over SSL. The connection pool enforces TLS to Neon. Database connections are encrypted in transit — even within Render's private network. Local-development connections
localhostskip TLS but production traffic does not. Audit item #18. - Encrypted at rest via Neon platform. Postgres data, WAL, backups, and replicas are encrypted at rest by Neon with AES-256. HatchOS does not operate a separate KMS layer — provider-managed encryption is auditable and reflected in Neon's SOC 2 Type II report.
-
Encrypted session store & credentials. Session records live in Postgres with the same SSL transport. OAuth tokens, API credentials, and integration secrets are encrypted with
ENCRYPTION_KEYusing AES-GCM — process-exits at boot ifENCRYPTION_KEYis missing or set to a known-insecure default. Audit item #19. -
File uploads → Cloudflare R2. Carrier documents, BOLs, COI packets, customer logos, and generated PDFs flow through
services/r2.js. Never committed to source. Endpoints are authenticated; no public listing. -
USPS address verification over OAuth 2.0. Stop entry on shipments and tender intake verifies addresses via USPS's OAuth-gated address API. We do not keep USPS credentials in source — they live as
USPS_CLIENT_ID/USPS_CLIENT_SECRETenv vars.
Infrastructure hardening
Boot-time guards, no-questions-asked. If a required secret is missing, the process exits before serving the first request.
| Control | Implementation | Source |
|---|---|---|
| SESSION_SECRET | Boot fails if missing or set to a known-insecure default. Required for cookie signing. | Live |
| ENCRYPTION_KEY | Boot fails if missing or set to a known-insecure default. Required for OAuth/credential encryption. | Live |
| STRIPE_WEBHOOK_SECRET | Boot fails if missing. Without it, Stripe webhooks would be unverified and forgeable. | Live |
| Cron auth — Bearer tokens only | Scheduled jobs (outreach tick, Ava, Quinn, Nia, etc.) read secrets from a Bearer header. No secrets in query parameters. | Live |
| Rate limiting (auth) | In-memory sliding-window: 5 login attempts / 15 min, 3 signup attempts / hour, 3 forgot-password / hour, 5 OTP verify attempts / 10 min. Locks logged to security_events. |
Live |
| Rate limiting (public) | 20 req/min/IP on the public demo surface, 60 req/min/IP on the public demo-click tracker. | Live |
| Upload caps | CSV and JSON imports capped at 5 MB via Multer. Only text/csv / application/json MIME types accepted. Carrier & customer import wizards reject everything else. |
Live |
| AI employee sandboxing | AI employees execute in isolated sandbox providers with allowlist-only environment construction. Production credentials (DATABASE_URL, Render tokens) are not in their env. |
Live |
Audit & observability
If you can't see who did what, you can't answer a security questionnaire. HatchOS writes an immutable audit trail for every privileged action and exposes a health probe you can put in your monitoring stack.
Audit log on every dispatch mutation
Create, status-change, accept, post-Settlement — every dispatch write passes through middleware/audit-logger.js and writes audit_log with user_id, role, action, target, JSONB details, client IP. The rows are append-only.
Health check with DB probe
/health runs SELECT 1 against Postgres. Returns 503 with diagnostic JSON if the connection fails. Suitable for an external uptime monitor — returns within the Render pinger's timeout.
Startup diagnostics endpoint
Boot-time checks are logged to lib/startup-log with category tags (env.check, db.acquire, env.warn). Every guard failure leaves a structured trace audit-able post-incident.
Impersonation audit, separated from login events
God Mode login to a tenant posts both a platform_impersonation_log session row (start/end) and a per-request impersonation_audit row (per API hit). The two are immutable and never overlap with regular audit_log.
- Webhook event lifecycle stored. Every Stripe webhook event is recorded by event ID before downstream effects fire — so an audit can reconstruct exactly what Stripe said, when.
-
Email health event sink (6 pipelines). Postmark open/click/bounce events for transactional, marketing, dunning, activation, nurture, and cold outreach flow through one
email_health_eventstable — easy to correlate a complaint back to the originating send.
AI safety
HatchOS ships seven autonomous AI employees (Hatch, Dash, Mark, Lena, Shield, Rex, Knox). They make decisions — but never without guardrails, and never without a human override path. Audit items #26–#32 in Jun–Jul 2026.
Quinn — margin-floor guardrails
Quinn auto-quotes against a tenant-configurable margin floor. Quotes below the floor never auto-send — they escalate to ai_employee_escalations for human review. Audit item #27. Per-tenant daily cap on Quinn auto-sends prevents a runaway batch.
Ava — human-in-the-loop on AP payouts
Ava groups approved bills into a payment run, but only posts after the tenant-approved threshold. Bills above the threshold require explicit user approval. The run is idempotent — re-entrant crons cannot double-post. Audit item #29.
Tenant-scoped model prompts
Every AI employee prompt includes the active tenant_id in context. Cross-tenant information is structurally absent from the prompt. Tenant isolation is enforced at the LLM-call level, not just at the data layer. Audit item #28.
Shield — Magica MCP COI extraction
Shield extracts carrier COI fields via Magica vision. Extraction is a suggestion; the ai_employee_escalations row is opened for human review before any dispatch_carriers row is mutated. Audit item #30.
ai_employee_escalation row. Resolved rows are visible to operators; unresolved ones surface in the God Mode dashboard. Killing an autonomous run is a single PATCH.
ai_employee_runs.cost_usd, migration 1791800000). Cost anomalies are visible in the analytics dashboard — not just in a vendor invoice.
Download & report a vulnerability
Forward this to your IT department. Or, if you found something we missed, tell us.
Download security summary
One-page PDF — the same audit closeout summary above, distilled for a procurement security review. Suitable for IT, compliance, and finance reviewers. No login required.
Last updated July 2026 · regenerated from the codebase
Report a vulnerability
Found something? Email security@hatchos.com. We acknowledge within 2 business days and do not pursue legal action against good-faith researchers.
Scope: HatchOS production tenants + public APIs, render URLs, and downloadable artifacts.
Out of scope: phishing, social engineering of staff, volumetric denial-of-service, vulnerabilities in third-party platforms we integrate with (report those to the upstream vendor).