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 bcryptjs before 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, and Securein 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 isSuperAdmin flag on the user record, not a role inside a tenant. Admin pages call requireSuperAdmin() 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 AuditLog table with the actor, the entity, the timestamp, the IP, and a structured metadata payload.
System actors are explicit
Audit entries distinguish between USER actors (a logged-in staff member or customer) and SYSTEM actors (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-Signature header 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-Signature and X-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 WebhookDelivery record 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, and preload.
Browser defences
We set X-Frame-Options: DENY, X-Content-Type-Options: nosniff, Referrer-Policy: strict-origin-when-cross-origin, and a Permissions-Policy that 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.