GrowthPilot AI is a multi-tenant SaaS platform handling business-critical data (client PII, financial records, API credentials). The primary threats are:
- Tenant data leakage — one workspace accessing another's data
- Credential exposure — API keys, OAuth tokens, payment data
- Unauthorized access — privilege escalation within a workspace
- Injection attacks — SQL injection, XSS, CSRF
- HSTS enforced (max-age 2 years, includeSubDomains, preload)
- All connections via HTTPS (Vercel terminates TLS at edge)
- Supabase connections use TLS 1.3
| Mechanism | Implementation |
|---|---|
| Email/password | Supabase Auth with bcrypt hashing (server-side) |
| OAuth | Google, GitHub via PKCE flow |
| Magic Link | OTP via email; 5-minute expiry |
| MFA (TOTP) | Supabase Auth MFA with challenge/verify flow |
| Session tokens | httpOnly, SameSite=Lax cookies via @supabase/ssr |
| JWT verification | Middleware calls getUser() (server-validated, not just decoded) |
- 4 system roles: owner > admin > editor > viewer
- 35 granular permissions mapped to roles
- Server-side enforcement via
requirePermission()/hasWorkspaceRole() - Database-level enforcement via RLS policies on every table
- Row-Level Security (RLS) enabled on all 70+ tables
- Helper functions (
is_workspace_member,has_workspace_role) useSECURITY DEFINERto prevent infinite recursion - Organization → workspace → member hierarchy with cascading deletes
- No application-code-only tenant filtering — RLS is the source of truth
- Public API keys: SHA-256 hashed before storage; raw key shown exactly once at creation
- Key prefix format (
gp_live_) for easy identification without exposing the full key - Scope-based authorization per key
- Expiration and revocation support
- Rate limiting: 100 requests/minute per key
- Stripe: HMAC signature verification via
constructEvent() - Shopify: Timing-safe HMAC comparison (
crypto.timingSafeEqual) - All inbound webhook endpoints validate signatures before processing
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
Permissions-Policy: camera=(), microphone=(), geolocation=()
X-Powered-By: (removed)
- All user inputs validated with Zod schemas in server actions
- UUID format enforcement for entity IDs
- String length limits on all text fields
- Enum constraints for status/type fields
| Secret | Storage | Access Pattern |
|---|---|---|
| Supabase keys | Env vars (Vercel secrets) | getServerEnv() with Zod validation |
| Service role key | Env var + server-only guard |
createAdminClient() — cannot be imported from client |
| OAuth tokens | integrations.credentials JSONB (RLS admin-only) |
Auto-refreshed before expiry |
| API key raw values | Never stored — SHA-256 hash only | Shown once at creation |
| Stripe webhook secret | Env var | Used only in webhook route |
audit_logstable records all security-sensitive operations- Actor ID, action, entity type/ID, old/new values, IP address, timestamp
- Admin-only read access (RLS enforced)
- Immutable — no UPDATE or DELETE policies for regular users
Report security vulnerabilities to: security@growthpilot.ai
We commit to:
- Acknowledging receipt within 24 hours
- Providing a timeline within 72 hours
- Not pursuing legal action for good-faith research
| Standard | Status | Notes |
|---|---|---|
| OWASP Top 10 | Addressed | Injection (Zod + RLS), Auth (Supabase), XSS (React escaping), CSRF (SameSite cookies) |
| SOC 2 Type II | Infrastructure-level via Supabase + Vercel | Application-level audit logging in place |
| GDPR | Data isolation + soft deletes | Data export/erasure endpoints can be built on the existing framework |
| PCI DSS | Delegated to Stripe | No card data touches GrowthPilot servers |
- Verify RLS is enabled on every table (Supabase dashboard → Tables)
- Confirm
SUPABASE_SERVICE_ROLE_KEYnever appears in client bundles - Test HSTS header with
curl -I https://yourdomain.com - Run
supabase db lintto check for tables missing RLS - Verify Stripe webhook signature in test mode before going live
- Enable Supabase Auth email rate limiting in dashboard
- Review OAuth redirect URIs — no wildcards