Security
Auktiva implements multiple layers of security to protect your data and users. This page documents the security features and best practices for deployment.
Overview
Security features include:
- XSS Protection - HTML sanitization for user-generated content
- CSRF Protection - Built-in NextAuth.js CSRF tokens
- Rate Limiting - Protection against brute-force attacks
- Input Validation - Zod schema validation on all API endpoints
- SQL Injection Prevention - Prisma ORM with parameterized queries
- Path Traversal Protection - Secure file upload handling
- Security Headers - Comprehensive HTTP security headers
Content Security Policy (CSP)
Auktiva includes a strict Content Security Policy configured in next.config.ts:
{
"default-src": "'self'",
"script-src": "'self' 'unsafe-inline' 'unsafe-eval' https://www.google.com https://www.gstatic.com https://www.recaptcha.net",
"style-src": "'self' 'unsafe-inline'",
"img-src": "'self' data: blob: https:",
"frame-src": "'self' https://www.google.com https://www.recaptcha.net",
"frame-ancestors": "'none'",
"object-src": "'none'",
"upgrade-insecure-requests": true
}unsafe-inline and unsafe-eval are required for Google reCAPTCHA
compatibility. If you don’t use reCAPTCHA, you can remove these directives for
stricter security.
Additional Security Headers
- Strict-Transport-Security - Enforces HTTPS
- X-Content-Type-Options - Prevents MIME sniffing
- X-Frame-Options - Prevents clickjacking
- X-XSS-Protection - Legacy XSS filter
- Referrer-Policy - Controls referrer information
- Permissions-Policy - Restricts browser features
- Cross-Origin-Opener-Policy - Isolates browsing context
- Cross-Origin-Resource-Policy - Restricts resource loading
XSS Protection
Rich Text Content
User-generated rich text content is sanitized using DOMPurify before rendering:
// src/components/ui/rich-text-editor.tsx
import DOMPurify from "dompurify";
const sanitizedContent = DOMPurify.sanitize(content, {
ALLOWED_TAGS: [
"p",
"br",
"strong",
"em",
"u",
"s",
"h1",
"h2",
"h3",
"ul",
"ol",
"li",
"a",
"blockquote",
],
ALLOWED_ATTR: ["href", "target", "rel", "class"],
});Email Templates
All user-provided content in email templates is HTML-encoded using the escapeHtml() utility:
// src/lib/email/layout.ts
export function escapeHtml(unsafe: string): string {
return unsafe
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}Rate Limiting
Auktiva includes rate limiting middleware to protect against brute-force attacks.
Configuration
Rate limiting is implemented in src/lib/api/middleware/rate-limit.ts using rate-limiter-flexible .
| Endpoint | Limit | Window |
|---|---|---|
| Registration | 3 requests | 1 hour |
| Password Reset | 3 requests | 1 hour |
| Authentication | 5 requests | 15 minutes |
| Bid Placement | 30 requests | 1 minute |
Usage
import { withRateLimit, withRegistrationRateLimit } from "@/lib/api";
// Use preset rate limiters
export default createHandler({
POST: [[withRegistrationRateLimit], register],
});
// Or create custom rate limits
export default createHandler({
POST: [[withRateLimit({ points: 10, duration: 60 })], handler],
});Serverless Limitation: The default in-memory rate limiting only works reliably for self-hosted deployments (PM2, Docker, single server). On serverless platforms like Vercel, each function invocation may run in a different instance, making in-memory storage ineffective. For Vercel deployments, consider using: - @upstash/ratelimit with Upstash Redis
- Vercel KV - External Redis with
RateLimiterRedis
Input Validation
All API endpoints use Zod schemas for input validation:
const createAuctionSchema = z.object({
name: z.string().min(1).max(100),
description: z.string().max(5000).optional(),
currency: z.string().length(3),
// ...
});
export default createHandler({
POST: [[withAuth, withValidation(createAuctionSchema)], createAuction],
});File Upload Security
Path Traversal Protection
File uploads are protected against path traversal attacks:
// src/pages/api/uploads/[...path].ts
const uploadsDir = path.resolve(process.cwd(), "public/uploads");
const filePath = path.resolve(uploadsDir, relativePath);
// Ensure resolved path stays within uploads directory
if (!filePath.startsWith(uploadsDir + path.sep)) {
return res.status(400).json({ error: "Invalid path" });
}File Type Validation
Only allowed image types are accepted:
image/jpegimage/pngimage/webpimage/gif
Size Limits
- Item images: 10MB max
- Auction thumbnails: 5MB max
- Maximum 10 images per item
Authentication Security
Password Hashing
Passwords are hashed using bcryptjs with 12 salt rounds:
const passwordHash = await bcrypt.hash(password, 12);Password Reset
- Tokens are hashed with SHA-256 before storage
- Tokens expire after 10 minutes
- Rate limited to 3 requests per hour
- Email enumeration is prevented (same response regardless of email existence)
Email Enumeration Prevention
Registration and password reset endpoints don’t reveal whether an email exists:
// Registration: Always returns success, sends different emails
// - New user: Welcome email
// - Existing user: "Account already exists" email with login link
// Password reset: Always returns same message
return res.status(200).json({
message:
"If an account with that email exists, we've sent a password reset link.",
});Cron Job Security
Cron endpoints are protected by a secret token:
# .env
CRON_SECRET=your-secure-random-string// Cron requests must include the secret
Authorization: Bearer ${CRON_SECRET}In production (NODE_ENV=production), CRON_SECRET is required. The cron
endpoints will return a 500 error if not configured.
Environment Variables
Sensitive configuration should be stored in environment variables:
| Variable | Description | Required |
|---|---|---|
NEXTAUTH_SECRET | Session encryption key | Yes |
DATABASE_URL | Database connection string | Yes |
CRON_SECRET | Cron job authentication | Production |
RECAPTCHA_SECRET_KEY | reCAPTCHA server key | Optional |
S3_SECRET_ACCESS_KEY | S3 storage credentials | If using S3 |
See Configuration for the complete list.
Security Best Practices
For Self-Hosted Deployments
- Use HTTPS - Always deploy behind HTTPS (nginx, Caddy, or cloud load balancer)
- Set strong secrets - Use cryptographically random values for
NEXTAUTH_SECRETandCRON_SECRET - Keep dependencies updated - Regularly run
npm auditand update packages - Backup database - Regular backups, especially before updates
- Monitor logs - Watch for suspicious activity patterns
For Vercel Deployments
- Use Vercel’s environment variables - Never commit secrets to git
- Enable Vercel’s DDoS protection - Included by default
- Consider Upstash Redis - For effective rate limiting
- Use Vercel KV - For session storage if needed
Reporting Security Issues
If you discover a security vulnerability, please report it responsibly:
- Do not open a public GitHub issue
- Email security concerns to the maintainers
- Allow time for a fix before public disclosure
Changelog
December 2024
- Added DOMPurify sanitization for rich text content
- Implemented email enumeration prevention on registration
- Added rate limiting middleware with
rate-limiter-flexible - Enhanced path traversal protection for file uploads
- Added HTML encoding for email templates
- Required
CRON_SECRETin production environments - Improved CSP with
object-src 'none'andupgrade-insecure-requests