Security & data handling

The boring details, written down.

For the dealer who wants to know exactly what we do — and for the IT auditor who'll ask.

Where your data lives

  • 📍 Database: Supabase Postgres, London region (eu-west-2). At-rest disk encryption (AES-256) by default.
  • 📦 Object storage: Cloudflare R2, EU jurisdiction. Customer photos, V5C scans, audit packs.
  • 📨 Messaging: Bird (Amsterdam). SMS bodies mirrored to R2 EU at send time.
  • 💳 Payments: Stripe + Crezco. Your customers' payments flow into your account, not ours.
  • 🚫 Never: any production data in the US.

How users sign in

SMS OTP only. Six-digit code to your registered mobile, valid for 5 minutes, max 3 attempts. Session tokens are httpOnly + Secure cookies — JavaScript on the page cannot read them, which defeats the most common token-theft attack class. Sessions expire after 1 hour and auto-refresh from a 30-day rotated refresh token.

How dealer data is isolated

Every row in every business table carries a dealer_id. PostgreSQL Row Level Security enforces tenant scoping at the database level — a query that tries to read another dealer's data returns zero rows, regardless of the application code. We run an automated cross-tenant test on every code change that attempts to read Dealer B's data using Dealer A's credentials; that test must return zero rows or the deploy is blocked.

We chose this over schema-per-tenant because the operational overhead at our target scale (1,000+ UK independent dealers within 5 years) would dominate before the marginal safety gain of physical isolation materialised. The discipline above is what makes RLS safe.

How your credentials are stored

Your Stripe Connect account ID, Bird API key, Anthropic key, AutoTrader OAuth tokens, HPI subscription key — every third-party credential — is stored encrypted at rest using PostgreSQL pgcrypto with the master encryption key held in Cloudflare Workers secrets. The master key is never loaded into PostgreSQL beyond a per-function-call memory window. Even a service-role key leak would not expose plaintext.

Annual hygiene rotation; immediate rotation on suspected compromise.

How the audit log works

Every meaningful business action — complaint received, payment captured, vehicle sold — writes a row to an append-only events table. Each row's payload contains the full state of the legally-material fields at that moment (not a sparse diff). Each row's current_hash is computed from the previous row's hash + the canonical JSON of the new row's content (SHA-256). This produces a per-dealer hash chain that's mathematically tamper-evident: any modification to a historic row invalidates every hash downstream.

A nightly reconciliation job verifies the chain and checks for drift between the audit log and the row state it describes. Divergence pages an engineer within 24 hours. The same job checks that every regulated relationship has its matching typed-extension row (1:1 enforced at the database level).

FCA / DISP retention: 6 years minimum, configurable longer. HMRC / Margin Scheme retention: 6 years. MLR / AML: 5 years from end of relationship.

How XSS and other browser attacks are prevented

Strict Content Security Policy on every page:

  • No inline <script> tags. Every JS handler is an external file with addEventListener.
  • No onclick= attributes anywhere in our HTML.
  • External origins allowlisted: Stripe.js, Persona embed, Supabase, R2, Bird, Companies House. Nothing else loads.
  • No 'unsafe-inline' on script-src. An XSS payload that injects <script> doesn't execute.
  • HTTP Strict Transport Security enabled. All traffic over TLS 1.3.
  • Browsers cannot frame our site (X-Frame-Options DENY equivalent in CSP).

How customer data is erased on request

Under GDPR Article 17 a customer can ask for their personal data to be erased. We honour this via "soft delete with PII tombstone": the customer's UUID and the audit chain stay intact (required by FCA / HMRC retention), but every PII field — name, email, phone, address, DOB — is set to NULL. Subsequent SARs return only the lawfully-retained metadata. We document the lawful basis for each retained record.

The customer's UUID becomes structurally unfindable by name, email or phone after erasure — only by VIN, complaint reference, or vehicle registration that the customer themselves provides.

Cross-dealer customer separation

If the same physical person interacts with two SortedTrade dealers, they have two separate customer records in our database, with no SQL link between them. We never reveal to one dealer that one of their customers is also a customer of another dealer. Cross-dealer customer linkage is structurally forbidden — there is no table that joins customers across dealer boundaries.

Vulnerability disclosure

Found something? Email security@sortedcarstrade.co.uk. We respond within one business day. Coordinated disclosure within 90 days; we ask for a 30-day embargo before public posting to allow remediation. We will name and credit you in the release note unless you ask us not to.

What we have NOT done yet

Honest disclosure — these are on the roadmap but not in place:

  • SOC 2 Type II audit (target: end of first paying-year)
  • ISO 27001 (target: 24 months post-launch)
  • Penetration test by an independent CHECK-accredited firm (target: pre-launch)
  • Cyber Essentials Plus (target: post-launch)

Until then, the architecture above is the answer to "show us your security posture". The full technical doc is available under NDA — contact us.

Questions?

We'll answer them.

Talk to us