Security Best Practices Guide
Version: 1.0.0 Last Updated: 2025-12-13 Author: Petunia Team Status: Production
Table of Contents
- Overview
- Security Architecture
- Authentication
- Authorization (RBAC)
- Encryption
- Secret Management
- Rate Limiting & DDoS Protection
- CSRF Protection
- Session Management
- Audit Logging
- Security Headers
- API Security
- Security Review Checklist
Overview
Petunia implements defense-in-depth security with multiple layers of protection:
- Authentication: Supabase Auth with MFA support
- Authorization: Role-based access control (RBAC) with tenant isolation
- Encryption: AES-256-GCM with tenant-specific keys (HKDF-derived)
- Session Security: Automatic rotation on privilege changes
- Rate Limiting: IP and endpoint-based throttling
- Audit Logging: Comprehensive tracking of all security events
- CSRF Protection: Double-submit cookie pattern
- Security Headers: CSP, HSTS, X-Frame-Options, etc.
Security Principles
- Zero Trust: Never trust, always verify
- Least Privilege: Grant minimal necessary permissions
- Defense in Depth: Multiple security layers
- Fail Secure: Security failures should deny access
- Audit Everything: Log all security-relevant events
Security Architecture
Layers of Defense
┌─────────────────────────────────────────────────────────────┐
│ Edge Middleware │
│ - Rate Limiting │
│ - CSRF Validation │
│ - Security Headers │
└─────────────────────────────────────────────────────────────┘
│
┌─────────────────────────────────────────────────────────────┐
│ Authentication Layer (Supabase) │
│ - Email/Password + OAuth │
│ - MFA (TOTP) │
│ - Email Verification │
│ - Device Fingerprinting │
└─────────────────────────────────────────────────────────────┘
│
┌─────────────────────────────────────────────────────────────┐
│ Authorization Layer (RBAC + RLS) │
│ - Role-based permissions │
│ - Tenant isolation (clientId) │
│ - Database Row-Level Security │
└─────────────────────────────────────────────────────────────┘
│
┌─────────────────────────────────────────────────────────────┐
│ Data Protection Layer │
│ - Encryption at rest (AES-256-GCM) │
│ - Encryption in transit (TLS 1.3) │
│ - Tenant-isolated encryption keys │
└─────────────────────────────────────────────────────────────┘
│
┌─────────────────────────────────────────────────────────────┐
│ Audit & Monitoring │
│ - Comprehensive audit logs │
│ - Security event monitoring │
│ - Anomaly detection │
└─────────────────────────────────────────────────────────────┘
Key Security Files
| Component | Location |
|---|---|
| Encryption | /lib/utils/encryption.ts |
| Authentication | /lib/auth/helpers.ts, /lib/auth/server.ts |
| Authorization | /lib/auth/permissions.ts |
| Rate Limiting | /lib/auth/otp-rate-limit.ts, /lib/security/edge-rate-limiter.ts |
| CSRF | /lib/security/edge-csrf.ts |
| Audit Logging | /lib/auth/audit.ts |
| Session Management | /lib/auth/session-manager.ts, /lib/auth/sessionRotation.ts |
| API Security | /lib/utils/api-security.ts |
| Security Headers | /lib/security/edge-security-headers.ts |
| Middleware | /proxy.ts |
Authentication
Supabase Auth Integration
Petunia uses Supabase Auth for user authentication with the following features:
- Email/password authentication
- OAuth providers (Google, Facebook)
- Email verification
- Password reset
- Multi-factor authentication (MFA)
Server-Side Auth Helpers
// File: /lib/auth/helpers.ts
import { auth } from '@/lib/auth/server';
// Require authentication
const session = await requireAuth();
// Require specific role
const session = await requireRole('admin');
// Require client access
const session = await requireClientAccess();
Client-Side Auth
// File: /lib/hooks/auth/useAuth.ts
import { useAuth } from '@/hooks/auth/useAuth';
function MyComponent() {
const { user, session, isLoading } = useAuth();
if (isLoading) return <Loading />;
if (!user) return <SignIn />;
return <Dashboard user={user} />;
}
Multi-Factor Authentication (MFA)
Setup Flow
- User enables MFA in settings
- Generate TOTP secret:
generateSecret() - Encrypt secret before storing:
encryptMfaSecret(secret) - Display QR code to user
- Verify initial code:
verifyTOTP(token, secret) - Store encrypted secret in database
Verification Flow
- User enters TOTP code
- Retrieve encrypted secret from database
- Decrypt secret:
decryptMfaSecret(encryptedSecret) - Verify code:
verifyTOTP(token, secret, window) - Log audit event:
audit.twoFAVerified(userId)
Implementation
// File: /lib/utils/totp.ts
// Generate a new TOTP secret
const secret = generateSecret();
// Build OTP auth URL for QR code
const otpauthURL = buildOtpauthURL(secret, user.email, 'Petunia');
// Encrypt secret before storing
const encryptedSecret = encryptMfaSecret(secret);
await db.user.update({ mfaSecret: encryptedSecret });
// Verify TOTP code
const isValid = verifyTOTP(userCode, decryptMfaSecret(user.mfaSecret));
Security Features
- Encrypted Storage: MFA secrets stored using AES-256-GCM
- Time Window: Configurable window (default: ±1 period = 60s)
- Backup Codes: Generate one-time backup codes for recovery
- Rate Limiting: Max 3 attempts per OTP session
Device Fingerprinting
Track trusted devices to detect anomalous logins:
// File: /lib/auth/device-fingerprint.ts
// Generate device fingerprint
const fingerprint = await generateDeviceFingerprint();
// Check if device is trusted
const trustLevel = await checkDeviceTrust(userId, fingerprint.fingerprint);
// Record device login
await recordDeviceLogin(userId, fingerprint);
// Trust a device
await trustDevice(userId, fingerprint, 'My MacBook Pro', 30); // 30 days
Trust Levels
UNKNOWN: First time seeing this deviceTRUSTED: Explicitly trusted by userSUSPICIOUS: Anomalous behavior detectedBLOCKED: Explicitly blocked
Email Verification
All new users must verify their email:
- User signs up
- Verification email sent with token
- User clicks link:
/auth/verify-email?token=xxx - Token verified, email confirmed
- Audit log:
audit.emailVerified(userId, email)
Authorization (RBAC)
Role Hierarchy
owner (100) - Full access to everything
↓
admin (80) - Manage team, integrations, settings
↓
manager (60) - View analytics, manage automation
↓
agent (40) - Send messages, view assigned leads
↓
viewer (20) - Read-only access
Permissions
Each role has specific permissions:
// File: /lib/auth/permissions.ts
export type Permission =
| 'manage_team'
| 'manage_billing'
| 'manage_integrations'
| 'view_analytics'
| 'send_messages'
| 'manage_automation'
| 'export_data'
| 'delete_data'
| 'view_all_conversations'
| 'manage_settings'
| 'manage_api_keys'
| 'view_audit_logs';
Usage
import { permissionsService } from '@/lib/auth/permissions';
// Check permission
const canExport = await permissionsService.hasPermission(userId, 'export_data');
// Check any permission
const hasAccess = await permissionsService.hasAnyPermission(userId, [
'manage_team',
'manage_settings'
]);
// Check all permissions
const isFullAdmin = await permissionsService.hasAllPermissions(userId, [
'manage_team',
'manage_billing',
'manage_integrations'
]);
// Check if user can manage another user
const canManage = await permissionsService.canManageUser(managerId, targetUserId);
Decorator Pattern
class MyService {
@requirePermission('export_data')
async exportData(request: { userId: string }) {
// Only called if user has permission
}
}
Tenant Isolation
All data is isolated by clientId:
- Database queries filtered by RLS policies
- Encryption keys derived per tenant (HKDF)
- API responses filtered by client access
// Always verify client access
const session = await requireClientAccess();
const clientId = session.user.clientId;
// All queries scoped to client
const leads = await prisma.lead.findMany({
where: { clientId }
});
Encryption
Overview
Petunia uses AES-256-GCM (Galois/Counter Mode) for all encryption:
- Key Size: 256 bits
- IV: 16 bytes (random per encryption)
- Auth Tag: 16 bytes (prevents tampering)
- Format:
iv:authTag:encryptedData(base64)
Encryption Key Derivation
Master Key (CREDENTIALS_ENCRYPTION_KEY)
↓
PBKDF2 (100,000 iterations) + Salt
↓
Derived Master Key (32 bytes)
↓
HKDF (tenant ID as salt) [OPTIONAL]
↓
Tenant-Specific Key (32 bytes)
Tenant-Isolated Encryption
For multi-tenant isolation, use tenant-specific keys:
// File: /lib/utils/encryption.ts
// Encrypt with tenant isolation
const encrypted = encryptData(data, { tenantId: clientId });
// Decrypt with tenant isolation
const decrypted = decryptData(encrypted, { tenantId: clientId });
// Convenience functions
const encrypted = encryptForTenant(data, clientId);
const decrypted = decryptForTenant(encrypted, clientId);
Security Benefit: Even if master key is compromised, tenant data remains isolated because each tenant uses a cryptographically derived unique key.
Use Cases
1. API Keys & OAuth Tokens
// File: /lib/utils/integration-token-encryption.ts
// Encrypt integration token (with tenant isolation)
const encryptedToken = encryptIntegrationToken(accessToken, {
integrationId: 'GoogleConnection',
tenantId: clientId
});
// Decrypt integration token (with tenant isolation)
const accessToken = decryptIntegrationToken(encryptedToken, {
integrationId: 'GoogleConnection',
tenantId: clientId
});
2. MFA Secrets
// File: /lib/utils/totp.ts
// Encrypt MFA secret
const encryptedSecret = encryptMfaSecret(totpSecret);
// Decrypt MFA secret
const totpSecret = decryptMfaSecret(user.mfaSecret);
3. Sensitive User Data
// Encrypt any sensitive data
const encrypted = encryptData(sensitiveData);
// Encrypt objects
const encrypted = encryptObject({ ssn, taxId }, { tenantId: clientId });
const data = decryptObject<{ ssn: string; taxId: string }>(encrypted, { tenantId: clientId });
Validation
// Check if data is encrypted
const isEncrypted = isValidEncryptedFormat(data);
// Expected format: "iv:authTag:encryptedData"
// Example: "dGVzdA==:dGVzdA==:dGVzdGRhdGE="
Key Rotation
When rotating encryption keys:
- Generate new
CREDENTIALS_ENCRYPTION_KEY - Run migration script to re-encrypt all data
- Update environment variable
- Clear key cache:
clearKeyCache()
# Rotate encryption key
node scripts/rotate-encryption-key.ts
Security Best Practices
- Never log decrypted data
- Never expose encryption keys in client code
- Use tenant isolation for multi-tenant data
- Rotate keys annually or after security incidents
- Validate encrypted format before decryption
- Handle legacy plaintext gracefully during migration
Secret Management
Environment Variables
All secrets stored in environment variables:
# Critical secrets (rotate regularly)
CREDENTIALS_ENCRYPTION_KEY="" # openssl rand -base64 32
SESSION_SECRET="" # openssl rand -base64 32
CSRF_SECRET="" # openssl rand -base64 32
SUPABASE_SERVICE_ROLE_KEY="" # From Supabase dashboard
# Database
DATABASE_URL="" # Supabase connection pool
DIRECT_DATABASE_URL="" # Direct connection (migrations)
# Auth
SUPABASE_JWT_SECRET="" # From Supabase dashboard
NEXT_PUBLIC_SUPABASE_ANON_KEY="" # From Supabase dashboard
NEXT_PUBLIC_SUPABASE_URL="" # From Supabase dashboard
# Third-party API keys
OPENAI_API_KEY=""
STRIPE_SECRET_KEY=""
MAILGUN_API_KEY=""
Secret Rotation Schedule
| Secret | Rotation Frequency | Priority |
|---|---|---|
CREDENTIALS_ENCRYPTION_KEY | Annually | Critical |
SESSION_SECRET | Annually | Critical |
CSRF_SECRET | Annually | High |
SUPABASE_SERVICE_ROLE_KEY | Quarterly | Critical |
| API Keys (Stripe, OpenAI, etc.) | When compromised | High |
| OAuth Secrets | When compromised | High |
Security Guidelines
-
Never commit secrets to git
- Use
.env.local(gitignored) - Check
.env.examplefor required variables
- Use
-
Use strong secrets
- Minimum 32 characters
- Generate with:
openssl rand -base64 32 - No weak patterns (password, test, 12345, etc.)
-
Production secrets
- Store in Vercel environment variables
- Use different secrets per environment
- Enable "Encrypted" option in Vercel
-
Key validation
- System validates key strength on startup
- Rejects weak patterns in production
- Logs warnings in development
// Automatic validation on encryption
// File: /lib/utils/encryption.ts
validateEncryptionKey(key, 'CREDENTIALS_ENCRYPTION_KEY');
// Throws if:
// - Length < 32 characters
// - Contains weak patterns
// - All same character
Rate Limiting & DDoS Protection
Overview
Multi-layer rate limiting protects against abuse:
- Edge Middleware: IP-based rate limits
- API Routes: Endpoint-specific limits
- Auth Routes: Aggressive limits on sensitive endpoints
- OTP/2FA: Specialized limits for verification attempts
Edge Rate Limiting
// File: /lib/security/edge-rate-limiter.ts
// Applied in proxy.ts
const rateLimitType = pathname.startsWith('/api/auth/')
? 'auth'
: pathname.startsWith('/api/')
? 'api'
: 'page';
const result = await rateLimit(request, rateLimitType);
Rate Limit Tiers
| Tier | Max Requests | Window | Use Case |
|---|---|---|---|
health | 1000 | 1 minute | Health checks, static assets |
page | 100 | 1 minute | Page navigation |
api | 60 | 1 minute | Standard API calls |
auth | 10 | 1 minute | Authentication endpoints |
heavy | 10 | 1 minute | Expensive operations |
OTP Rate Limiting
// File: /lib/auth/otp-rate-limit.ts
// Check OTP request limit (5 per hour)
const result = await checkOTPRequestLimit(email, 'email');
if (!result.allowed) {
// Rate limit exceeded
return { error: result.message, resetAt: result.resetAt };
}
// Track OTP generation for verification limits
await trackOTPGeneration(otpId);
// Check verification attempts (3 max per OTP)
const verifyResult = await checkOTPVerificationLimit(otpId);
API Security Middleware
// File: /lib/utils/api-security.ts
export const middleware = createApiSecurityMiddleware({
rateLimit: {
max: 100,
windowSec: 60,
keyGen: 'ip',
specialLimits: {
'/api/auth/signin': { max: 5, windowSec: 300 },
'/api/auth/signup': { max: 3, windowSec: 3600 }
}
}
});
Response Headers
Rate limit information included in responses:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 73
X-RateLimit-Reset: 2025-12-13T14:30:00.000Z
When rate limited:
HTTP 429 Too Many Requests
Retry-After: 45
Best Practices
- Use distributed cache (Redis) for production
- Monitor rate limit hits for DDoS attacks
- Whitelist trusted IPs (health checks, monitoring)
- Adjust limits per endpoint based on usage
- Log rate limit violations to audit log
CSRF Protection
Double-Submit Cookie Pattern
Petunia uses the double-submit cookie pattern:
- Server sets secure, HTTP-only cookie with CSRF token
- Client includes same token in
X-CSRF-Tokenheader - Server verifies tokens match
Implementation
// File: /lib/security/edge-csrf.ts
// Generate CSRF token
const token = generateCSRFToken();
// Set cookie
setCSRFCookie(response, token);
// Verify CSRF token (in middleware)
const result = await verifyCSRFToken(request);
if (!result.valid) {
return new NextResponse(
JSON.stringify({ error: 'CSRF validation failed' }),
{ status: 403 }
);
}
Protected Methods
CSRF required for:
POSTPUTPATCHDELETE
Exempt methods:
GETHEADOPTIONS
Exempt Routes
CSRF validation skipped for:
/api/webhooks/*(HMAC signature auth)/api/health/*(public health checks)/api/auth/signin(initial auth)/api/auth/signup(initial auth)
Client Usage
// React component
import { useCSRF } from '@/hooks/useCSRF';
function MyForm() {
const { csrfToken } = useCSRF();
const handleSubmit = async () => {
await fetch('/api/data', {
method: 'POST',
headers: {
'X-CSRF-Token': csrfToken
},
body: JSON.stringify(data)
});
};
}
Session Management
Session Lifecycle
- Creation: User signs in, session created
- Validation: Each request validates session
- Refresh: Session refreshed periodically (30 min)
- Rotation: Session rotated on privilege changes
- Expiration: Session expires after max age (7 days)
Session Rotation
Automatic rotation occurs on:
- Periodic: Every 7 days
- Privilege Change: Role or permission changes
- Password Change: All sessions invalidated
- Suspicious Activity: Force rotation
// File: /lib/auth/sessionRotation.ts
// Check if rotation needed
const { needsRotation, reason } = await sessionRotation.needsRotation(
userId,
sessionCreatedAt
);
// Rotate session
const result = await sessionRotation.rotateSession(
userId,
'privilege_change',
{ ipAddress, userAgent }
);
// Force logout all sessions
await sessionRotation.forceLogoutAll(userId, 'password_change');
Rotation Policy
const DEFAULT_ROTATION_POLICY = {
maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days
rotationInterval: 7 * 24 * 60 * 60 * 1000, // 7 days
gracePeriod: 5 * 60 * 1000, // 5 minutes
rotateOnPasswordChange: true,
rotateOnPrivilegeChange: true
};
Session Storage
Sessions stored in:
- Database:
Sessiontable (source of truth) - Cookies: Encrypted JWT token
- Local Storage: Backup for recovery (client)
Concurrent Session Management
Users can have multiple active sessions (different devices):
// List user's sessions
const sessions = await prisma.session.findMany({
where: { userId, expires: { gt: new Date() } }
});
// Revoke specific session
await prisma.session.update({
where: { id: sessionId },
data: { expires: new Date() }
});
Audit Logging
Comprehensive Event Tracking
All security-relevant events logged to database:
// File: /lib/auth/audit.ts
// Authentication events
await audit.loginSuccess(userId, email);
await audit.loginFailed(email, 'Invalid password');
await audit.logout(userId);
// 2FA events
await audit.twoFAEnabled(userId);
await audit.twoFAVerified(userId);
await audit.twoFAFailed(userId, 'Invalid code');
// Security events
await audit.suspiciousActivity(userId, 'Multiple failed logins', 'high');
await audit.rateLimitExceeded(identifier, '/api/auth/signin');
await audit.permissionDenied(userId, 'settings', 'delete');
// Admin events
await audit.adminImpersonate(adminId, targetUserId, 'Support request');
await audit.adminUserUpdated(adminId, targetUserId, { role: 'admin' });
// Data events
await audit.dataExport(userId, 'leads', 'csv', 1000);
await audit.dataDelete(userId, 'lead', leadId, 'User request');
Event Types
export enum AuditEventType {
// Auth
LOGIN_SUCCESS = 'auth.login_success',
LOGIN_FAILED = 'auth.login_failed',
LOGOUT = 'auth.logout',
SIGNUP = 'auth.signup',
// 2FA
TWO_FA_ENABLED = 'auth.2fa_enabled',
TWO_FA_VERIFIED = 'auth.2fa_verified',
TWO_FA_FAILED = 'auth.2fa_failed',
// Security
SUSPICIOUS_ACTIVITY = 'security.suspicious_activity',
RATE_LIMIT_EXCEEDED = 'security.rate_limit_exceeded',
PERMISSION_DENIED = 'security.permission_denied',
// Admin
ADMIN_IMPERSONATE = 'admin.impersonate',
ADMIN_USER_UPDATED = 'admin.user_updated',
// Data
DATA_EXPORT = 'data.export',
DATA_DELETE = 'data.delete'
}
Audit Log Schema
CREATE TABLE "AuditLog" (
"id" TEXT PRIMARY KEY,
"userId" TEXT,
"action" TEXT NOT NULL,
"resource" TEXT NOT NULL,
"resourceId" TEXT,
"ipAddress" TEXT,
"userAgent" TEXT,
"success" BOOLEAN NOT NULL,
"error" TEXT,
"details" JSONB,
"timestamp" TIMESTAMP NOT NULL DEFAULT NOW()
);
-- Indexes for efficient queries
CREATE INDEX idx_audit_user ON "AuditLog"("userId", "timestamp");
CREATE INDEX idx_audit_action ON "AuditLog"("action", "timestamp");
CREATE INDEX idx_audit_timestamp ON "AuditLog"("timestamp");
Querying Audit Logs
// Get user's audit trail
const logs = await prisma.auditLog.findMany({
where: { userId },
orderBy: { timestamp: 'desc' },
take: 100
});
// Security events in last 24 hours
const securityEvents = await prisma.auditLog.findMany({
where: {
resource: 'security',
timestamp: { gte: new Date(Date.now() - 24 * 60 * 60 * 1000) }
}
});
// Failed login attempts
const failedLogins = await prisma.auditLog.findMany({
where: {
action: 'auth.login_failed',
timestamp: { gte: new Date(Date.now() - 60 * 60 * 1000) }
}
});
Best Practices
- Log everything security-relevant
- Include context (IP, user agent, metadata)
- Never log secrets (passwords, tokens, API keys)
- Set retention policy (1 year minimum for compliance)
- Monitor for anomalies (unusual patterns)
- Export for compliance (GDPR, SOC 2)
Security Headers
Content Security Policy (CSP)
// File: /lib/security/edge-security-headers.ts
const csp = {
'default-src': ["'self'"],
'script-src': ["'self'"],
'style-src': ["'self'", "'unsafe-inline'"],
'img-src': ["'self'", 'data:', 'https:'],
'font-src': ["'self'"],
'connect-src': ["'self'", 'https://app.petunia.gardenpatch.xyz/api'],
'frame-ancestors': ["'none'"],
'object-src': ["'none'"]
};
Complete Header Set
Content-Security-Policy: [See above]
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()
Custom Headers
X-Request-ID: req_12345_abc
X-Processing-Time: 45ms
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 73
X-Petunia-User-Type: returning
API Security
Request Validation
import { z } from 'zod';
// Define schema
const createLeadSchema = z.object({
name: z.string().min(1).max(100),
email: z.string().email(),
phone: z.string().optional()
});
// Validate in API route
export async function POST(request: Request) {
const body = await request.json();
const validation = createLeadSchema.safeParse(body);
if (!validation.success) {
return new Response(
JSON.stringify({ error: validation.error }),
{ status: 400 }
);
}
const data = validation.data;
// ...
}
API Authentication
// API routes require authentication
import { requireAuth, requireRole } from '@/lib/auth/helpers';
export async function GET() {
const session = await requireAuth();
// User is authenticated
}
export async function DELETE() {
const session = await requireRole('admin');
// User has admin role
}
API Key Authentication
For external API access:
// Validate API key
const apiKey = request.headers.get('X-API-Key');
if (!apiKey) {
return new Response('API key required', { status: 401 });
}
const validKey = await prisma.apiKey.findUnique({
where: { key: hashString(apiKey), active: true }
});
if (!validKey) {
return new Response('Invalid API key', { status: 401 });
}
Webhook Security (HMAC)
// Verify webhook signature
import { createHmac } from 'crypto';
function verifyWebhookSignature(
payload: string,
signature: string,
secret: string
): boolean {
const expectedSignature = createHmac('sha256', secret)
.update(payload)
.digest('hex');
return constantTimeCompare(signature, expectedSignature);
}
// In webhook handler
const signature = request.headers.get('X-Webhook-Signature');
const rawBody = await request.text();
if (!verifyWebhookSignature(rawBody, signature, WEBHOOK_SECRET)) {
return new Response('Invalid signature', { status: 401 });
}
Security Review Checklist
Pre-Deployment
- All secrets in environment variables (not hardcoded)
- Strong encryption keys generated (
openssl rand -base64 32) - CSRF protection enabled for all state-changing endpoints
- Rate limiting configured for all public endpoints
- Security headers applied (CSP, HSTS, X-Frame-Options)
- Database RLS policies enabled and tested
- Audit logging enabled for all security events
- MFA available for all users
- Email verification required for new accounts
- Session rotation enabled
- Input validation on all API routes
- SQL injection protection (Prisma ORM)
- XSS protection (React escaping + CSP)
- TLS 1.3 enforced in production
Code Review
- No secrets in code or logs
- Authentication required for protected routes
- Authorization checks for all data access
- Tenant isolation verified (clientId filtering)
- Sensitive data encrypted at rest
- Passwords never logged or displayed
- Error messages don't leak sensitive info
- File uploads validated (type, size, content)
- SQL queries use parameterized statements
- User input sanitized before display
API Routes
- Authentication middleware applied
- Rate limiting configured
- Input validation with Zod schemas
- CSRF token verified for mutations
- Audit logging on sensitive operations
- Proper HTTP status codes
- Error handling without information leakage
- Tenant isolation enforced
Authentication & Authorization
- Strong password requirements
- Password reset flow secure (timed tokens)
- Email verification required
- MFA available and encouraged
- Session expiration configured
- Session rotation on privilege changes
- Device tracking enabled
- Failed login attempts logged
- Account lockout after N failed attempts
- Role-based access control enforced
Data Protection
- Sensitive data encrypted (API keys, tokens, MFA secrets)
- Tenant-specific encryption keys used
- Encryption key rotation plan in place
- Database backups encrypted
- TLS enforced for all connections
- Secure cookie settings (httpOnly, secure, sameSite)
- GDPR compliance (data export, deletion)
Monitoring & Response
- Audit logs enabled and retained (1 year minimum)
- Security event monitoring configured
- Rate limit violations logged
- Failed authentication attempts tracked
- Anomaly detection alerts
- Incident response plan documented
- Security contact information published
Third-Party Integrations
- OAuth secrets stored securely
- Webhook signatures verified (HMAC)
- API keys encrypted in database
- Integration tokens use tenant-isolated keys
- Rate limits respect provider limits
- Error handling doesn't expose secrets
Compliance
- SOC 2 controls implemented
- GDPR requirements met (consent, export, deletion)
- HIPAA compliance (if handling health data)
- PCI DSS (if handling payment data)
- Audit trail for all data access
- Privacy policy published
- Terms of service published
Additional Resources
Internal Documentation
/docs/features/AUTHENTICATION.md- Authentication architecture/docs/features/ENCRYPTION.md- Encryption implementation/docs/RLS_POLICIES.md- Row-level security policies/docs/TESTING.md- Security testing guidelines
Security Contacts
- Security Team: support@gardenpatch.xyz
- Bug Bounty: [Link to program]
- Security Incidents: support@gardenpatch.xyz
External Standards
Last Updated: 2025-12-13 Next Review: 2026-03-13 (Quarterly)