Security at Distribu
The short, honest version. Every claim on this page is wired to something real in the codebase — no vapor, no badges we haven't earned.
No formal certifications yet.Distribu isn't SOC 2 or ISO 27001 certified. We'll say so here the day that changes. If your procurement process requires one, email security@distribu.app and we'll be straight with you about where we are.
Authentication
- Password-based sign-in
- Staff and storefront customers sign in with email + password. Passwords are hashed with
bcryptjsbefore they touch the database — the plaintext is never logged, never stored, and never included in a session token. - Email verification
- New accounts must click a single-use link mailed to the registered address before gaining access. Links are delivered via our transactional provider and expire after use.
- Password reset
- Reset tokens are hashed before persistence, single-use, and time-limited. Requesting a reset for an unknown address returns the same response as a known one so enumeration attacks can't be used to harvest emails.
- Session cookies
- Session cookies are
httpOnly,sameSite=lax, andSecurein production. Tenant staff sessions are JWT-backed and scoped to the signing secret; storefront customer sessions are a separate cookie so the two trust boundaries can't leak into each other. - Brute-force protection
- Login attempts are rate-limited to 10 per 15 minutes per IP. Signups are capped at 5 per hour per IP. Contact and feature-request forms sit behind the same limiter.
Authorization
- Role-based access control
- Every workspace has three roles — OWNER, ADMIN, and MEMBER — enforced on every server action, page load, and API route via a shared
requireRole()guard. Route-level enforcement means a hand-crafted URL can't skip the check. - Multi-tenant isolation
- Every tenant-facing row carries a
companyIdand every query is scoped to the caller's current company. There is no cross-tenant lookup anywhere in the product surface — a compromised session can't enumerate other workspaces. - Super-admin separation
- Distribu staff access lives on an orthogonal
isSuperAdminflag on the user record, not a role inside a tenant. Admin pages callrequireSuperAdmin()directly so server actions stay gated even if a layout-level check is bypassed. - Scoped API keys
- Public-API keys are issued with one or more of nine scopes (
products:read,orders:write, etc.). A key can only perform the actions its scopes allow, and scopes are checked on every request — not just at key creation.
Audit logging
- Everything that matters is recorded
- Product edits, stock movements, order status changes, returns, refunds, member invites, role changes, API-key creation and revocation, webhook secret rotation, subscription changes — every material action writes a row to an append-only
AuditLogtable with the actor, the entity, the timestamp, the IP, and a structured metadata payload. - System actors are explicit
- Audit entries distinguish between
USERactors (a logged-in staff member or customer) andSYSTEMactors (webhooks, scheduled jobs, Stripe-triggered events). You can see at a glance whether a change was human-driven. - Exportable
- Workspace admins can view and export the full audit log from the dashboard. The schema is stable enough to ingest into your SIEM of choice.
Rate limiting
- Public API
- 60 requests per minute per API key, 120 per minute per IP, whichever you hit first. Every response includes standard
X-RateLimit-*headers so clients can back off gracefully. - Auth surfaces
- Login, signup, password reset, and email verification each have independent limiters tuned for their risk profile.
- Backend
- Production limits run on Upstash Redis via a sliding-window algorithm. Development falls back to an in-memory bucket so local testing stays fast without a Redis dependency.
Webhook security
- HMAC-signed payloads
- Every outbound webhook carries an
X-Webhook-Signatureheader computed as HMAC-SHA256 over the payload plus a timestamp. Receivers should verify the signature and reject timestamps more than a few minutes old. - Secret rotation with grace period
- Rotating a webhook secret keeps the previous secret valid for 7 days and includes both signatures in outbound requests (
X-Webhook-SignatureandX-Webhook-Signature-Old), so you can roll your verifier at your own pace without dropping events. - Delivery retries with backoff
- Failed deliveries are retried automatically with exponential backoff up to a bounded cap. Every attempt and response is stored on the
WebhookDeliveryrecord so you can replay or diagnose failures from the dashboard. - Bounded timeouts
- Outbound webhook requests time out after 10 seconds. A slow receiver won't stall other deliveries or hold a function instance open.
Data protection
- Database
- Postgres, hosted on Neon via the Vercel Marketplace. Traffic between the application and the database is TLS-only; backups are managed by the provider.
- API key storage
- We store the SHA-256 hash of each API key plus its first eight characters for UI display. The plaintext key is shown to the user exactly once at creation — even Distribu staff cannot recover a lost key. Keys can be revoked instantly and carry an optional expiry.
- No card data on our servers
- All payment-card data is handled directly by Stripe via their hosted elements. Distribu never sees, stores, or logs a card number, expiry, or CVC. This keeps us out of PCI scope beyond the SAQ A tier.
- Idempotent payment webhooks
- Stripe webhook events are deduplicated at the event-id level before any side effect runs, so a retried Stripe delivery can't double-apply a subscription change.
Transport & headers
- HTTPS-only
- All production traffic is HTTPS. HSTS is served with a two-year max-age,
includeSubDomains, andpreload. - Browser defences
- We set
X-Frame-Options: DENY,X-Content-Type-Options: nosniff,Referrer-Policy: strict-origin-when-cross-origin, and aPermissions-Policythat disables camera, microphone, geolocation, and interest-cohort tracking. - CSRF
- Mutations run through Next.js server actions, which require a same-origin request and a valid session cookie. Session cookies are
sameSite=laxso third-party sites can't forge a logged-in request against your workspace.
Infrastructure & subprocessors
- Hosting
- The application runs on Vercel. Edge DDoS mitigation, TLS termination, and automatic deploy rollbacks are inherited from the platform.
- Subprocessors
- We use a deliberately small set of third parties, all named in our privacy policy: Stripe (payments), Resend (transactional email), Neon (Postgres), Upstash (rate-limit state), and Vercel (hosting). Adding a new subprocessor triggers a privacy-policy update and a changelog entry.
- Secrets
- All secrets — database URLs, Stripe keys, webhook signing secrets, cron bearer tokens — live in Vercel's encrypted environment variables. None are committed to the repository; pull-request previews use isolated preview values.
Responsible disclosure
Found something? Email security@distribu.app. We'll acknowledge within one business day, keep you in the loop while we fix it, and credit you in the changelog on request. We don't currently run a paid bug-bounty, but we won't send you a cease-and-desist for finding a real issue in good faith.
Please avoid testing against other workspaces, exfiltrating data beyond what's needed to demonstrate the bug, or running load / fuzz tests against production without coordinating with us first.
Questions we haven't answered?
We'd rather tell you the boring truth than pitch you. Email security@distribu.app or start a free trial and poke around.
