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, nounsafe-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.