Security Guide
Comprehensive security documentation for the Layers API Gateway, including implemented protections, known concerns, and hardening recommendations for production deployments.
Living Document
Security Architecture Overview#
Layers implements a multi-layered security approach across authentication, authorization, rate limiting, and cost controls. However, as an API gateway handling sensitive operations and billing, ongoing security hardening is critical.
What We Do Well
- API keys hashed before storage (SHA256)
- Stripe webhook signature verification
- Tier-based rate limiting
- Pre-flight credit checks
- Session cookies with httpOnly, secure, SameSite=Lax
Areas Needing Attention
- Service role key bypasses Row Level Security (RLS)
- Test mode authentication bypass with weak defaults
- No CORS restrictions on API endpoints
- PII (emails) logged in production
- No request size limits
Critical Security Concerns#
1. Row Level Security (RLS) Bypass#
High Priority
Current State:
// lib/supabase/client.ts:8
// Create a Supabase server client with service role key (bypasses RLS)
export function createServerClient(): SupabaseClient {
serverClient = createClient(url, serviceKey, { /* ... */ });
return serverClient;
}Risk:
- A bug in user_id filtering could expose all user data
- SQL injection vulnerabilities become critical
- No defense-in-depth if authorization logic fails
Mitigation Steps:
- Enable RLS on all tables:
-- In Supabase SQL Editor ALTER TABLE users ENABLE ROW LEVEL SECURITY; ALTER TABLE api_keys ENABLE ROW LEVEL SECURITY; ALTER TABLE credit_balances ENABLE ROW LEVEL SECURITY; ALTER TABLE usage_logs ENABLE ROW LEVEL SECURITY; ALTER TABLE credit_transactions ENABLE ROW LEVEL SECURITY; - Create policies for each table:
-- Users can only see their own data CREATE POLICY "Users can view own data" ON credit_balances FOR SELECT USING (auth.uid() = user_id); CREATE POLICY "Users can update own data" ON credit_balances FOR UPDATE USING (auth.uid() = user_id); - Audit all queries: Search codebase for database queries and verify each includes proper user_id filtering
- Use anon key where possible: For read operations, consider using the anon key instead of service role
2. Test Mode Authentication Bypass#
High Priority
Current State:
// lib/middleware/auth.ts:6
const TEST_MODE_SECRET = process.env.LAYERS_TEST_SECRET || 'layers-integration-test-2026';
// Anyone who sends this header bypasses auth:
// X-Layers-Test-Mode: layers-integration-test-2026Risk:
- Public GitHub repo exposes the default secret
- Attackers can bypass all authentication and rate limiting
- Could be used to drain credits or access user data
Mitigation Steps:
- Remove hardcoded default:
// Require the environment variable const TEST_MODE_SECRET = process.env.LAYERS_TEST_SECRET; if (!TEST_MODE_SECRET) { throw new Error('LAYERS_TEST_SECRET required for test mode'); } - Disable in production:
function isTestMode(headers?: Headers): boolean { // Never allow test mode in production if (process.env.NODE_ENV === 'production') { return false; } // ... rest of test mode checks } - Use cryptographically random secret: Generate with
openssl rand -base64 48 - Consider IP allowlisting: Only allow test mode from specific IPs (CI servers)
3. Missing CORS Configuration#
Current State:
API endpoints under /api/v1/* have no CORS restrictions. Any website can call your API.
Risk:
- If a user's API key leaks (e.g., committed to GitHub), any site can use it
- Malicious sites could make requests on behalf of users
- Cross-origin attacks become easier
Mitigation Steps:
// In app/api/v1/chat/route.ts (and other endpoints)
export async function OPTIONS() {
return new Response(null, {
status: 204,
headers: {
'Access-Control-Allow-Origin': process.env.ALLOWED_ORIGINS || 'https://layers.hustletogether.com',
'Access-Control-Allow-Methods': 'POST, OPTIONS',
'Access-Control-Allow-Headers': 'Authorization, Content-Type',
'Access-Control-Max-Age': '86400', // 24 hours
},
});
}
// Add to response headers in POST handler
headers: {
...getRateLimitHeaders(rateLimitResult),
'Access-Control-Allow-Origin': process.env.ALLOWED_ORIGINS || 'https://layers.hustletogether.com',
}Medium Priority Concerns#
4. Logging Sensitive Data#
Found in:
- [app/auth/callback/route.ts:51](app/auth/callback/route.ts#L51) - Logs user emails
- [app/api/v1/chat/route.ts:357](app/api/v1/chat/route.ts#L357) - Exposes error details to users
Risk:
- GDPR/privacy violations
- Stack traces might leak implementation details
- Logs become a security liability if breached
Mitigation:
// Bad - logs PII
console.log('[OAuth] User:', data?.user?.email);
// Good - logs anonymized ID
console.log('[OAuth] User authenticated:', data?.user?.id);
// Bad - exposes details to user
return NextResponse.json(
{ error: 'Internal server error', details: String(error) },
{ status: 500 }
);
// Good - generic message, detailed server log
console.error('[API Error]', error);
return NextResponse.json(
{ error: 'Internal server error', request_id: requestId },
{ status: 500 }
);5. SHA256 for API Key Hashing#
While better than plain text, SHA256 is not ideal for hashing secrets due to its speed (enables rainbow table attacks).
Current State:
// lib/supabase/client.ts:46
export function hashApiKey(key: string): string {
return createHash('sha256').update(key).digest('hex');
}Recommendation:
import bcrypt from 'bcrypt';
export async function hashApiKey(key: string): Promise<string> {
return bcrypt.hash(key, 12); // 12 rounds = good balance
}
// Note: This makes key lookups slower, but more secure
// Consider caching valid keys in Redis for performance6. Demo Mode Fallback#
If Supabase isn't configured, auth middleware returns a mock user. This could accidentally run in production.
Mitigation:
if (!isSupabaseConfigured()) {
if (process.env.NODE_ENV === 'production') {
throw new Error('Supabase must be configured in production');
}
// Demo mode only for local dev
console.warn('Running in demo mode - dev only');
return mockUser;
}Lower Priority Improvements#
7. Request Size Limits#
Add body size limits to prevent abuse:
// next.config.js
module.exports = {
api: {
bodyParser: {
sizeLimit: '1mb',
},
},
};8. Input Sanitization#
Consider adding:
- Maximum message length (e.g., 100,000 chars)
- Maximum messages array length (e.g., 100 messages)
- Content filtering for known attack patterns
- Rate limiting on specific model + user combinations
9. Maximum Spend Limits#
const MAX_CREDITS_PER_REQUEST = 100; // ~$1.00
const MAX_CREDITS_PER_HOUR = 1000; // ~$10.00
if (estimated > MAX_CREDITS_PER_REQUEST) {
return NextResponse.json(
{
error: 'Request exceeds maximum cost',
max_credits: MAX_CREDITS_PER_REQUEST,
estimated_credits: estimated
},
{ status: 400 }
);
}Environment Variable Security#
| Variable | Risk Level | If Compromised |
|---|---|---|
| SUPABASE_SERVICE_ROLE_KEY | Critical | Full database access, can read/write all data |
| AI_GATEWAY_API_KEY | Critical | Can make expensive AI requests on your account |
| STRIPE_SECRET_KEY | Critical | Can charge customers, access payment info |
| STRIPE_WEBHOOK_SECRET | High | Can forge billing webhooks |
| LAYERS_TEST_SECRET | High | Bypass authentication completely |
| NEXT_PUBLIC_SUPABASE_ANON_KEY | Medium | Limited by RLS, but could abuse rate limits |
Best Practices:
- Never commit .env files to git
- Use Vercel's encrypted environment variables
- Different keys for dev/staging/production
- Rotate keys regularly (quarterly minimum)
- Monitor for leaked keys using GitHub secret scanning
- Store production secrets in a password manager (1Password, Bitwarden)
Security Monitoring & Alerts#
Implement Anomaly Detection#
Monitor for suspicious patterns:
| Pattern | Threshold | Action |
|---|---|---|
| Usage spike | 10x normal hourly usage | Alert admin, temporary rate limit |
| Failed auth attempts | 50 from same IP in 5 min | Block IP temporarily |
| Rapid key creation | 5+ keys in 1 hour | Flag account for review |
| Credit refund abuse | Multiple refunds same user | Manual review required |
| High-cost models | Opus/O1 > $100/day | Alert user and admin |
Audit Logging#
Log security-relevant events to a separate audit table:
CREATE TABLE audit_logs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
timestamp TIMESTAMPTZ DEFAULT NOW(),
user_id UUID,
event_type TEXT NOT NULL, -- 'key_created', 'tier_changed', 'auth_failed', etc.
ip_address INET,
user_agent TEXT,
metadata JSONB,
severity TEXT -- 'info', 'warning', 'critical'
);Events to log:
- API key creation/deletion
- Failed authentication attempts
- Credit purchases and refunds
- Subscription tier changes
- Rate limit violations
- Unusually expensive requests
Security Hardening Checklist#
Create policies for user_id filtering
Require LAYERS_TEST_SECRET env var
Check NODE_ENV before allowing bypass
Restrict origins to your domains
Use user IDs instead of emails
Throw error if Supabase not configured
Verify user_id filtering on every query
Set 1MB limit in Next.js config
Prevent >$1 per request, >$10 per hour
Better than SHA256 for secret hashing
Alert on 10x usage spikes
Track key creation, auth failures, tier changes
New keys for LAYERS_TEST_SECRET, webhooks
Detect accidental key commits
Auto-update vulnerable dependencies
Incident Response Plan#
If an API Key Leaks#
- Immediately deactivate the key in dashboard
- Check usage_logs for unauthorized usage
- Notify affected user
- Review how the leak occurred (committed to git, public logs, etc.)
- Generate replacement key for user
- If in git history: use BFG Repo Cleaner to remove from history
If Database Access Compromised#
- Immediately rotate SUPABASE_SERVICE_ROLE_KEY
- Force logout all users (invalidate sessions)
- Audit database for unauthorized changes
- Check usage_logs for anomalous activity
- Notify affected users per GDPR requirements
- Review and strengthen RLS policies
- Enable database audit logging
- Consider incident response service (e.g., Stripe incident response)
If Billing Abuse Detected#
- Temporarily suspend affected API keys
- Contact user via email
- Review usage_logs for patterns
- Implement stricter rate limits for account
- Consider refund policy (case-by-case)
- Update anomaly detection to catch similar patterns
Compliance Considerations#
GDPR (EU Users)#
- Right to access: Users can export their data via dashboard
- Right to deletion: Delete user accounts and all associated data
- Data minimization: Only collect necessary data (email, usage)
- Breach notification: Must notify within 72 hours of discovery
- Logging PII: Remove emails from production logs
PCI DSS (Credit Cards)#
Layers uses Stripe for payment processing, which is PCI Level 1 certified. We never handle raw credit card data. Stripe handles all card storage and processing.
SOC 2 (Enterprise Customers)#
If targeting enterprise customers, consider SOC 2 Type II certification:
- Implement all items in security checklist
- Document security policies and procedures
- Regular penetration testing
- Vendor risk assessments (Anthropic, OpenAI, Supabase, Stripe)
- Incident response procedures
- Employee security training
Additional Resources#
| Resource | Link |
|---|---|
| OWASP Top 10 | owasp.org/top-ten ↗ |
| Supabase Security | supabase.com/docs/security ↗ |
| Next.js Security Headers | nextjs.org/docs/headers ↗ |
| Stripe Security | stripe.com/docs/security ↗ |
| GDPR Compliance | gdpr.eu/checklist ↗ |
Related Documentation
- Authentication Guide - User and API authentication
- Architecture Overview - System design and components
- Billing & Credits - Credit system and fraud prevention
Questions or security concerns? Please email alfonso@mirrorfactory.dev for responsible disclosure of security vulnerabilities.