Environment Secrets
Learn how to securely manage environment variables in production with best practices for Supabase (your main database) and Stripe keys.
Understanding Public vs Secret Variables
Public Variables (Safe for Client-Side)
These variables are safe to expose in the browser and start with PUBLIC_
:
# ✅ PUBLIC - Safe for client-side
PUBLIC_SUPABASE_URL=https://your-project.supabase.co
PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6...
PUBLIC_STRIPE_KEY=pk_live_51ABC123...
Why these are safe:
- Supabase URL - Public endpoint for your main database, protected by RLS
- Supabase Anon Key - Limited permissions, row-level security enforced
- Stripe Public Key - Designed for client-side use, no secret operations
Secret Variables (Server-Only)
These variables are sensitive and must stay server-side only:
# ❌ SECRET - Never expose to client
SUPABASE_SERVICE_ROLE_SECRET=eyJhbGciOiJIUzI1NiIsInR5cCI6...
STRIPE_SECRET_KEY=sk_live_51ABC123...
STRIPE_WEBHOOK_SECRET=whsec_ABC123...
SUPABASE_DB_PASSWORD=your_db_password (if using direct connection)
Why these are secret:
- Service Role Secret - Full database access, bypasses RLS (critical for SvelteBolt)
- Stripe Secret Key - Can process payments, refunds, access customer data
- Webhook Secret - Validates webhook authenticity
- DB Password - Direct database access (if not using Supabase client)
Platform-Specific Secret Management
Vercel
Add secrets in Vercel dashboard:
- Go to your project → Settings → Environment Variables
- Add each variable:
- Name: Variable name (e.g.,
STRIPE_SECRET_KEY
) - Value: The secret value
- Environment: Choose Production, Preview, or Development
- Name: Variable name (e.g.,
- Click "Save"
Using Vercel CLI:
# Add production secret
vercel env add STRIPE_SECRET_KEY production
# Add for all environments
vercel env add SUPABASE_SERVICE_ROLE_SECRET production preview development
Environment separation:
- Production: Live site with real payments
- Preview: Pull request deployments (can use test keys)
- Development: Local development (use test keys)
Netlify
Add secrets in Netlify dashboard:
- Go to Site settings → Environment variables
- Click "Add a variable"
- Choose deployment context:
- Production: Live deployments from main branch
- Deploy previews: Pull request deployments
- All: Both production and previews
Using Netlify CLI:
# Add secret to site
netlify env:set STRIPE_SECRET_KEY "sk_live_..." --context production
# List environment variables
netlify env:list
Cloudflare Pages
Add secrets in Cloudflare dashboard:
- Go to Pages → Your project → Settings
- Scroll to "Environment variables"
- Add variables for each environment:
- Production: Live site
- Preview: Branch deployments
Using Wrangler CLI:
# Add secret
wrangler pages secret put STRIPE_SECRET_KEY --project-name=your-project
# List secrets
wrangler pages secret list --project-name=your-project
Development vs Production Separation
Development Environment (.env.local)
# Development - Test keys only
PUBLIC_SUPABASE_URL=https://your-dev-project.supabase.co
PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6...
SUPABASE_SERVICE_ROLE_SECRET=eyJhbGciOiJIUzI1NiIsInR5cCI6...
# Stripe Test Keys
PUBLIC_STRIPE_KEY=pk_test_51ABC123...
STRIPE_SECRET_KEY=sk_test_51ABC123...
STRIPE_WEBHOOK_SECRET=whsec_test_ABC123...
Note: Use a separate Supabase project for development to avoid affecting production data.
Production Environment
# Production - Live keys only
PUBLIC_SUPABASE_URL=https://your-prod-project.supabase.co
PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6...
SUPABASE_SERVICE_ROLE_SECRET=eyJhbGciOiJIUzI1NiIsInR5cCI6...
# Stripe Live Keys
PUBLIC_STRIPE_KEY=pk_live_51ABC123...
STRIPE_SECRET_KEY=sk_live_51ABC123...
STRIPE_WEBHOOK_SECRET=whsec_live_ABC123...
Important: Always use a separate Supabase project for production with different database schema if needed.
Security Best Practices
1. Key Rotation
Regularly rotate sensitive keys:
Supabase Service Role:
- Go to Supabase Dashboard → Settings → API
- Generate new service role key
- Update production environment variables immediately
- Deploy to activate new key
- Revoke old key after confirming deployment works
- Critical: Never use the same service role key across environments
Stripe Secret Keys:
- Generate new secret key in Stripe dashboard
- Update production environment variables
- Deploy and test payment functionality
- Delete old key from Stripe dashboard
2. Access Control
Limit who can access production secrets:
Team access levels:
- Developers: Access to development/staging environments only
- DevOps/Leads: Access to production environment variables
- Admin: Full access to all environments
Platform permissions:
- Vercel: Use team roles to restrict access
- Netlify: Configure team member permissions
- Cloudflare: Use account-level access controls
3. Monitoring & Auditing
Track secret usage:
Supabase:
- Monitor API usage in Supabase Dashboard → Settings → Usage
- Set up alerts for unusual database activity
- Review auth logs regularly in Authentication → Logs
- Monitor real-time connections if your app uses subscriptions
- Check storage usage if using Supabase Storage
Stripe:
- Monitor API requests in Stripe logs
- Set up webhook delivery monitoring
- Review payment activity for anomalies
4. Backup & Recovery
Secure backup of critical secrets:
# Store encrypted backups of critical env vars
# Use tools like:
# - 1Password
# - Bitwarden
# - AWS Secrets Manager
# - HashiCorp Vault
Environment Variable Validation
Add runtime validation in your app:
// src/lib/config.ts
import { z } from "zod";
const envSchema = z.object({
// Supabase (Main Database) - Required
PUBLIC_SUPABASE_URL: z.string().url("Invalid Supabase URL"),
PUBLIC_SUPABASE_ANON_KEY: z.string().min(1, "Supabase anon key required"),
SUPABASE_SERVICE_ROLE_SECRET: z
.string()
.min(1, "Supabase service role secret required"),
// Stripe - Required for payments
PUBLIC_STRIPE_KEY: z.string().startsWith("pk_", "Invalid Stripe public key"),
STRIPE_SECRET_KEY: z.string().startsWith("sk_", "Invalid Stripe secret key"),
STRIPE_WEBHOOK_SECRET: z
.string()
.startsWith("whsec_", "Invalid webhook secret"),
});
export const env = envSchema.parse(process.env);
// Helper to check if we're using test vs live keys
export const isProduction = env.STRIPE_SECRET_KEY.startsWith("sk_live_");
export const isDevelopment = env.STRIPE_SECRET_KEY.startsWith("sk_test_");
Troubleshooting
Common Security Issues
❌ Accidentally committed secrets to Git:
# Immediately rotate the exposed keys
# Remove from Git history:
git filter-branch --force --index-filter \
'git rm --cached --ignore-unmatch .env' HEAD
# Or use tools like:
# - git-secrets
# - truffleHog
# - GitGuardian
❌ Different environments using same keys:
- Use separate Supabase projects for dev/staging/prod (critical!)
- Each Supabase project should have its own database schema
- Use Stripe test keys for non-production environments
- Never use production Supabase service role key in development
❌ Team members sharing personal API keys:
- Create service accounts for shared resources
- Use team-level API keys where available
- Implement proper access controls
❌ Secrets visible in client-side code:
- Ensure secret variables don't start with
PUBLIC_
- Use server-side validation for sensitive operations
- Never log sensitive variables
Debugging Environment Issues
Check variable loading:
// Add to your app for debugging (remove before production)
console.log("Environment check:", {
supabaseUrl: !!process.env.PUBLIC_SUPABASE_URL,
supabaseConnected: !!process.env.PUBLIC_SUPABASE_ANON_KEY,
hasServiceRole: !!process.env.SUPABASE_SERVICE_ROLE_SECRET,
stripeMode: process.env.STRIPE_SECRET_KEY?.startsWith("sk_live_")
? "LIVE"
: "TEST",
// Never log actual secret values!
});
Verify deployment context:
// Check which environment is being used
export const load = async ({ url }) => {
const isDev = url.hostname === "localhost";
const isPreview =
url.hostname.includes("preview") || url.hostname.includes("deploy-preview");
const isProd = !isDev && !isPreview;
console.log("Environment:", { isDev, isPreview, isProd });
};
Emergency Response
If Secrets Are Compromised
Immediate actions:
- Rotate all affected keys immediately
- Revoke old keys in respective platforms
- Monitor for unauthorized usage
- Update all deployment environments
- Notify team members
- Review access logs
Supabase compromise:
- Immediately generate new service role key
- Review database access logs in Supabase Dashboard → Logs
- Check for unauthorized data access or modifications
- Update Row Level Security (RLS) policies if needed
- Consider temporarily disabling auth if breach is severe
- Review user activity and reset passwords if necessary
Stripe compromise:
- Generate new API keys
- Review payment activity
- Check for unauthorized transactions
- Contact Stripe support if needed
Next Steps
- Set up monitoring for your deployed application
- Implement proper logging and error tracking
- Create staging environments with separate secrets
- Regular security audits of environment variables