Implementation:Langgenius Dify Security Middleware
| Knowledge Sources | |
|---|---|
| Domains | Frontend, Security, Middleware |
| Last Updated | 2026-02-12 07:00 GMT |
Overview
Next.js middleware that enforces Content-Security-Policy headers with nonce-based script protection and X-Frame-Options for clickjacking prevention, while allowing iframe embedding for designated chatbot and workflow paths.
Description
The web/proxy.ts module implements a Next.js middleware function named proxy that intercepts all non-static requests to apply security headers. It addresses two primary web security concerns: cross-site scripting (XSS) mitigation through Content Security Policy (CSP) and clickjacking prevention through X-Frame-Options.
When CSP is enabled (determined by the presence of NEXT_PUBLIC_CSP_WHITELIST in production), the middleware generates a cryptographically random nonce using crypto.randomUUID() encoded in Base64. This nonce is embedded into a comprehensive CSP header that governs default-src, connect-src, script-src, style-src, worker-src, media-src, img-src, font-src, object-src, base-uri, and form-action directives. The CSP combines the nonce, user-configured whitelist domains, and a necessary domain list that includes Sentry, Google Analytics, Google Tag Manager, GitHub API, and Amplitude. The style-src directive includes 'unsafe-inline' to accommodate dynamically injected styles. The nonce is passed to downstream rendering via the x-nonce request header.
For clickjacking protection, the wrapResponseWithXFrameOptions helper sets X-Frame-Options: DENY on all responses except those for paths that are designed to be embedded in iframes: /chat, /workflow, /completion, and /webapp-signin. This behavior can be globally disabled with the NEXT_PUBLIC_ALLOW_EMBED environment variable.
The middleware matcher excludes static assets (_next/static, _next/image, favicon.ico) from processing.
Usage
This middleware is applied automatically by Next.js to all matching requests. No explicit invocation is needed. Configure the CSP whitelist via the NEXT_PUBLIC_CSP_WHITELIST environment variable in production to enable the full CSP header. The nonce value is accessible in server components via the x-nonce request header for embedding in script tags.
Code Reference
Source Location
- Repository: Langgenius_Dify
- File: web/proxy.ts
- Lines: 1-88
Signature
import type { NextRequest } from 'next/server'
import { NextResponse } from 'next/server'
const NECESSARY_DOMAIN: string
const wrapResponseWithXFrameOptions = (response: NextResponse, pathname: string): NextResponse
export function proxy(request: NextRequest): NextResponse
export const config: {
matcher: [{ source: '/((?!_next/static|_next/image|favicon.ico).*)' }]
}
Import
// Imported by the Next.js middleware entry point (web/middleware.ts):
import { proxy } from '@/proxy'
// Used within the middleware function:
export function middleware(request: NextRequest) {
return proxy(request)
}
I/O Contract
Inputs
| Name | Type | Required | Description |
|---|---|---|---|
| request | NextRequest | Yes | The incoming HTTP request object provided by the Next.js middleware pipeline |
| request.nextUrl.pathname | string | Yes (implicit) | The URL path used to determine X-Frame-Options and CSP application |
| env.NEXT_PUBLIC_CSP_WHITELIST | string | No | Space-separated list of allowed domains for CSP directives; CSP is only active when this is set in production |
| env.NEXT_PUBLIC_ALLOW_EMBED | boolean | No | When true, disables X-Frame-Options entirely, allowing all pages to be embedded |
| env.NODE_ENV | string | Yes | Must be production for CSP to be active
|
Outputs
| Name | Type | Description |
|---|---|---|
| NextResponse | NextResponse | Modified response with security headers applied |
| Content-Security-Policy header | string | CSP header with nonce, whitelist, and scheme sources (production only, when whitelist is configured) |
| X-Frame-Options header | string | Set to DENY for non-embeddable paths; omitted for chat/workflow/completion/webapp-signin paths
|
| x-nonce request header | string | Base64-encoded nonce value forwarded to downstream server components for script tag injection |
Usage Examples
CSP Header Output (Production with Whitelist)
Content-Security-Policy:
default-src 'self' data: mediastream: blob: filesystem: 'nonce-YWJjZGVm...' example.com *.sentry.io ...;
connect-src 'self' data: mediastream: blob: filesystem: 'nonce-YWJjZGVm...' example.com *.sentry.io ...;
script-src 'self' data: mediastream: blob: filesystem: 'nonce-YWJjZGVm...' example.com *.sentry.io ...;
style-src 'self' 'unsafe-inline' data: mediastream: blob: filesystem: example.com *.sentry.io ...;
worker-src 'self' data: mediastream: blob: filesystem: 'nonce-YWJjZGVm...' example.com *.sentry.io ...;
media-src 'self' data: mediastream: blob: filesystem: 'nonce-YWJjZGVm...' example.com *.sentry.io ...;
img-src * data: blob:;
font-src 'self';
object-src 'none';
base-uri 'self';
form-action 'self';
upgrade-insecure-requests;
X-Frame-Options Behavior by Path
GET /apps -> X-Frame-Options: DENY
GET /datasets -> X-Frame-Options: DENY
GET /chat/abc123 -> (no X-Frame-Options header, embeddable)
GET /workflow/def -> (no X-Frame-Options header, embeddable)
GET /completion/ghi -> (no X-Frame-Options header, embeddable)
GET /webapp-signin -> (no X-Frame-Options header, embeddable)
Using the Nonce in a Server Component
import { headers } from 'next/headers'
export default async function Page() {
const nonce = (await headers()).get('x-nonce') ?? undefined
return (
<script nonce={nonce} src="/analytics.js" />
)
}