* Handles security filtering for suspicious user agents
(request: NextRequest)
| 190 | * Handles security filtering for suspicious user agents |
| 191 | */ |
| 192 | function handleSecurityFiltering(request: NextRequest): NextResponse | null { |
| 193 | const userAgent = request.headers.get('user-agent') || '' |
| 194 | const { pathname } = request.nextUrl |
| 195 | const isWebhookEndpoint = pathname.startsWith('/api/webhooks/trigger/') |
| 196 | const isMcpEndpoint = pathname.startsWith('/api/mcp/') |
| 197 | const isMcpOauthDiscoveryEndpoint = |
| 198 | pathname.startsWith('/.well-known/oauth-authorization-server') || |
| 199 | pathname.startsWith('/.well-known/oauth-protected-resource') |
| 200 | const isSuspicious = SUSPICIOUS_UA_PATTERNS.some((pattern) => pattern.test(userAgent)) |
| 201 | |
| 202 | // Block suspicious requests, but exempt machine-to-machine endpoints that may |
| 203 | // legitimately omit User-Agent headers (webhooks and MCP protocol discovery/calls). |
| 204 | if (isSuspicious && !isWebhookEndpoint && !isMcpEndpoint && !isMcpOauthDiscoveryEndpoint) { |
| 205 | logger.warn('Blocked suspicious request', { |
| 206 | userAgent, |
| 207 | ip: getClientIp(request), |
| 208 | url: request.url, |
| 209 | method: request.method, |
| 210 | pattern: SUSPICIOUS_UA_PATTERNS.find((pattern) => pattern.test(userAgent))?.toString(), |
| 211 | }) |
| 212 | |
| 213 | return new NextResponse(null, { |
| 214 | status: 403, |
| 215 | statusText: 'Forbidden', |
| 216 | headers: { |
| 217 | 'Content-Type': 'text/plain', |
| 218 | 'X-Content-Type-Options': 'nosniff', |
| 219 | 'X-Frame-Options': 'DENY', |
| 220 | 'Content-Security-Policy': "default-src 'none'", |
| 221 | 'Cache-Control': 'no-store, no-cache, must-revalidate, proxy-revalidate', |
| 222 | Pragma: 'no-cache', |
| 223 | Expires: '0', |
| 224 | }, |
| 225 | }) |
| 226 | } |
| 227 | |
| 228 | return null |
| 229 | } |
| 230 | |
| 231 | export async function proxy(request: NextRequest) { |
| 232 | const url = request.nextUrl |