This document outlines the security measures implemented in the Betirement website and provides guidelines for maintaining security best practices.
- Rate Limiting
- CORS Configuration
- Input Validation and Sanitization
- Content Security Policy
- Environment Variables
- Honeypot Protection
- Security Headers
- Best Practices
Rate limiting is implemented using an in-memory store with configurable presets:
import { checkRateLimit, RateLimitPresets, getClientIp } from '@/lib/rate-limit';
// In your API route
const ip = getClientIp(request);
const rateLimitResult = checkRateLimit(ip, RateLimitPresets.STANDARD);
if (!rateLimitResult.success) {
return NextResponse.json(
{ error: 'Too many requests' },
{ status: 429 }
);
}- STRICT: 5 requests per 15 minutes (authentication, payments)
- STANDARD: 10 requests per 10 minutes (form submissions)
- RELAXED: 100 requests per 15 minutes (read operations)
- PUBLIC: 1000 requests per hour (public content)
For production deployments with multiple instances, consider using:
- Redis for distributed rate limiting
- Upstash Rate Limit for serverless environments
- Cloudflare Rate Limiting at the edge
CORS is configured per endpoint with different security levels:
import { createCorsHeaders, STRICT_CORS_CONFIG } from '@/lib/cors';
// In your API route
const corsHeaders = createCorsHeaders(request, STRICT_CORS_CONFIG);DEFAULT_CORS_CONFIG (Public APIs):
- Origin: Site URL or wildcard
- Methods: GET, POST, OPTIONS
- Credentials: false
STRICT_CORS_CONFIG (Sensitive endpoints):
- Origin: Site URL only
- Methods: POST, OPTIONS
- Credentials: false
const customCors: CorsOptions = {
origin: ['https://betirement.com', 'https://www.betirement.com'],
methods: ['GET', 'POST'],
allowedHeaders: ['Content-Type'],
maxAge: 3600,
};Use validation functions from @/lib/validation.ts:
import { validateEmail, validateName, validateMessage } from '@/lib/validation';
const emailResult = validateEmail(email);
if (!emailResult.isValid) {
return { error: emailResult.error };
}Use sanitization functions from @/lib/sanitization.ts:
import { sanitizeEmail, sanitizeName, sanitizeText } from '@/lib/sanitization';
const cleanEmail = sanitizeEmail(userInput);
const cleanName = sanitizeName(userInput);
const cleanText = sanitizeText(userInput, 5000); // max lengthimport { detectXss, detectSqlInjection } from '@/lib/sanitization';
if (detectXss(input) || detectSqlInjection(input)) {
console.warn('Malicious input detected');
return { error: 'Invalid input' };
}sanitizeEmail()- Email addressessanitizeName()- Names (letters, spaces, hyphens, apostrophes)sanitizePhone()- Phone numberssanitizeUrl()- URLs with protocol validationsanitizeText()- General text contentsanitizeHtml()- HTML content (removes dangerous tags)sanitizeFilename()- Filenames (prevents path traversal)sanitizeSlug()- URL slugs
CSP is configured in next.config.mjs:
{
key: 'Content-Security-Policy',
value: [
"default-src 'self'",
"script-src 'self' 'unsafe-eval' 'unsafe-inline' https://www.youtube.com ...",
"style-src 'self' 'unsafe-inline' https://fonts.googleapis.com",
"img-src 'self' data: https: blob:",
// ... more directives
].join('; ')
}- default-src 'self': Only load resources from same origin by default
- script-src: Allowed script sources (includes YouTube, analytics)
- style-src: Allowed style sources (includes Google Fonts)
- img-src: Allowed image sources (includes CDNs)
- frame-src: Allowed iframe sources (YouTube embeds)
- object-src 'none': Block plugins (Flash, Java)
- upgrade-insecure-requests: Upgrade HTTP to HTTPS
- Check browser console for CSP violations
- Use CSP Evaluator
- Monitor violations in production
When adding new third-party services:
- Add domain to appropriate directive
- Test in development
- Monitor for violations
- Update documentation
Environment variables are protected through:
-
Never expose in client code:
// ❌ Wrong - exposed to client const apiKey = process.env.NEXT_PUBLIC_API_KEY; // ✅ Correct - server-side only const apiKey = process.env.API_KEY;
-
Use in API routes only:
// app/api/example/route.ts export async function GET() { const secret = process.env.SECRET_KEY; // Safe }
-
Validate on startup:
if (!process.env.YOUTUBE_API_KEY) { throw new Error('YOUTUBE_API_KEY is required'); }
# YouTube API
YOUTUBE_API_KEY=your_key
YOUTUBE_CHANNEL_ID=your_channel_id
# ConvertKit
CONVERTKIT_API_KEY=your_key
CONVERTKIT_API_SECRET=your_secret
# Site Configuration
NEXT_PUBLIC_SITE_URL=https://betirement.com- Go to Site Settings → Environment Variables
- Add each variable
- Redeploy site
Honeypot fields catch bots that auto-fill all form fields:
// In your form component
<input
type="text"
name="website"
tabIndex={-1}
autoComplete="off"
style={{ position: 'absolute', left: '-9999px' }}
aria-hidden="true"
/>// In your API route
if (body.website || body.phone_number || body.company) {
// Bot detected - return fake success
return NextResponse.json(
{ success: true },
{ status: 200 }
);
}Common honeypot field names:
websitephone_numbercompanyurladdress
- Use CSS to hide (not
display: none) - Add
tabIndex={-1}to prevent keyboard access - Add
aria-hidden="true"for screen readers - Use realistic field names
- Return fake success to avoid bot detection
All security headers are configured in next.config.mjs:
{
'X-DNS-Prefetch-Control': 'on',
'X-Frame-Options': 'SAMEORIGIN',
'X-Content-Type-Options': 'nosniff',
'Referrer-Policy': 'origin-when-cross-origin',
'X-XSS-Protection': '1; mode=block',
'Permissions-Policy': 'camera=(), microphone=(), geolocation=()',
'Content-Security-Policy': '...',
}- X-Frame-Options: Prevents clickjacking attacks
- X-Content-Type-Options: Prevents MIME type sniffing
- Referrer-Policy: Controls referrer information
- X-XSS-Protection: Enables browser XSS filter
- Permissions-Policy: Restricts browser features
- Content-Security-Policy: Controls resource loading
API routes have additional headers:
{
'X-Frame-Options': 'DENY', // Stricter for APIs
'Cache-Control': 'no-store, max-age=0', // No caching
}- Always validate on server-side (client validation is for UX only)
- Sanitize all user input before processing
- Use TypeScript for type safety
- Limit input sizes to prevent DoS
- Check for malicious patterns (XSS, SQL injection)
- Implement rate limiting on all endpoints
- Use CORS to restrict origins
- Validate all parameters (query, body, headers)
- Return appropriate status codes (400, 401, 403, 429, 500)
- Log security events (without exposing PII)
When implementing authentication:
- Use bcrypt for password hashing (cost factor 10+)
- Implement JWT with short expiration
- Use HTTP-only cookies for tokens
- Implement CSRF protection
- Add 2FA for sensitive operations
- Never log sensitive data (passwords, tokens, PII)
- Use HTTPS everywhere (enforced by Netlify)
- Sanitize error messages (don't expose internals)
- Implement proper access controls
- Regular security audits
- Monitor rate limit hits (potential attacks)
- Track failed validations (malicious input)
- Alert on unusual patterns (spike in errors)
- Review logs regularly
- Keep dependencies updated
- Test with malicious input (XSS, SQL injection)
- Test rate limiting (verify limits work)
- Test CORS (verify origins are restricted)
- Test error handling (no information leakage)
- Run security scanners (OWASP ZAP, Burp Suite)
- Rate limiting implemented on all API routes
- CORS configured appropriately per endpoint
- All user input validated and sanitized
- CSP headers configured and tested
- Environment variables protected
- Honeypot fields added to forms
- Security headers configured
- Error messages sanitized
- Logging excludes PII
- Dependencies up to date
- Security testing completed
- Documentation updated
If a security issue is discovered:
- Assess severity (critical, high, medium, low)
- Contain the issue (disable affected feature if needed)
- Fix the vulnerability (patch and test)
- Deploy the fix (emergency deployment if critical)
- Review logs (check for exploitation)
- Notify affected users (if data breach)
- Document the incident (for future prevention)
- Update security measures (prevent recurrence)
For security concerns, contact: [security@betirement.com]