Overview
VulnTrack is a vulnerability tracking system that handles sensitive security data. This guide covers essential security configurations and best practices for production deployments.
Security is critical for vulnerability management systems. Follow all recommendations in this guide before deploying to production.
Authentication & Authorization
NextAuth Configuration
VulnTrack uses NextAuth.js for authentication with JWT sessions:
export const authOptions : NextAuthOptions = {
adapter: PrismaAdapter ( prisma ),
session: {
strategy: "jwt" ,
maxAge: 30 * 24 * 60 * 60 , // 30 days
},
providers: [
CredentialsProvider ({
// Credentials authentication
})
],
callbacks: {
async jwt ({ token , user }) {
// Include role and team info in JWT
if ( user ) {
token . id = user . id ;
token . role = user . role ;
token . isOnboarded = user . isOnboarded ;
}
return token ;
}
}
}
Secure Session Configuration
Generate secure NEXTAUTH_SECRET
# Generate a secure secret (minimum 32 characters)
openssl rand -base64 32
Add to .env: NEXTAUTH_SECRET =< generated-secret-here >
Never use the same secret across environments. Generate unique secrets for development, staging, and production.
Configure secure cookies
export const authOptions : NextAuthOptions = {
// ... other config
cookies: {
sessionToken: {
name: `__Secure-next-auth.session-token` ,
options: {
httpOnly: true ,
sameSite: 'lax' ,
path: '/' ,
secure: process . env . NODE_ENV === 'production'
}
}
}
}
Set appropriate session timeouts
Balance security and user experience:
High security : 2-4 hours
Standard : 24 hours
Low sensitivity : 30 days (current default)
Password Security
VulnTrack uses bcryptjs for password hashing:
import { hash , compare } from 'bcryptjs' ;
// Hashing passwords (on registration)
const hashedPassword = await hash ( password , 12 );
// Verifying passwords (on login)
const isValid = await compare ( password , user . password );
Implement password requirements:
Minimum 12 characters
Mix of uppercase, lowercase, numbers, and symbols
Check against common password lists
Password Policy Implementation
import { z } from 'zod' ;
export const passwordSchema = z
. string ()
. min ( 12 , 'Password must be at least 12 characters' )
. regex ( / [ A-Z ] / , 'Password must contain uppercase letter' )
. regex ( / [ a-z ] / , 'Password must contain lowercase letter' )
. regex ( / [ 0-9 ] / , 'Password must contain number' )
. regex ( / [ ^ A-Za-z0-9 ] / , 'Password must contain special character' );
Role-Based Access Control (RBAC)
User Roles
VulnTrack implements three user roles:
Role Permissions Use Case ADMIN Full system access, user management, settings System administrators ANALYST Create, edit, delete vulnerabilities Security analysts VIEWER Read-only access Stakeholders, auditors
Implementing Role Checks
Server Actions
API Routes
Middleware
// src/app/actions/vulnerabilities.ts
import { getServerSession } from 'next-auth' ;
import { authOptions } from '@/lib/auth' ;
export async function createVulnerability ( data : VulnerabilityInput ) {
const session = await getServerSession ( authOptions );
// Check authentication
if ( ! session ?. user ) {
throw new Error ( 'Unauthorized' );
}
// Check role authorization
if ( session . user . role === 'VIEWER' ) {
throw new Error ( 'Insufficient permissions' );
}
// Proceed with creation
return await prisma . vulnerability . create ({
data: {
... data ,
userId: session . user . id ,
teamId: session . user . teamId
}
});
}
Team Isolation
VulnTrack implements multi-tenancy with team isolation:
// Always scope queries by team
export async function getVulnerabilities ( userId : string ) {
const user = await prisma . user . findUnique ({
where: { id: userId },
select: { teamId: true , role: true }
});
if ( ! user ?. teamId ) {
throw new Error ( 'User not associated with a team' );
}
return await prisma . vulnerability . findMany ({
where: {
teamId: user . teamId // Team isolation
}
});
}
Always include teamId in database queries to prevent data leakage between teams.
Database Security
Connection Security
Enable SSL/TLS
DATABASE_URL = "postgresql://user:pass@host:5432/db?sslmode=require"
SSL modes:
require: Enforce SSL
verify-ca: Verify certificate authority
verify-full: Verify CA and hostname
Use connection pooling
Prevent connection exhaustion attacks: DATABASE_URL = "postgresql://user:pass@host:5432/db?sslmode=require&connection_limit=20"
Restrict database user permissions
-- Create application user with limited permissions
CREATE USER vulntrack_app WITH PASSWORD 'secure_password' ;
-- Grant only necessary permissions
GRANT CONNECT ON DATABASE vulntrack TO vulntrack_app;
GRANT SELECT , INSERT , UPDATE , DELETE ON ALL TABLES IN SCHEMA public TO vulntrack_app;
GRANT USAGE ON ALL SEQUENCES IN SCHEMA public TO vulntrack_app;
-- Revoke dangerous permissions
REVOKE CREATE ON SCHEMA public FROM vulntrack_app;
Database Security Best Practices
Place database in private subnet
Use security groups/firewall rules to restrict access
Only allow connections from application servers
Enable database audit logging
Enable encryption for:
Database storage volumes
Automated backups
Snapshot copies
Replica storage
Most managed database services provide this by default.
Keep PostgreSQL updated to latest patch version
Subscribe to security advisories
Test updates in staging before production
Maintain update schedule (monthly recommended)
Secrets Management
Environment Variables
Never commit .env files with real secrets to version control.
AWS Secrets Manager
HashiCorp Vault
Kubernetes Secrets
Docker Secrets
#!/bin/bash
# Fetch secrets from AWS Secrets Manager
export DATABASE_URL = $( aws secretsmanager get-secret-value \
--secret-id prod/vulntrack/database-url \
--query SecretString --output text )
export NEXTAUTH_SECRET = $( aws secretsmanager get-secret-value \
--secret-id prod/vulntrack/nextauth-secret \
--query SecretString --output text )
Secret Rotation
Implement regular secret rotation:
Database password rotation
Create new database user/password
Update application configuration
Deploy application update
Remove old database credentials
NEXTAUTH_SECRET rotation
Rotating NEXTAUTH_SECRET will invalidate all active sessions.
Schedule during maintenance window
Update secret in environment
Restart application
Notify users to re-authenticate
Automate rotation
# Example: Monthly rotation via cron
0 2 1 * * /scripts/rotate-secrets.sh
HTTPS & Transport Security
TLS Configuration
Terminate TLS at the load balancer or reverse proxy for optimal performance.
Nginx Configuration
Traefik (Docker)
server {
listen 443 ssl http2;
server_name vulntrack.example.com;
# TLS certificates
ssl_certificate /etc/ssl/certs/vulntrack.crt;
ssl_certificate_key /etc/ssl/private/vulntrack.key;
# TLS configuration
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on ;
# HSTS
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "origin-when-cross-origin" always;
# Proxy to application
location / {
proxy_pass http://localhost:3000;
proxy_set_header Host $ host ;
proxy_set_header X-Real-IP $ remote_addr ;
proxy_set_header X-Forwarded-For $ proxy_add_x_forwarded_for ;
proxy_set_header X-Forwarded-Proto $ scheme ;
}
}
# Redirect HTTP to HTTPS
server {
listen 80 ;
server_name vulntrack.example.com;
return 301 https://$ server_name $ request_uri ;
}
VulnTrack includes security headers in next.config.mjs:
async headers () {
return [
{
source: '/:path*' ,
headers: [
{
key: 'Strict-Transport-Security' ,
value: 'max-age=63072000; includeSubDomains; preload'
},
{
key: 'X-Frame-Options' ,
value: 'SAMEORIGIN' // Prevent clickjacking
},
{
key: 'X-Content-Type-Options' ,
value: 'nosniff' // Prevent MIME sniffing
},
{
key: 'Referrer-Policy' ,
value: 'origin-when-cross-origin'
},
{
key: 'Content-Security-Policy' ,
value: "default-src 'self'; script-src 'self' 'unsafe-eval' 'unsafe-inline';"
}
]
}
]
}
Review and tighten the Content-Security-Policy for your deployment. Remove 'unsafe-eval' and 'unsafe-inline' if possible.
Validation with Zod
VulnTrack uses Zod for schema validation:
import { z } from 'zod' ;
export const vulnerabilitySchema = z . object ({
title: z . string ()
. min ( 1 , 'Title is required' )
. max ( 200 , 'Title too long' ),
description: z . string ()
. min ( 10 , 'Description must be at least 10 characters' )
. max ( 10000 , 'Description too long' ),
severity: z . enum ([ 'CRITICAL' , 'HIGH' , 'MEDIUM' , 'LOW' , 'INFO' ]),
status: z . enum ([ 'OPEN' , 'IN_PROGRESS' , 'RESOLVED' , 'CLOSED' ]),
cveId: z . string ()
. regex ( / ^ CVE- \d {4} - \d {4,} $ / , 'Invalid CVE format' )
. optional (),
cvssScore: z . number ()
. min ( 0 )
. max ( 10 )
. optional ()
});
// Validate input
export async function createVulnerability ( input : unknown ) {
const validated = vulnerabilitySchema . parse ( input );
// Proceed with validated data
}
SQL Injection Prevention
Prisma automatically prevents SQL injection through parameterized queries:
// Safe - Prisma parameterizes automatically
const vulns = await prisma . vulnerability . findMany ({
where: {
title: {
contains: userInput // Automatically escaped
}
}
});
// Dangerous - Never use raw queries with user input
// ❌ DON'T DO THIS
const vulns = await prisma . $queryRawUnsafe (
`SELECT * FROM Vulnerability WHERE title LIKE '% ${ userInput } %'`
);
// If raw queries are necessary, use parameterization
const vulns = await prisma . $queryRaw `
SELECT * FROM "Vulnerability" WHERE title LIKE ${ `% ${ userInput } %` }
` ;
XSS Prevention
Next.js and React automatically escape output:
// Safe - React escapes automatically
const VulnCard = ({ vulnerability }) => (
< div >
< h2 >{vulnerability. title } </ h2 >
< p >{vulnerability. description } </ p >
</ div >
);
// Dangerous - Using dangerouslySetInnerHTML
// Only use with sanitized content
import DOMPurify from 'isomorphic-dompurify' ;
const SafeHTML = ({ content }) => (
< div
dangerouslySetInnerHTML = {{
__html : DOMPurify . sanitize ( content )
}}
/>
);
Rate Limiting
Implement rate limiting to prevent abuse:
import { Ratelimit } from '@upstash/ratelimit' ;
import { Redis } from '@upstash/redis' ;
const ratelimit = new Ratelimit ({
redis: Redis . fromEnv (),
limiter: Ratelimit . slidingWindow ( 10 , '10 s' ),
analytics: true ,
});
export async function middleware ( request : NextRequest ) {
const ip = request . ip ?? '127.0.0.1' ;
const { success , limit , reset , remaining } = await ratelimit . limit ( ip );
if ( ! success ) {
return new Response ( 'Rate limit exceeded' , {
status: 429 ,
headers: {
'X-RateLimit-Limit' : limit . toString (),
'X-RateLimit-Remaining' : remaining . toString (),
'X-RateLimit-Reset' : reset . toString (),
},
});
}
return NextResponse . next ();
}
Adjust rate limits based on endpoints:
Login: 5 attempts per 15 minutes
API endpoints: 100 requests per minute
Vulnerability creation: 10 per minute
Audit Logging
VulnTrack includes an audit log system:
import { prisma } from '@/lib/prisma' ;
export async function createAuditLog ({
action ,
entityType ,
entityId ,
userId ,
teamId ,
details
} : AuditLogInput ) {
return await prisma . auditLog . create ({
data: {
action , // CREATE, UPDATE, DELETE, LOGIN, etc.
entityType , // VULNERABILITY, USER, TEAM
entityId ,
userId ,
teamId ,
details: JSON . stringify ( details )
}
});
}
// Example usage
await createAuditLog ({
action: 'CREATE' ,
entityType: 'VULNERABILITY' ,
entityId: vuln . id ,
userId: session . user . id ,
teamId: session . user . teamId ,
details: {
title: vuln . title ,
severity: vuln . severity
}
});
Events to Log
Authentication events (login, logout, failed attempts)
Vulnerability operations (create, update, delete)
User management (creation, role changes, deletion)
Team operations (creation, user assignments)
Permission changes
Data exports
Configuration changes
Security Checklist
Authentication & Authorization
Security Monitoring
Failed Authentication Tracking
// Track failed login attempts
const failedAttempts = await prisma . auditLog . count ({
where: {
action: 'LOGIN_FAILED' ,
userId: user . id ,
createdAt: {
gte: new Date ( Date . now () - 15 * 60 * 1000 ) // Last 15 minutes
}
}
});
if ( failedAttempts >= 5 ) {
// Lock account or require additional verification
await lockUserAccount ( user . id );
}
Security Alerts
Set up alerts for:
Multiple failed login attempts
Unusual data access patterns
Privilege escalation attempts
Database connection failures
High error rates
Unauthorized API access
Compliance Considerations
Implement data export functionality
Add user deletion (right to be forgotten)
Maintain audit logs
Include privacy policy and consent
Encrypt personal data
Enable comprehensive audit logging
Implement access controls
Regular security assessments
Incident response procedures
Data encryption in transit and at rest
Document security policies
Regular risk assessments
Security awareness training
Business continuity planning
Regular security audits
Incident Response
Security Incident Playbook
Detection
Monitor for security events through:
Application logs
Database audit logs
System monitoring tools
User reports
Containment
Immediate actions:
Isolate affected systems
Revoke compromised credentials
Enable additional logging
Preserve evidence
Investigation
Review audit logs
Identify attack vector
Determine scope of breach
Document findings
Recovery
Patch vulnerabilities
Restore from clean backups if needed
Reset compromised credentials
Update security controls
Post-Incident
Document incident
Update security procedures
Notify affected parties if required
Conduct lessons learned review
Next Steps
Production Deployment Deploy with security best practices
Docker Security Secure containerized deployments