Skip to main content

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:
src/lib/auth.ts
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

1

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.
2

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'
      }
    }
  }
}
3

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:
src/app/actions/auth.ts
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

src/lib/validation.ts
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:
RolePermissionsUse Case
ADMINFull system access, user management, settingsSystem administrators
ANALYSTCreate, edit, delete vulnerabilitiesSecurity analysts
VIEWERRead-only accessStakeholders, auditors

Implementing Role Checks

// 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:
src/lib/data-access.ts
// 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

1

Enable SSL/TLS

.env
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
2

Use connection pooling

Prevent connection exhaustion attacks:
DATABASE_URL="postgresql://user:pass@host:5432/db?sslmode=require&connection_limit=20"
3

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.
#!/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:
1

Database password rotation

  1. Create new database user/password
  2. Update application configuration
  3. Deploy application update
  4. Remove old database credentials
2

NEXTAUTH_SECRET rotation

Rotating NEXTAUTH_SECRET will invalidate all active sessions.
  1. Schedule during maintenance window
  2. Update secret in environment
  3. Restart application
  4. Notify users to re-authenticate
3

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.
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;
}

Security Headers

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.

Input Validation & Sanitization

Validation with Zod

VulnTrack uses Zod for schema validation:
src/lib/validations.ts
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:
src/middleware.ts
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:
src/lib/audit.ts
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

1

Authentication & Authorization

  • Strong NEXTAUTH_SECRET configured
  • Session timeouts configured appropriately
  • Password policy enforced (12+ characters, complexity)
  • RBAC implemented and tested
  • Team isolation verified
2

Database Security

  • SSL/TLS enabled for database connections
  • Database user permissions minimized
  • Connection pooling configured
  • Database in private network
  • Encryption at rest enabled
  • Regular backups configured
3

Transport Security

  • HTTPS enforced (no HTTP access)
  • TLS 1.2+ only
  • HSTS header configured
  • Security headers implemented
  • CSP policy reviewed and tightened
4

Application Security

  • Input validation on all endpoints
  • Output encoding/escaping in place
  • Rate limiting implemented
  • Audit logging enabled
  • Error messages don’t leak sensitive info
  • Dependencies regularly updated
5

Infrastructure Security

  • Secrets managed securely (not in .env files)
  • Firewall rules configured
  • Monitoring and alerting set up
  • Regular security updates
  • Backup and recovery tested

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

1

Detection

Monitor for security events through:
  • Application logs
  • Database audit logs
  • System monitoring tools
  • User reports
2

Containment

Immediate actions:
  • Isolate affected systems
  • Revoke compromised credentials
  • Enable additional logging
  • Preserve evidence
3

Investigation

  • Review audit logs
  • Identify attack vector
  • Determine scope of breach
  • Document findings
4

Recovery

  • Patch vulnerabilities
  • Restore from clean backups if needed
  • Reset compromised credentials
  • Update security controls
5

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

Build docs developers (and LLMs) love