Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 49 additions & 4 deletions sim/middleware.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
import { NextRequest, NextResponse } from 'next/server'
import { getSessionCookie } from 'better-auth/cookies'
import { verifyToken } from './lib/waitlist/token'
import { createLogger } from '@/lib/logs/console-logger'

const logger = createLogger('Middleware')

// Environment flag to check if we're in development mode
const isDevelopment = process.env.NODE_ENV === 'development'

const SUSPICIOUS_UA_PATTERNS = [
/^\s*$/, // Empty user agents
/\.\./, // Path traversal attempt
/<\s*script/i, // Potential XSS payloads
Comment thread
waleedlatif1 marked this conversation as resolved.
/^\(\)\s*{/, // Command execution attempt
/\b(sqlmap|nikto|gobuster|dirb|nmap)\b/i // Known scanning tools
]

export async function middleware(request: NextRequest) {
// Check for active session
const sessionCookie = getSessionCookie(request)
Expand Down Expand Up @@ -66,7 +77,7 @@ export async function middleware(request: NextRequest) {
return NextResponse.redirect(new URL('/', request.url))
}
} catch (error) {
console.error('Token validation error:', error)
logger.error('Token validation error:', error)
// In case of error, redirect signup attempts to home
if (request.nextUrl.pathname === '/signup') {
return NextResponse.redirect(new URL('/', request.url))
Expand All @@ -80,15 +91,49 @@ export async function middleware(request: NextRequest) {
}
}

return NextResponse.next()
const userAgent = request.headers.get('user-agent') || ''

const isSuspicious = SUSPICIOUS_UA_PATTERNS.some(pattern =>
pattern.test(userAgent)
)

if (isSuspicious) {
Comment thread
waleedlatif1 marked this conversation as resolved.
logger.warn('Blocked suspicious request', {
userAgent,
ip: request.headers.get('x-forwarded-for') || 'unknown',
url: request.url,
method: request.method,
pattern: SUSPICIOUS_UA_PATTERNS.find(pattern => pattern.test(userAgent))?.toString()
})

// Return 403 with security headers
return new NextResponse(null, {
Comment thread
waleedlatif1 marked this conversation as resolved.
status: 403,
statusText: 'Forbidden',
headers: {
'Content-Type': 'text/plain',
'X-Content-Type-Options': 'nosniff',
'X-Frame-Options': 'DENY',
'Content-Security-Policy': "default-src 'none'",
'Cache-Control': 'no-store, no-cache, must-revalidate, proxy-revalidate',
'Pragma': 'no-cache',
'Expires': '0'
}
})
}

const response = NextResponse.next()

response.headers.set('Vary', 'User-Agent')

return response
}

// Update matcher to include admin routes
export const config = {
matcher: [
'/w', // Match exactly /w
'/w/:path*', // Match protected routes
'/login',
'/signup',
'/signup'
],
}
111 changes: 110 additions & 1 deletion sim/next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ const isStandaloneBuild = process.env.USE_LOCAL_STORAGE === 'true'

const nextConfig: NextConfig = {
devIndicators: false,
experimental: {
sri: {
Comment thread
waleedlatif1 marked this conversation as resolved.
algorithm: 'sha256'
}
},
images: {
domains: [
'avatars.githubusercontent.com',
Expand Down Expand Up @@ -35,7 +40,7 @@ const nextConfig: NextConfig = {
async headers() {
return [
{
// API routes CORS headers
// API routes CORS headers - keep no-cache for dynamic API endpoints
source: '/api/:path*',
headers: [
{ key: 'Access-Control-Allow-Credentials', value: 'true' },
Expand All @@ -52,6 +57,110 @@ const nextConfig: NextConfig = {
value:
'X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version',
},
{
key: 'Cache-Control',
Comment thread
waleedlatif1 marked this conversation as resolved.
value: 'no-store, no-cache, must-revalidate, proxy-revalidate',
},
{
key: 'Pragma',
value: 'no-cache',
},
{
key: 'Expires',
value: '0',
},
{
key: 'Surrogate-Control',
value: 'no-store',
},
],
},
{
// Static assets - long caching for better performance
// This targets common static file extensions
source: '/:path*.(js|css|svg|png|jpg|jpeg|gif|webp|avif|ico|woff|woff2|ttf|eot)',
headers: [
{
key: 'Cache-Control',
value: 'public, max-age=31536000, immutable',
},
{
key: 'Vary',
value: 'User-Agent',
},
],
},
{
// HTML/dynamic content - use validation caching instead of no-cache
source: '/:path*',
has: [
{
type: 'header',
key: 'Accept',
value: '(.*text/html.*)',
},
],
headers: [
{
key: 'X-Content-Type-Options',
value: 'nosniff',
},
{
key: 'X-Frame-Options',
value: 'SAMEORIGIN',
},
{
key: 'Content-Security-Policy',
value: "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self'; connect-src 'self'; frame-ancestors 'self'; form-action 'self'; base-uri 'self'; object-src 'none'",
},
{
key: 'Cache-Control',
value: 'public, max-age=0, must-revalidate',
},
{
key: 'Vary',
value: 'User-Agent',
},
],
},
{
// Apply security headers to all routes
source: '/:path*',
headers: [
{
key: 'X-Content-Type-Options',
value: 'nosniff',
},
{
key: 'X-Frame-Options',
value: 'SAMEORIGIN',
},
{
key: 'Content-Security-Policy',
value: "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self'; connect-src 'self'; frame-ancestors 'self'; form-action 'self'; base-uri 'self'; object-src 'none'",
},
],
},
{
// Dynamic routes containing user data - strict no caching
source: '/w/:path*',
headers: [
{
key: 'Cache-Control',
value: 'private, no-store, no-cache, must-revalidate, proxy-revalidate',
},
{
key: 'Pragma',
value: 'no-cache',
},
{
key: 'Expires',
value: '0',
},
{
key: 'Surrogate-Control',
value: 'no-store',
},
],
},
{
Expand Down