Security & Trust

How GroomSpark protects your data.

This page describes how we keep each tenant's data, and their pet parents' data, isolated, encrypted, and out of the wrong hands.

Data protection

Tenant data is isolated at the database layer.

GroomSpark is multi-tenant Postgres, hosted on Supabase. Every tenant-scoped table runs Postgres' Row-Level Security in FORCE mode: a cross-tenant read or write is denied by the database itself, not by application code that could be bypassed.

  • FORCE Row-Level Security on every tenant table, enforced in our database migrations.
  • Continuous RLS audit: an automated audit exercises SELECT, INSERT, UPDATE, DELETE, and anonymous access against every tenant table (~90 assertions per run), from a second tenant's JWT. A single regression fails CI.
  • Encryption at rest on Postgres and Storage volumes via Supabase infrastructure; connections are TLS-only.
  • Service-role key is server-only: an ESLint import wall prevents any client-bundle file from importing the admin Supabase client.
Payments

Card data never touches GroomSpark.

We process payments on Stripe Connect. Card entry happens inside Stripe-hosted iframes; the primary account number, CVV, and expiry never enter our servers, our logs, or our database. This keeps us within scope for PCI DSS v4.0.1 SAQ A.

  • Stripe Connect: each tenant onboards their own Stripe account; payouts land directly with the groomer, never in a GroomSpark-controlled ledger.
  • Enforcing Content-Security-Policy with a per-request nonce on every response: no unsafe-inline, no unsafe-eval, no wildcards.
  • Checkout-specific script allowlist: payment pages use a stricter CSP variant with only Stripe and Cloudflare Turnstile permitted. We publish the allowlist as our SAQ A evidence artifact, and a parser test keeps the doc and the code in lockstep.
  • Restricted Stripe API keys: GroomSpark backend keys are scoped to the minimum needed capabilities, with a documented rotation procedure.
  • Signature-verified Stripe webhooks: every webhook request is verified against the Stripe signing secret before any side effect.
Application security

Defense in depth in the request path.

  • Enforcing CSP with per-request nonces on every response.
  • HTTP Strict Transport Security (HSTS) with preload eligibility.
  • Fail-closed rate limits on authentication and signup endpoints, backed by Upstash Redis. If the limiter is unreachable, requests are denied rather than allowed.
  • Signature-verified inbound webhooks for Stripe, Twilio, Resend, and Vercel deployment hooks. Bodies are read raw before verification.
  • Zod schemas at the request boundary: every server action and route handler validates its input; anything unrecognized is rejected.
  • Cloudflare Turnstile CAPTCHA on the customer booking flow to blunt automated abuse.
  • ESLint import walls prevent accidental service-role imports into client-bundled code and cross-tenant client leakage.
SMS & privacy

SMS is opt-in, revocable, and scoped per tenant.

  • Express written consent captured at the point of booking, with the exact consent language stored alongside the customer record. Aligned with the TCPA and the FCC one-to-one consent posture.
  • Per-tenant Twilio numbers: each grooming business gets its own provisioned phone number; STOP and suppression apply per tenant, so opting out of one groomer never enrolls a pet parent into another.
  • STOP and HELP handled at the platform: inbound STOP suppresses further outbound automatically.
  • No SMS from production credentials in development: engineering environments use sandbox modes; production credentials live only on Vercel.