This document outlines the security measures implemented in QA Studio and provides guidance for maintaining a secure deployment.
- Authentication Security
- CSRF Protection
- Rate Limiting
- Environment Variables
- Session Management
- Password Reset Security
- Production Deployment Checklist
- Future Improvements
- Algorithm: bcrypt with 12 rounds (OWASP 2025 recommended)
- Implementation: src/lib/server/crypto.ts
- Note: Bcrypt automatically handles salting and is resistant to timing attacks
Enforced on signup, password reset, and password setup:
- Minimum 8 characters
- At least one uppercase letter
- At least one lowercase letter
- At least one number
- Generation: Cryptographically secure random tokens (32 bytes)
- Storage: HMAC-SHA256 hashed in database
- Transmission: HTTP-only cookies (prevents XSS theft)
- Validation: Constant-time comparison (prevents timing attacks)
- Expiry: 30 days, automatically cleaned up every 6 hours
All state-changing authentication endpoints validate CSRF tokens:
- Login: src/routes/api/auth/login/+server.ts
- Signup: src/routes/api/auth/signup/+server.ts
- Setup Password: src/routes/api/auth/setup-password/+server.ts
- Request Reset: src/routes/api/auth/request-reset/+server.ts
- Reset Password: src/routes/api/auth/reset-password/+server.ts
- Server generates CSRF token on session creation
- Token stored in non-httpOnly cookie (client can read)
- Client includes token in request body for state-changing operations
- Server validates cookie token matches submitted token
- Request rejected if tokens don't match (403 Forbidden)
When making authentication API calls, include the CSRF token:
// Read CSRF token from cookie
const csrfToken = document.cookie
.split('; ')
.find((row) => row.startsWith('qa_studio_csrf='))
?.split('=')[1];
// Include in request
await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email: 'user@example.com',
password: 'password123',
csrfToken // <-- Include this
})
});Location: src/routes/api/auth/login/+server.ts
Limitations:
- In-memory storage (resets on server restart)
- Not suitable for multi-instance deployments
- No automatic cleanup (Map grows indefinitely)
Rules:
- 5 login attempts per email address
- 15-minute cooldown period
- Counter reset on successful login
For production deployments, especially with multiple server instances, migrate to Redis-based rate limiting:
import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from '@upstash/redis';
const ratelimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(5, '15 m'), // 5 requests per 15 minutes
analytics: true
});
// In login endpoint
const { success } = await ratelimit.limit(email);
if (!success) {
throw error(429, {
message: 'Too many login attempts. Please try again in 15 minutes.'
});
}Setup:
- Add dependency:
npm install @upstash/ratelimit @upstash/redis - Create Redis instance in Vercel Dashboard → Storage → Redis
- Environment variables automatically configured
import { RateLimiterRedis } from 'rate-limiter-flexible';
import Redis from 'ioredis';
const redisClient = new Redis({
host: process.env.REDIS_HOST,
port: parseInt(process.env.REDIS_PORT || '6379'),
password: process.env.REDIS_PASSWORD
});
const rateLimiter = new RateLimiterRedis({
storeClient: redisClient,
keyPrefix: 'login',
points: 5, // 5 attempts
duration: 900 // 15 minutes in seconds
});
// In login endpoint
try {
await rateLimiter.consume(email);
} catch (err) {
throw error(429, {
message: 'Too many login attempts. Please try again in 15 minutes.'
});
}Setup:
- Add dependencies:
npm install rate-limiter-flexible ioredis - Configure Redis connection in environment variables
- Update login endpoint to use rate limiter
- Different limits for different endpoints: Login (5/15m), Signup (3/hour), Password Reset (3/hour)
- IP-based limiting: Add IP-based rate limiting for additional protection
- Gradual backoff: Increase cooldown period for repeated violations
- Monitoring: Log rate limit hits for security monitoring
- User feedback: Provide clear error messages with retry timing
All security-critical environment variables are validated at server startup (see src/lib/server/env.ts).
- Purpose: HMAC signing of session tokens
- Required: Yes (production will not start without it)
- Generate:
node -e "console.log(require('crypto').randomBytes(32).toString('base64'))" - Security: Must be cryptographically random, never commit to version control
- Purpose: HMAC signing of password reset tokens
- Required: No (falls back to SESSION_SECRET)
- Recommended: Use separate value in production
- Generate: Same as SESSION_SECRET
- Purpose: Protect cron job endpoints from unauthorized access
- Required: Yes (for cleanup jobs to run)
- Generate: Same as SESSION_SECRET
- Used by: src/routes/api/cron/cleanup-sessions/+server.ts
The server validates environment variables at startup:
// src/hooks.server.ts
import { validateEnvironment } from '$lib/server/env';
// Validates all security-critical environment variables
validateEnvironment();Behavior:
- Development: Allows defaults with warnings
- Production: Requires actual values, rejects defaults, fails fast
- Creation: User logs in → session token generated → stored in database + cookie
- Validation: Each request → validate session ID + token → retrieve user ID
- Expiration: Sessions expire after 30 days
- Cleanup: Automated cron job removes expired sessions every 6 hours
- Database: PostgreSQL via Prisma ORM
- Table:
Sessionmodel - Indexes:
id(primary key),userId(foreign key),expiresAt(for cleanup) - Token Security: Only HMAC hash stored, not the actual token
{
path: '/',
httpOnly: true, // Prevents JavaScript access (XSS protection)
sameSite: 'lax', // CSRF protection
secure: true, // HTTPS only (production)
maxAge: 2592000 // 30 days in seconds
}- Create: src/lib/server/sessions.ts →
createSession() - Validate: src/lib/server/sessions.ts →
validateSession() - Delete: src/lib/server/sessions.ts →
deleteSession() - Cleanup: src/lib/server/sessions.ts →
cleanupExpiredSessions()
- Token: 32-byte cryptographically secure random
- Storage: HMAC-SHA256 hashed in database
- Expiry: 1 hour
- Single Use: Marked as used after successful reset
- User requests reset → token generated → email sent (TODO: implement)
- User clicks link → validates token → sets new password
- Token marked as used → all sessions invalidated → user must re-login
- Timing-safe validation: Constant-time comparison prevents timing attacks
- User enumeration prevention: Always return success (don't reveal if user exists)
- Token format:
tokenId:tokenprevents database enumeration - Session invalidation: Force re-login after password reset
- Cleanup: Expired/used tokens removed every 6 hours
Before deploying to production, ensure:
-
SESSION_SECRETset to cryptographically random value -
RESET_SECRETset (or confirmed SESSION_SECRET fallback is acceptable) -
CRON_SECRETset for cleanup job authentication - All default values removed from environment
- HTTPS enabled (required for secure cookies)
- CSRF protection tested and working
- Rate limiting tested (consider Redis migration for scaling)
- Session cleanup cron job verified in Vercel dashboard
- Password requirements enforced on all endpoints
- Rate limit violations logged
- Failed authentication attempts logged
- Cron job execution logged
- Environment validation warnings reviewed
- Configure email service for password reset
- Remove console.log of reset links
- Test email delivery in production
-
Redis-Based Rate Limiting
- Migrate from in-memory to Redis/Upstash
- Add IP-based rate limiting
- Implement gradual backoff
-
Email Integration
- Set up transactional email service (Resend, SendGrid, etc.)
- Design password reset email template
- Remove console.log of reset links
-
Email Verification
- Require email verification on signup
- Send verification link with time-limited token
- Mark accounts as verified in database
-
Two-Factor Authentication (2FA)
- TOTP-based 2FA support
- Backup codes for account recovery
- Remember device option
-
Session Management UI
- View active sessions
- Revoke individual sessions
- "Logout from all devices" functionality
-
Audit Logs
- Track all authentication events
- Log IP addresses and user agents
- Retention policy for compliance
-
Security Headers
- Content-Security-Policy
- X-Frame-Options
- X-Content-Type-Options
- Referrer-Policy
-
SSO Integration
- Support for Authentik, Authelia
- SAML/OAuth provider support
- Enterprise directory integration
If you discover a security vulnerability, please email security@qastudio.dev instead of creating a public issue.