Security posture
A snapshot of what's in place today. Designed to answer the questions a customer's auditor typically asks: how is access authenticated, how is data encrypted, where does customer data live, what happens when a key is rotated, what's logged for forensics.
Transport and perimeter
- HTTPS everywhere with HSTS preloaded, both the marketing site and the API. HTTP requests in production are rejected; in development they redirect.
- Security headers on the landing site (and inherited where applicable): Content-Security-Policy, X-Frame-Options: DENY, X-Content-Type-Options: nosniff, Referrer-Policy: strict-origin-when-cross-origin, Permissions-Policy. Configured at the nginx server-block level so they apply to all responses including 4xx / 5xx pages.
- CORS allowlist on admin and portal session routes, fails closed if the operator forgets to configure the allowed origins, rather than silently defaulting to wildcard. Public storefront API (
/api/t/*) is intentionally open since it's protected by API key, not by CORS. - Per-tenant allowed-domain restriction on storefront API, tenants can pin their API key to a specific origin from their portal. Browser requests with a leaked key from a different origin get rejected with 403.
Authentication and authorisation
- Modern authentication framework for admin and customer-portal sessions. Email + password with a 12-character minimum and a digit + uppercase requirement. Sessions are cookie-based with HttpOnly, Secure, SameSite=Lax flags. TOTP-based two-factor authentication is opt-in self-service on both surfaces: admin Settings and customer-portal Settings → Account both expose the same enrollment flow (password gate, QR / URI, single-use backup codes, sign-in challenge). Recommended for every account; strongly recommended for owner and admin roles where a single password compromise would expose the whole tenant.
- X-API-Key for storefront and integrations endpoints. Keys are tenant-scoped, one key grants access to one tenant's data only. Rotatable from the admin without downtime; a rotation invalidates the previous key on next request.
- Admin vs tenant-user separation is enforced server-side, not just by URL routing. An admin account is structurally distinct from a tenant user at the database level; admin endpoints reject any session whose user is linked to a tenant, even with a valid cookie.
Application-layer hardening
- HTML sanitisation on every write to fields that accept user-supplied HTML (currently only product descriptions). Industry-standard sanitiser with a tight whitelist, paragraphs, headings, lists, basic formatting, links restricted to http/https/mailto, images restricted to https. Scripts, iframes, event handlers, javascript: URLs, and data: URLs are stripped.
- Audit log captures every state-changing API call across admin, customer-portal, and integrations surfaces. Append-only, queryable in the admin. Actor type (ADMIN / TENANT_USER / INTEGRATION / UNKNOWN) plus actor id, email, tenant, method, path, status, duration, IP. Reads are not logged (volume).
- Type-safe ORM parameterises every database query, SQL injection is structurally prevented, not just defended against.
- Server-side payload validation on every write endpoint, type-checked + shape-checked before reaching the database layer. Malformed payloads return 400 with a clear error.
Storage and data
- EU-based object storage for product images and manuals. Bucket versioning enabled (forward-protection from 2026-05-12 onwards), accidental overwrites and deletes are recoverable.
- Browser uploads go through pre-signed PUT URLs, so the API never proxies the asset body. Reduces attack surface and bandwidth cost.
- Nightly encrypted database backup to a separate EU-based storage location at 02:30 UTC daily. Independently monitored, a missed backup pages immediately.
- SSH key-auth only on the host server. Password auth is disabled.
Host
- EU GDPR-friendly hosting in Helsinki, ISO 27001-certified data centre.
- Live kernel patching + extended security maintenance via Ubuntu Pro. Kernel CVEs apply in-memory without reboot; security updates extend ~10 years vs. the standard 5.
- Internal-network hostname resolution between API and database so container restarts during patches don't trigger downtime or require manual configuration changes.
Where data lives and what's processed
All customer data is stored in the EU, database and object storage are EU-based, backups stay in the EU. We don't ship data to US-based services for processing. Third parties that touch customer data (sub-processors):
- Hetzner
- EU hosting (compute, storage, backup). Frankfurt + Helsinki. GDPR-aligned, ISO 27001 certified.
- Resend
- Transactional email (password resets, admin-side communications). EU-aware.
- Sentry
- Error tracking. Configured to scrub PII from event payloads before sending.
- Mapbox
- Only relevant for customers using the dealer-map widget on their storefront. Customer's own Mapbox account; we don't proxy or store map queries on the PowersportOS backend.
Recent hardening work
- Q2 2026: Internal security audit + seven hardening fixes. Notable items: CORS fail-closed behaviour on misconfiguration, HTML-sanitisation policy tightening, admin-vs-tenant-user separation enforcement.
- Q2 2026: Object-storage bucket versioning enabled across all customer-facing assets. Security headers added to the landing site. HSTS preloading prepared.
- Q2 2026: Reboot-resilience hardening, wait-for-db gate + internal DNS-based addressing eliminating the "API stuck on database-unreachable" outage class. Live kernel patching attached on the host.