Network Security
HTTPS configuration, Content Security Policy, rate limiting, and network protection measures.
Network Security
Smart Shelf implements comprehensive network security measures to protect against various attack vectors and ensure secure communication between clients and servers.
HTTPS & TLS Configuration
SSL/TLS Enforcement
// next.config.mjs
const nextConfig = {
// Force HTTPS in production
async redirects() {
if (process.env.NODE_ENV === 'production') {
return [
{
source: '/(.*)',
has: [
{
type: 'header',
key: 'x-forwarded-proto',
value: 'http',
},
],
destination: 'https://smartshelf.app/:path*',
permanent: true,
},
]
}
return []
},
// Security headers
async headers() {
return [
{
source: '/(.*)',
headers: [
{
key: 'Strict-Transport-Security',
value: 'max-age=31536000; includeSubDomains; preload'
},
{
key: 'X-Frame-Options',
value: 'DENY'
},
{
key: 'X-Content-Type-Options',
value: 'nosniff'
},
{
key: 'X-XSS-Protection',
value: '1; mode=block'
},
{
key: 'Referrer-Policy',
value: 'strict-origin-when-cross-origin'
},
{
key: 'Permissions-Policy',
value: 'camera=(), microphone=(), geolocation=(), payment=()'
}
]
}
]
}
}
export default nextConfig
Middleware HTTPS Enforcement
// middleware.ts
import { NextRequest, NextResponse } from 'next/server'
export function middleware(request: NextRequest) {
const response = NextResponse.next()
// Enforce HTTPS in production
if (
process.env.NODE_ENV === 'production' &&
request.headers.get('x-forwarded-proto') !== 'https'
) {
return NextResponse.redirect(
`https://${request.headers.get('host')}${request.nextUrl.pathname}${request.nextUrl.search}`,
301
)
}
// Add security headers
response.headers.set('X-DNS-Prefetch-Control', 'off')
response.headers.set('X-Frame-Options', 'DENY')
response.headers.set('X-Content-Type-Options', 'nosniff')
response.headers.set('Referrer-Policy', 'origin-when-cross-origin')
response.headers.set('X-XSS-Protection', '1; mode=block')
return response
}
Content Security Policy (CSP)
Comprehensive CSP Implementation
// lib/security/csp.ts
export class ContentSecurityPolicy {
private static getCSPDirectives(): Record<string, string[]> {
const isProd = process.env.NODE_ENV === 'production'
return {
'default-src': ["'self'"],
'script-src': [
"'self'",
...(isProd ? [] : ["'unsafe-eval'", "'unsafe-inline'"]),
'https://vercel.live',
'https://*.supabase.co',
'https://va.vercel-scripts.com'
],
'style-src': [
"'self'",
"'unsafe-inline'",
'https://fonts.googleapis.com'
],
'font-src': [
"'self'",
'https://fonts.gstatic.com',
'data:'
],
'img-src': [
"'self'",
'data:',
'blob:',
'https:',
'https://*.supabase.co'
],
'connect-src': [
"'self'",
'https://*.supabase.co',
'wss://*.supabase.co',
'https://vercel.live',
'https://vitals.vercel-insights.com'
],
'frame-src': ["'none'"],
'object-src': ["'none'"],
'base-uri': ["'self'"],
'form-action': ["'self'"],
'frame-ancestors': ["'none'"],
'upgrade-insecure-requests': []
}
}
static generateCSPHeader(): string {
const directives = this.getCSPDirectives()
return Object.entries(directives)
.map(([directive, sources]) => {
if (sources.length === 0) {
return directive
}
return `${directive} ${sources.join(' ')}`
})
.join('; ')
}
static applyCSP(response: NextResponse): NextResponse {
const csp = this.generateCSPHeader()
response.headers.set('Content-Security-Policy', csp)
// Also set CSP report-only for monitoring
if (process.env.CSP_REPORT_URI) {
const reportOnlyCSP = `${csp}; report-uri ${process.env.CSP_REPORT_URI}`
response.headers.set('Content-Security-Policy-Report-Only', reportOnlyCSP)
}
return response
}
}
// Apply CSP in middleware
export function middleware(request: NextRequest) {
const response = NextResponse.next()
return ContentSecurityPolicy.applyCSP(response)
}
CSP Violation Reporting
// app/api/csp-report/route.ts
import { NextRequest, NextResponse } from 'next/server'
export async function POST(request: NextRequest) {
try {
const report = await request.json()
// Log CSP violation
console.error('CSP Violation:', {
'document-uri': report['document-uri'],
'violated-directive': report['violated-directive'],
'blocked-uri': report['blocked-uri'],
'original-policy': report['original-policy'],
'user-agent': request.headers.get('user-agent'),
timestamp: new Date().toISOString()
})
// Store violation in database for analysis
await supabase
.from('csp_violations')
.insert({
document_uri: report['document-uri'],
violated_directive: report['violated-directive'],
blocked_uri: report['blocked-uri'],
original_policy: report['original-policy'],
user_agent: request.headers.get('user-agent'),
ip_address: request.headers.get('x-forwarded-for') || 'unknown',
created_at: new Date().toISOString()
})
return NextResponse.json({ received: true })
} catch (error) {
console.error('CSP report processing failed:', error)
return NextResponse.json({ error: 'Failed to process report' }, { status: 500 })
}
}
Rate Limiting
Advanced Rate Limiting Strategy
// lib/security/rate-limit.ts
import { Ratelimit } from '@upstash/ratelimit'
import { Redis } from '@upstash/redis'
const redis = new Redis({
url: process.env.UPSTASH_REDIS_REST_URL!,
token: process.env.UPSTASH_REDIS_REST_TOKEN!
})
export class RateLimitManager {
// Authentication rate limiting - strict
static authRateLimit = new Ratelimit({
redis,
limiter: Ratelimit.slidingWindow(5, '15 m'),
analytics: true,
prefix: 'rl:auth'
})
// General API rate limiting
static apiRateLimit = new Ratelimit({
redis,
limiter: Ratelimit.slidingWindow(100, '1 h'),
analytics: true,
prefix: 'rl:api'
})
// Premium users get higher limits
static premiumApiRateLimit = new Ratelimit({
redis,
limiter: Ratelimit.slidingWindow(1000, '1 h'),
analytics: true,
prefix: 'rl:api:premium'
})
// Search API - more restrictive
static searchRateLimit = new Ratelimit({
redis,
limiter: Ratelimit.slidingWindow(20, '1 m'),
analytics: true,
prefix: 'rl:search'
})
// File upload rate limiting
static uploadRateLimit = new Ratelimit({
redis,
limiter: Ratelimit.slidingWindow(10, '10 m'),
analytics: true,
prefix: 'rl:upload'
})
// Get appropriate rate limiter based on user and endpoint
static getRateLimiter(userRole: string, endpoint: string): Ratelimit {
if (endpoint.includes('/auth/')) {
return this.authRateLimit
}
if (endpoint.includes('/search')) {
return this.searchRateLimit
}
if (endpoint.includes('/upload')) {
return this.uploadRateLimit
}
if (userRole === 'admin' || userRole === 'premium') {
return this.premiumApiRateLimit
}
return this.apiRateLimit
}
// Apply rate limiting with custom response
static async applyRateLimit(
identifier: string,
rateLimiter: Ratelimit,
request: NextRequest
): Promise<NextResponse | null> {
const { success, limit, reset, remaining } = await rateLimiter.limit(identifier)
if (!success) {
const resetTime = new Date(reset)
// Log rate limit violation
await this.logRateLimitViolation({
identifier,
endpoint: request.nextUrl.pathname,
limit,
reset_time: resetTime.toISOString(),
ip_address: request.headers.get('x-forwarded-for') || 'unknown',
user_agent: request.headers.get('user-agent') || 'unknown'
})
return NextResponse.json(
{
error: 'Rate limit exceeded',
limit,
remaining: 0,
reset: resetTime.toISOString()
},
{
status: 429,
headers: {
'X-RateLimit-Limit': limit.toString(),
'X-RateLimit-Remaining': '0',
'X-RateLimit-Reset': reset.toString(),
'Retry-After': Math.ceil((reset - Date.now()) / 1000).toString()
}
}
)
}
return null // No rate limit exceeded
}
private static async logRateLimitViolation(details: any): Promise<void> {
await supabase
.from('rate_limit_violations')
.insert({
...details,
created_at: new Date().toISOString()
})
}
}
Rate Limiting Middleware
// lib/security/rate-limit-middleware.ts
export function withRateLimit(
handler: (req: NextRequest) => Promise<NextResponse>,
options?: {
rateLimiter?: Ratelimit
getIdentifier?: (req: NextRequest) => string
}
) {
return async function(request: NextRequest): Promise<NextResponse> {
try {
// Get user info for role-based limiting
const user = await getCurrentUser(request)
const userRole = user?.user_metadata?.role || 'viewer'
// Determine rate limiter
const rateLimiter = options?.rateLimiter ||
RateLimitManager.getRateLimiter(userRole, request.nextUrl.pathname)
// Get identifier (IP + user ID if authenticated)
const identifier = options?.getIdentifier?.(request) ||
`${request.headers.get('x-forwarded-for') || 'unknown'}:${user?.id || 'anonymous'}`
// Apply rate limiting
const rateLimitResponse = await RateLimitManager.applyRateLimit(
identifier,
rateLimiter,
request
)
if (rateLimitResponse) {
return rateLimitResponse
}
// Proceed with original handler
return await handler(request)
} catch (error) {
console.error('Rate limiting error:', error)
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 }
)
}
}
}
DDoS Protection
Application-Level DDoS Protection
// lib/security/ddos-protection.ts
export class DDoSProtection {
private static suspiciousIPs = new Set<string>()
private static blockedIPs = new Set<string>()
static async checkForDDoSAttack(request: NextRequest): Promise<boolean> {
const ip = request.headers.get('x-forwarded-for') || 'unknown'
const now = Date.now()
// Check if IP is already blocked
if (this.blockedIPs.has(ip)) {
return true
}
// Collect metrics for the IP
const metrics = await this.collectIPMetrics(ip, now)
// Analyze patterns
const isDDoS = await this.analyzeDDoSPatterns(ip, metrics)
if (isDDoS) {
await this.blockIP(ip, 'DDoS_detected')
return true
}
return false
}
private static async collectIPMetrics(ip: string, timestamp: number) {
const timeWindow = 60 * 1000 // 1 minute
const startTime = new Date(timestamp - timeWindow).toISOString()
const { data: requests } = await supabase
.from('request_logs')
.select('*')
.eq('ip_address', ip)
.gte('created_at', startTime)
return {
request_count: requests?.length || 0,
unique_endpoints: new Set(requests?.map(r => r.endpoint)).size,
error_rate: requests?.filter(r => r.status >= 400).length || 0,
avg_request_size: requests?.reduce((sum, r) => sum + (r.request_size || 0), 0) / (requests?.length || 1)
}
}
private static async analyzeDDoSPatterns(ip: string, metrics: any): Promise<boolean> {
// Pattern 1: Too many requests per minute
if (metrics.request_count > 1000) {
await this.logSecurityEvent(ip, 'high_request_volume', metrics)
return true
}
// Pattern 2: High error rate (scanning for vulnerabilities)
if (metrics.error_rate > 50 && metrics.request_count > 100) {
await this.logSecurityEvent(ip, 'high_error_rate', metrics)
return true
}
// Pattern 3: Requesting too many different endpoints (reconnaissance)
if (metrics.unique_endpoints > 50) {
await this.logSecurityEvent(ip, 'endpoint_scanning', metrics)
return true
}
// Pattern 4: Unusual request patterns
if (metrics.avg_request_size > 1000000) { // > 1MB average
await this.logSecurityEvent(ip, 'large_payload_attack', metrics)
return true
}
return false
}
private static async blockIP(ip: string, reason: string): Promise<void> {
this.blockedIPs.add(ip)
// Store in database for persistent blocking
await supabase
.from('blocked_ips')
.insert({
ip_address: ip,
reason,
blocked_at: new Date().toISOString(),
expires_at: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString() // 24 hours
})
// Log security incident
await this.logSecurityEvent(ip, 'ip_blocked', { reason })
// Notify security team
await this.notifySecurityTeam({
type: 'ip_blocked',
ip_address: ip,
reason,
timestamp: new Date().toISOString()
})
}
private static async logSecurityEvent(ip: string, event_type: string, details: any): Promise<void> {
await supabase
.from('security_events')
.insert({
ip_address: ip,
event_type,
details,
severity: 'high',
created_at: new Date().toISOString()
})
}
private static async notifySecurityTeam(alert: any): Promise<void> {
// Implementation would send alerts to security team
console.log('Security Alert:', alert)
}
}
Web Application Firewall (WAF)
Custom WAF Rules
// lib/security/waf.ts
export class WebApplicationFirewall {
// Common attack patterns
private static readonly ATTACK_PATTERNS = {
sql_injection: [
/(\s|^)(union|select|insert|update|delete|drop|create|alter|exec|execute)(\s|$)/i,
/(\s|^)(or|and)\s+\d+\s*=\s*\d+/i,
/'\s*(or|and)\s*'.*?'\s*=\s*'/i,
/(\s|^)(sleep|benchmark|waitfor)\s*\(/i
],
xss: [
/<script[^>]*>.*?<\/script>/i,
/javascript:/i,
/on\w+\s*=/i,
/<iframe[^>]*>.*?<\/iframe>/i,
/eval\s*\(/i,
/expression\s*\(/i
],
command_injection: [
/(\s|^)(cat|ls|pwd|id|uname|whoami|ps|netstat|ifconfig)(\s|$)/i,
/[;&|`$()]/,
/\.\.\//,
/\/etc\/passwd/i,
/\/bin\/(sh|bash|csh|tcsh|zsh)/i
],
directory_traversal: [
/\.\.\//,
/\.\.\\/,
/\/etc\/passwd/i,
/\/windows\/system32/i,
/\\windows\\system32/i
],
file_inclusion: [
/php:\/\//i,
/file:\/\//i,
/ftp:\/\//i,
/data:\/\//i,
/include\s*\(/i,
/require\s*\(/i
]
}
static async analyzeRequest(request: NextRequest): Promise<{
blocked: boolean
reasons: string[]
risk_score: number
}> {
const reasons: string[] = []
let riskScore = 0
// Analyze URL
const urlAnalysis = this.analyzeURL(request.url)
if (urlAnalysis.malicious) {
reasons.push(...urlAnalysis.reasons)
riskScore += urlAnalysis.score
}
// Analyze headers
const headerAnalysis = this.analyzeHeaders(request.headers)
if (headerAnalysis.malicious) {
reasons.push(...headerAnalysis.reasons)
riskScore += headerAnalysis.score
}
// Analyze body (for POST requests)
if (request.method === 'POST' || request.method === 'PUT') {
try {
const body = await request.text()
const bodyAnalysis = this.analyzeBody(body)
if (bodyAnalysis.malicious) {
reasons.push(...bodyAnalysis.reasons)
riskScore += bodyAnalysis.score
}
} catch (error) {
// Body might not be text, skip analysis
}
}
const blocked = riskScore >= 7 || reasons.length >= 3
if (blocked) {
await this.logWAFBlock(request, reasons, riskScore)
}
return { blocked, reasons, risk_score: riskScore }
}
private static analyzeURL(url: string): { malicious: boolean; reasons: string[]; score: number } {
const reasons: string[] = []
let score = 0
// Check for attack patterns in URL
Object.entries(this.ATTACK_PATTERNS).forEach(([attackType, patterns]) => {
patterns.forEach(pattern => {
if (pattern.test(url)) {
reasons.push(`${attackType}_in_url`)
score += 3
}
})
})
// Check for suspicious characters
if (/[<>'"{}()]/g.test(url)) {
reasons.push('suspicious_characters_in_url')
score += 1
}
// Check for excessively long URLs
if (url.length > 2000) {
reasons.push('excessive_url_length')
score += 2
}
return { malicious: score > 0, reasons, score }
}
private static analyzeHeaders(headers: Headers): { malicious: boolean; reasons: string[]; score: number } {
const reasons: string[] = []
let score = 0
// Check User-Agent
const userAgent = headers.get('user-agent') || ''
if (!userAgent || userAgent.length < 10) {
reasons.push('suspicious_user_agent')
score += 2
}
// Check for attack patterns in headers
headers.forEach((value, key) => {
Object.entries(this.ATTACK_PATTERNS).forEach(([attackType, patterns]) => {
patterns.forEach(pattern => {
if (pattern.test(value)) {
reasons.push(`${attackType}_in_header_${key}`)
score += 3
}
})
})
})
// Check for HTTP method override attacks
const methodOverride = headers.get('x-http-method-override')
if (methodOverride && !['GET', 'POST', 'PUT', 'DELETE', 'PATCH'].includes(methodOverride.toUpperCase())) {
reasons.push('invalid_method_override')
score += 2
}
return { malicious: score > 0, reasons, score }
}
private static analyzeBody(body: string): { malicious: boolean; reasons: string[]; score: number } {
const reasons: string[] = []
let score = 0
// Check for attack patterns in body
Object.entries(this.ATTACK_PATTERNS).forEach(([attackType, patterns]) => {
patterns.forEach(pattern => {
if (pattern.test(body)) {
reasons.push(`${attackType}_in_body`)
score += 4 // Body attacks are more serious
}
})
})
// Check for excessive body size
if (body.length > 10000000) { // 10MB
reasons.push('excessive_body_size')
score += 3
}
return { malicious: score > 0, reasons, score }
}
private static async logWAFBlock(request: NextRequest, reasons: string[], riskScore: number): Promise<void> {
await supabase
.from('waf_blocks')
.insert({
ip_address: request.headers.get('x-forwarded-for') || 'unknown',
method: request.method,
url: request.url,
user_agent: request.headers.get('user-agent') || 'unknown',
reasons,
risk_score: riskScore,
blocked_at: new Date().toISOString()
})
}
}
WAF Middleware Implementation
// lib/security/waf-middleware.ts
export function withWAF(handler: (req: NextRequest) => Promise<NextResponse>) {
return async function(request: NextRequest): Promise<NextResponse> {
try {
// Skip WAF for certain paths (like health checks)
const skipPaths = ['/health', '/api/health', '/_next/', '/favicon.ico']
if (skipPaths.some(path => request.nextUrl.pathname.startsWith(path))) {
return await handler(request)
}
// Analyze request with WAF
const analysis = await WebApplicationFirewall.analyzeRequest(request)
if (analysis.blocked) {
return NextResponse.json(
{
error: 'Request blocked by security policy',
reference_id: crypto.randomUUID()
},
{ status: 403 }
)
}
// Add security score to headers for monitoring
const response = await handler(request)
response.headers.set('X-Security-Score', analysis.risk_score.toString())
return response
} catch (error) {
console.error('WAF processing error:', error)
// In case of WAF error, allow request to proceed but log the incident
return await handler(request)
}
}
}
This comprehensive network security framework provides multiple layers of protection against various attack vectors while maintaining application performance and user experience.