@cloudwerk/security 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +19 -1
- package/dist/index.js +14 -6
- package/dist/index.js.map +1 -1
- package/dist/middleware.d.ts +1 -1
- package/dist/middleware.js +14 -6
- package/dist/middleware.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -368,6 +368,24 @@ declare function requestedWithMiddleware(options?: RequestedWithOptions): Middle
|
|
|
368
368
|
* origin validation, and X-Requested-With validation.
|
|
369
369
|
*/
|
|
370
370
|
|
|
371
|
+
/**
|
|
372
|
+
* Compose multiple middleware functions into one.
|
|
373
|
+
*
|
|
374
|
+
* Executes middleware in order, passing the response through each.
|
|
375
|
+
*
|
|
376
|
+
* @example
|
|
377
|
+
* ```typescript
|
|
378
|
+
* import { composeMiddleware } from '@cloudwerk/security'
|
|
379
|
+
* import { securityMiddleware } from '@cloudwerk/security/middleware'
|
|
380
|
+
* import { authMiddleware } from '@cloudwerk/auth/middleware'
|
|
381
|
+
*
|
|
382
|
+
* export const middleware = composeMiddleware([
|
|
383
|
+
* securityMiddleware(),
|
|
384
|
+
* authMiddleware(),
|
|
385
|
+
* ])
|
|
386
|
+
* ```
|
|
387
|
+
*/
|
|
388
|
+
declare function composeMiddleware(middlewares: Middleware[]): Middleware;
|
|
371
389
|
/**
|
|
372
390
|
* Create a combined security middleware.
|
|
373
391
|
*
|
|
@@ -421,4 +439,4 @@ declare function requestedWithMiddleware(options?: RequestedWithOptions): Middle
|
|
|
421
439
|
*/
|
|
422
440
|
declare function securityMiddleware(options?: SecurityMiddlewareOptions): Middleware;
|
|
423
441
|
|
|
424
|
-
export { CSPDirectives, CSPOptions, CSRFMiddlewareOptions, DEFAULT_CSRF_COOKIE_NAME, DEFAULT_CSRF_FORM_FIELD_NAME, DEFAULT_CSRF_HEADER_NAME, OriginValidationOptions, RequestedWithOptions, SecurityHeadersOptions, SecurityMiddlewareOptions, SetCsrfCookieOptions, cspMiddleware, csrfMiddleware, generateCSPHeader, generateCsrfToken, generateNonce, getCsrfTokenFromCookie, getCsrfTokenFromFormBody, getCsrfTokenFromHeader, originValidationMiddleware, requestedWithMiddleware, rotateCsrfToken, securityHeadersMiddleware, securityMiddleware, setCsrfCookie, verifyCsrfToken };
|
|
442
|
+
export { CSPDirectives, CSPOptions, CSRFMiddlewareOptions, DEFAULT_CSRF_COOKIE_NAME, DEFAULT_CSRF_FORM_FIELD_NAME, DEFAULT_CSRF_HEADER_NAME, OriginValidationOptions, RequestedWithOptions, SecurityHeadersOptions, SecurityMiddlewareOptions, SetCsrfCookieOptions, composeMiddleware, cspMiddleware, csrfMiddleware, generateCSPHeader, generateCsrfToken, generateNonce, getCsrfTokenFromCookie, getCsrfTokenFromFormBody, getCsrfTokenFromHeader, originValidationMiddleware, requestedWithMiddleware, rotateCsrfToken, securityHeadersMiddleware, securityMiddleware, setCsrfCookie, verifyCsrfToken };
|
package/dist/index.js
CHANGED
|
@@ -133,15 +133,21 @@ function csrfMiddleware(options = {}) {
|
|
|
133
133
|
excludePaths = []
|
|
134
134
|
} = options;
|
|
135
135
|
return async (request, next) => {
|
|
136
|
-
|
|
137
|
-
|
|
136
|
+
const existingToken = getCsrfTokenFromCookie(request, cookieName);
|
|
137
|
+
const isMutationMethod = methods.includes(request.method);
|
|
138
|
+
if (!isMutationMethod) {
|
|
139
|
+
const response = await next();
|
|
140
|
+
if (!existingToken) {
|
|
141
|
+
const newToken = generateCsrfToken();
|
|
142
|
+
return setCsrfCookie(response, newToken, { cookieName });
|
|
143
|
+
}
|
|
144
|
+
return response;
|
|
138
145
|
}
|
|
139
146
|
const url = new URL(request.url);
|
|
140
147
|
if (excludePaths.some((path) => url.pathname.startsWith(path))) {
|
|
141
148
|
return next();
|
|
142
149
|
}
|
|
143
|
-
|
|
144
|
-
if (!cookieToken) {
|
|
150
|
+
if (!existingToken) {
|
|
145
151
|
return Response.json(
|
|
146
152
|
{ error: "Missing CSRF token cookie" },
|
|
147
153
|
{ status: 403 }
|
|
@@ -157,7 +163,7 @@ function csrfMiddleware(options = {}) {
|
|
|
157
163
|
{ status: 403 }
|
|
158
164
|
);
|
|
159
165
|
}
|
|
160
|
-
if (!verifyCsrfToken(
|
|
166
|
+
if (!verifyCsrfToken(existingToken, requestToken)) {
|
|
161
167
|
return Response.json(
|
|
162
168
|
{ error: "Invalid CSRF token" },
|
|
163
169
|
{ status: 403 }
|
|
@@ -286,7 +292,8 @@ function cspMiddleware(options = {}) {
|
|
|
286
292
|
directives = {},
|
|
287
293
|
reportOnly = false,
|
|
288
294
|
useNonce = false,
|
|
289
|
-
|
|
295
|
+
// TODO: Use context to store nonce instead of custom header
|
|
296
|
+
nonceContextKey: _nonceContextKey = "cspNonce"
|
|
290
297
|
} = options;
|
|
291
298
|
const headerName = reportOnly ? "Content-Security-Policy-Report-Only" : "Content-Security-Policy";
|
|
292
299
|
return async (request, next) => {
|
|
@@ -473,6 +480,7 @@ export {
|
|
|
473
480
|
DEFAULT_CSRF_COOKIE_NAME,
|
|
474
481
|
DEFAULT_CSRF_FORM_FIELD_NAME,
|
|
475
482
|
DEFAULT_CSRF_HEADER_NAME,
|
|
483
|
+
composeMiddleware,
|
|
476
484
|
cspMiddleware,
|
|
477
485
|
csrfMiddleware,
|
|
478
486
|
generateCSPHeader,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/utils/cookie.ts","../src/utils/timing-safe.ts","../src/csrf/token.ts","../src/csrf/middleware.ts","../src/headers/security-headers.ts","../src/headers/csp.ts","../src/origin/middleware.ts","../src/request/middleware.ts","../src/combined/security-middleware.ts"],"sourcesContent":["/**\n * @cloudwerk/security - Cookie Utilities\n *\n * Utilities for parsing and serializing cookies.\n */\n\nimport type { CookieAttributes } from '../types.js'\n\n/**\n * Parse a cookie header string into key-value pairs.\n *\n * @param cookieHeader - The Cookie header value\n * @returns Record of cookie names to values\n *\n * @example\n * ```typescript\n * const cookies = parseCookies('session=abc123; theme=dark')\n * // { session: 'abc123', theme: 'dark' }\n * ```\n */\nexport function parseCookies(cookieHeader: string): Record<string, string> {\n const cookies: Record<string, string> = {}\n\n if (!cookieHeader) {\n return cookies\n }\n\n const pairs = cookieHeader.split(';')\n for (const pair of pairs) {\n const [name, ...valueParts] = pair.split('=')\n const trimmedName = name?.trim()\n if (trimmedName) {\n // Rejoin in case value contains '='\n const value = valueParts.join('=').trim()\n // Handle quoted values\n cookies[trimmedName] = value.startsWith('\"') && value.endsWith('\"')\n ? value.slice(1, -1)\n : value\n }\n }\n\n return cookies\n}\n\n/**\n * Serialize a cookie with name, value, and attributes into a Set-Cookie header value.\n *\n * @param name - Cookie name\n * @param value - Cookie value\n * @param attributes - Cookie attributes\n * @returns Set-Cookie header value\n *\n * @example\n * ```typescript\n * const setCookie = serializeCookie('session', 'abc123', {\n * httpOnly: true,\n * secure: true,\n * sameSite: 'lax',\n * maxAge: 86400,\n * path: '/',\n * })\n * // \"session=abc123; HttpOnly; Secure; SameSite=Lax; Max-Age=86400; Path=/\"\n * ```\n */\nexport function serializeCookie(\n name: string,\n value: string,\n attributes: CookieAttributes = {}\n): string {\n const parts: string[] = [`${encodeURIComponent(name)}=${encodeURIComponent(value)}`]\n\n if (attributes.domain) {\n parts.push(`Domain=${attributes.domain}`)\n }\n\n if (attributes.path) {\n parts.push(`Path=${attributes.path}`)\n }\n\n if (attributes.expires) {\n parts.push(`Expires=${attributes.expires.toUTCString()}`)\n }\n\n if (attributes.maxAge !== undefined) {\n parts.push(`Max-Age=${attributes.maxAge}`)\n }\n\n if (attributes.httpOnly) {\n parts.push('HttpOnly')\n }\n\n if (attributes.secure) {\n parts.push('Secure')\n }\n\n if (attributes.sameSite) {\n const sameSiteValue = attributes.sameSite.charAt(0).toUpperCase() + attributes.sameSite.slice(1)\n parts.push(`SameSite=${sameSiteValue}`)\n }\n\n return parts.join('; ')\n}\n","/**\n * @cloudwerk/security - Timing-Safe Utilities\n *\n * Utilities for preventing timing attacks.\n */\n\n/**\n * Perform a timing-safe string comparison.\n *\n * This prevents timing attacks by comparing all characters regardless\n * of where differences occur.\n *\n * @param a - First string\n * @param b - Second string\n * @returns True if strings are equal\n */\nexport function timingSafeEqual(a: string, b: string): boolean {\n if (a.length !== b.length) {\n return false\n }\n\n // Use XOR to compare without early exit\n let result = 0\n for (let i = 0; i < a.length; i++) {\n result |= a.charCodeAt(i) ^ b.charCodeAt(i)\n }\n\n return result === 0\n}\n","/**\n * @cloudwerk/security - CSRF Token Utilities\n *\n * Functions for generating, setting, and verifying CSRF tokens.\n */\n\nimport type { SetCsrfCookieOptions } from '../types.js'\nimport { serializeCookie, parseCookies } from '../utils/cookie.js'\nimport { timingSafeEqual } from '../utils/timing-safe.js'\n\n// ============================================================================\n// Constants\n// ============================================================================\n\n/** Default CSRF cookie name */\nexport const DEFAULT_CSRF_COOKIE_NAME = 'cloudwerk.csrf-token'\n\n/** Default CSRF header name */\nexport const DEFAULT_CSRF_HEADER_NAME = 'X-CSRF-Token'\n\n/** Default CSRF form field name */\nexport const DEFAULT_CSRF_FORM_FIELD_NAME = 'csrf_token'\n\n/** CSRF token length in bytes (32 bytes = 256 bits) */\nconst CSRF_TOKEN_BYTES = 32\n\n/** Default max age for CSRF cookie (24 hours) */\nconst DEFAULT_CSRF_MAX_AGE = 24 * 60 * 60\n\n// ============================================================================\n// Token Generation\n// ============================================================================\n\n/**\n * Generate a cryptographically secure CSRF token.\n *\n * Uses Web Crypto API for secure random number generation.\n *\n * @returns A URL-safe base64-encoded random token\n *\n * @example\n * ```typescript\n * import { generateCsrfToken } from '@cloudwerk/security'\n *\n * const token = generateCsrfToken()\n * // 'Yx8nK2pQ...' (43 characters)\n * ```\n */\nexport function generateCsrfToken(): string {\n const bytes = new Uint8Array(CSRF_TOKEN_BYTES)\n crypto.getRandomValues(bytes)\n\n // Convert to URL-safe base64\n const base64 = btoa(String.fromCharCode(...bytes))\n return base64.replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '')\n}\n\n// ============================================================================\n// Cookie Helpers\n// ============================================================================\n\n/**\n * Set a CSRF cookie on a response.\n *\n * Creates a new response with the CSRF cookie set. The cookie is accessible\n * to JavaScript (not httpOnly) so that SPA frameworks can read it and include\n * it in request headers.\n *\n * @param response - The response to add the cookie to\n * @param token - The CSRF token to set (generate with generateCsrfToken())\n * @param options - Cookie configuration options\n * @returns A new response with the Set-Cookie header added\n *\n * @example\n * ```typescript\n * import { generateCsrfToken, setCsrfCookie } from '@cloudwerk/security'\n *\n * export function GET(request: Request) {\n * const token = generateCsrfToken()\n * const response = new Response(JSON.stringify({ csrfToken: token }))\n * return setCsrfCookie(response, token)\n * }\n * ```\n */\nexport function setCsrfCookie(\n response: Response,\n token: string,\n options: SetCsrfCookieOptions = {}\n): Response {\n const {\n cookieName = DEFAULT_CSRF_COOKIE_NAME,\n path = '/',\n httpOnly = false, // Must be false to allow JS access\n secure = true,\n sameSite = 'lax',\n maxAge = DEFAULT_CSRF_MAX_AGE,\n } = options\n\n const cookieValue = serializeCookie(cookieName, token, {\n path,\n httpOnly,\n secure,\n sameSite,\n maxAge,\n })\n\n // Clone response and append cookie\n const headers = new Headers(response.headers)\n headers.append('Set-Cookie', cookieValue)\n\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers,\n })\n}\n\n/**\n * Get the CSRF token from a request's cookie.\n *\n * @param request - The request to extract the token from\n * @param cookieName - The cookie name to look for\n * @returns The CSRF token or null if not found\n */\nexport function getCsrfTokenFromCookie(\n request: Request,\n cookieName: string = DEFAULT_CSRF_COOKIE_NAME\n): string | null {\n const cookieHeader = request.headers.get('Cookie')\n if (!cookieHeader) return null\n\n const cookies = parseCookies(cookieHeader)\n return cookies[cookieName] ?? null\n}\n\n/**\n * Get the CSRF token from a request's header.\n *\n * @param request - The request to extract the token from\n * @param headerName - The header name to look for\n * @returns The CSRF token or null if not found\n */\nexport function getCsrfTokenFromHeader(\n request: Request,\n headerName: string = DEFAULT_CSRF_HEADER_NAME\n): string | null {\n return request.headers.get(headerName)\n}\n\n/**\n * Get the CSRF token from a request's form body.\n *\n * @param request - The request to extract the token from (will be cloned)\n * @param fieldName - The form field name to look for\n * @returns The CSRF token or null if not found\n */\nexport async function getCsrfTokenFromFormBody(\n request: Request,\n fieldName: string = DEFAULT_CSRF_FORM_FIELD_NAME\n): Promise<string | null> {\n const contentType = request.headers.get('Content-Type') || ''\n\n // Only check form data for form submissions\n if (!contentType.includes('application/x-www-form-urlencoded') &&\n !contentType.includes('multipart/form-data')) {\n return null\n }\n\n try {\n // Clone request to avoid consuming the body\n const clonedRequest = request.clone()\n const formData = await clonedRequest.formData()\n const token = formData.get(fieldName)\n return typeof token === 'string' ? token : null\n } catch {\n return null\n }\n}\n\n// ============================================================================\n// Token Verification\n// ============================================================================\n\n/**\n * Verify a CSRF token against the token in the cookie.\n *\n * Uses timing-safe comparison to prevent timing attacks.\n *\n * @param cookieToken - The token from the cookie\n * @param requestToken - The token from the request (header or form body)\n * @returns True if tokens match\n *\n * @example\n * ```typescript\n * import { verifyCsrfToken, getCsrfTokenFromCookie, getCsrfTokenFromHeader } from '@cloudwerk/security'\n *\n * const cookieToken = getCsrfTokenFromCookie(request)\n * const headerToken = getCsrfTokenFromHeader(request)\n *\n * if (cookieToken && headerToken && verifyCsrfToken(cookieToken, headerToken)) {\n * // Token is valid\n * }\n * ```\n */\nexport function verifyCsrfToken(cookieToken: string, requestToken: string): boolean {\n return timingSafeEqual(cookieToken, requestToken)\n}\n\n// ============================================================================\n// Token Rotation\n// ============================================================================\n\n/**\n * Rotate the CSRF token on a response.\n *\n * Generates a new CSRF token and sets it as a cookie. This should be called\n * after successful authentication to bind the CSRF token to the new session\n * and prevent session fixation attacks.\n *\n * @param response - The response to add the new CSRF cookie to\n * @param options - Cookie configuration options\n * @returns A new response with the rotated CSRF token cookie\n *\n * @example\n * ```typescript\n * import { rotateCsrfToken } from '@cloudwerk/security'\n *\n * // After successful login\n * export async function handleLogin(request: Request) {\n * const user = await validateCredentials(request)\n * const session = await createSession(user)\n *\n * let response = createAuthResponse(user, session)\n * response = rotateCsrfToken(response)\n * return response\n * }\n * ```\n */\nexport function rotateCsrfToken(\n response: Response,\n options: SetCsrfCookieOptions = {}\n): Response {\n const newToken = generateCsrfToken()\n return setCsrfCookie(response, newToken, options)\n}\n","/**\n * @cloudwerk/security - CSRF Middleware\n *\n * Provides CSRF (Cross-Site Request Forgery) protection for mutation requests.\n * Uses the double-submit cookie pattern for stateless CSRF protection.\n */\n\nimport type { Middleware } from '@cloudwerk/core'\nimport type { CSRFMiddlewareOptions } from '../types.js'\nimport {\n DEFAULT_CSRF_COOKIE_NAME,\n DEFAULT_CSRF_HEADER_NAME,\n DEFAULT_CSRF_FORM_FIELD_NAME,\n getCsrfTokenFromCookie,\n getCsrfTokenFromHeader,\n getCsrfTokenFromFormBody,\n verifyCsrfToken,\n} from './token.js'\n\n/** Default methods requiring CSRF validation */\nconst DEFAULT_CSRF_METHODS = ['POST', 'PUT', 'PATCH', 'DELETE']\n\n/**\n * Create CSRF protection middleware.\n *\n * Validates that mutation requests (POST, PUT, PATCH, DELETE) include a valid\n * CSRF token that matches the token in the cookie. Uses the double-submit\n * cookie pattern for stateless CSRF protection.\n *\n * The token can be provided via:\n * 1. Request header (X-CSRF-Token by default) - for AJAX requests\n * 2. Form field (csrf_token by default) - for traditional form submissions\n *\n * @param options - Middleware configuration options\n * @returns Middleware function\n *\n * @example\n * ```typescript\n * // In middleware.ts\n * import { csrfMiddleware } from '@cloudwerk/security/middleware'\n *\n * export const middleware = csrfMiddleware()\n * ```\n *\n * @example\n * ```typescript\n * // Exclude webhook paths\n * export const middleware = csrfMiddleware({\n * excludePaths: ['/api/webhooks/stripe', '/api/webhooks/github'],\n * })\n * ```\n */\nexport function csrfMiddleware(options: CSRFMiddlewareOptions = {}): Middleware {\n const {\n cookieName = DEFAULT_CSRF_COOKIE_NAME,\n headerName = DEFAULT_CSRF_HEADER_NAME,\n formFieldName = DEFAULT_CSRF_FORM_FIELD_NAME,\n methods = DEFAULT_CSRF_METHODS,\n excludePaths = [],\n } = options\n\n return async (request, next) => {\n // Skip if method doesn't require CSRF validation\n if (!methods.includes(request.method)) {\n return next()\n }\n\n // Skip excluded paths\n const url = new URL(request.url)\n if (excludePaths.some((path) => url.pathname.startsWith(path))) {\n return next()\n }\n\n // Get token from cookie\n const cookieToken = getCsrfTokenFromCookie(request, cookieName)\n if (!cookieToken) {\n return Response.json(\n { error: 'Missing CSRF token cookie' },\n { status: 403 }\n )\n }\n\n // Get token from header or form body\n let requestToken = getCsrfTokenFromHeader(request, headerName)\n\n // If not in header, check form body\n if (!requestToken) {\n requestToken = await getCsrfTokenFromFormBody(request, formFieldName)\n }\n\n if (!requestToken) {\n return Response.json(\n { error: 'Missing CSRF token in request' },\n { status: 403 }\n )\n }\n\n // Compare tokens using timing-safe comparison\n if (!verifyCsrfToken(cookieToken, requestToken)) {\n return Response.json(\n { error: 'Invalid CSRF token' },\n { status: 403 }\n )\n }\n\n return next()\n }\n}\n","/**\n * @cloudwerk/security - Security Headers Middleware\n *\n * Sets standard security headers on responses.\n */\n\nimport type { Middleware } from '@cloudwerk/core'\nimport type { SecurityHeadersOptions } from '../types.js'\n\n/**\n * Default security headers configuration.\n */\nconst DEFAULT_OPTIONS: Required<SecurityHeadersOptions> = {\n contentTypeOptions: 'nosniff',\n frameOptions: 'DENY',\n referrerPolicy: 'strict-origin-when-cross-origin',\n xssProtection: '0', // Disabled as modern browsers handle XSS\n permittedCrossDomainPolicies: 'none',\n dnsPrefetchControl: 'off',\n crossOriginOpenerPolicy: false,\n crossOriginEmbedderPolicy: false,\n crossOriginResourcePolicy: false,\n}\n\n/**\n * Create security headers middleware.\n *\n * Sets standard security headers on all responses:\n * - X-Content-Type-Options: nosniff\n * - X-Frame-Options: DENY\n * - Referrer-Policy: strict-origin-when-cross-origin\n * - X-XSS-Protection: 0 (disabled, modern browsers handle XSS)\n * - X-Permitted-Cross-Domain-Policies: none\n * - X-DNS-Prefetch-Control: off\n *\n * @param options - Header configuration options\n * @returns Middleware function\n *\n * @example\n * ```typescript\n * import { securityHeadersMiddleware } from '@cloudwerk/security/middleware'\n *\n * // Use defaults\n * export const middleware = securityHeadersMiddleware()\n *\n * // Custom configuration\n * export const middleware = securityHeadersMiddleware({\n * frameOptions: 'SAMEORIGIN',\n * crossOriginOpenerPolicy: 'same-origin',\n * })\n * ```\n */\nexport function securityHeadersMiddleware(\n options: SecurityHeadersOptions = {}\n): Middleware {\n const config = { ...DEFAULT_OPTIONS, ...options }\n\n return async (request, next) => {\n const response = await next()\n\n // Clone headers to modify\n const headers = new Headers(response.headers)\n\n // X-Content-Type-Options\n if (config.contentTypeOptions !== false) {\n headers.set('X-Content-Type-Options', config.contentTypeOptions)\n }\n\n // X-Frame-Options\n if (config.frameOptions !== false) {\n headers.set('X-Frame-Options', config.frameOptions)\n }\n\n // Referrer-Policy\n if (config.referrerPolicy !== false) {\n headers.set('Referrer-Policy', config.referrerPolicy)\n }\n\n // X-XSS-Protection\n if (config.xssProtection !== false) {\n headers.set('X-XSS-Protection', config.xssProtection)\n }\n\n // X-Permitted-Cross-Domain-Policies\n if (config.permittedCrossDomainPolicies !== false) {\n headers.set('X-Permitted-Cross-Domain-Policies', config.permittedCrossDomainPolicies)\n }\n\n // X-DNS-Prefetch-Control\n if (config.dnsPrefetchControl !== false) {\n headers.set('X-DNS-Prefetch-Control', config.dnsPrefetchControl)\n }\n\n // Cross-Origin-Opener-Policy\n if (config.crossOriginOpenerPolicy !== false) {\n headers.set('Cross-Origin-Opener-Policy', config.crossOriginOpenerPolicy)\n }\n\n // Cross-Origin-Embedder-Policy\n if (config.crossOriginEmbedderPolicy !== false) {\n headers.set('Cross-Origin-Embedder-Policy', config.crossOriginEmbedderPolicy)\n }\n\n // Cross-Origin-Resource-Policy\n if (config.crossOriginResourcePolicy !== false) {\n headers.set('Cross-Origin-Resource-Policy', config.crossOriginResourcePolicy)\n }\n\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers,\n })\n }\n}\n","/**\n * @cloudwerk/security - Content Security Policy Middleware\n *\n * Generates and sets Content-Security-Policy headers.\n */\n\nimport type { Middleware } from '@cloudwerk/core'\nimport type { CSPOptions, CSPDirectives, CSPDirectiveValue } from '../types.js'\n\n/**\n * Map of directive names to their CSP header names.\n */\nconst DIRECTIVE_MAP: Record<keyof CSPDirectives, string> = {\n defaultSrc: 'default-src',\n scriptSrc: 'script-src',\n styleSrc: 'style-src',\n imgSrc: 'img-src',\n fontSrc: 'font-src',\n connectSrc: 'connect-src',\n mediaSrc: 'media-src',\n objectSrc: 'object-src',\n prefetchSrc: 'prefetch-src',\n frameSrc: 'frame-src',\n workerSrc: 'worker-src',\n childSrc: 'child-src',\n frameAncestors: 'frame-ancestors',\n formAction: 'form-action',\n pluginTypes: 'plugin-types',\n baseUri: 'base-uri',\n sandbox: 'sandbox',\n requireSriFor: 'require-sri-for',\n reportUri: 'report-uri',\n reportTo: 'report-to',\n requireTrustedTypesFor: 'require-trusted-types-for',\n trustedTypes: 'trusted-types',\n upgradeInsecureRequests: 'upgrade-insecure-requests',\n blockAllMixedContent: 'block-all-mixed-content',\n manifestSrc: 'manifest-src',\n scriptSrcElem: 'script-src-elem',\n scriptSrcAttr: 'script-src-attr',\n styleSrcElem: 'style-src-elem',\n styleSrcAttr: 'style-src-attr',\n}\n\n/**\n * Generate a cryptographically secure nonce for CSP.\n *\n * @returns A base64-encoded random nonce\n */\nexport function generateNonce(): string {\n const bytes = new Uint8Array(16)\n crypto.getRandomValues(bytes)\n return btoa(String.fromCharCode(...bytes))\n}\n\n/**\n * Format a directive value for CSP header.\n */\nfunction formatDirectiveValue(value: CSPDirectiveValue): string {\n if (Array.isArray(value)) {\n return value.join(' ')\n }\n return value\n}\n\n/**\n * Generate a Content-Security-Policy header string from directives.\n *\n * @param directives - CSP directives object\n * @param nonce - Optional nonce to include in script-src and style-src\n * @returns CSP header string\n *\n * @example\n * ```typescript\n * const csp = generateCSPHeader({\n * defaultSrc: [\"'self'\"],\n * scriptSrc: [\"'self'\", 'https://cdn.example.com'],\n * styleSrc: [\"'self'\", \"'unsafe-inline'\"],\n * })\n * // \"default-src 'self'; script-src 'self' https://cdn.example.com; style-src 'self' 'unsafe-inline'\"\n * ```\n */\nexport function generateCSPHeader(\n directives: CSPDirectives,\n nonce?: string\n): string {\n const parts: string[] = []\n\n for (const [key, value] of Object.entries(directives)) {\n if (value === undefined || value === false) continue\n\n const directiveName = DIRECTIVE_MAP[key as keyof CSPDirectives]\n if (!directiveName) continue\n\n // Boolean directives (no value needed)\n if (value === true) {\n parts.push(directiveName)\n continue\n }\n\n // Add nonce to script-src and style-src if provided\n let formattedValue = formatDirectiveValue(value as CSPDirectiveValue)\n if (nonce && (key === 'scriptSrc' || key === 'styleSrc' ||\n key === 'scriptSrcElem' || key === 'styleSrcElem')) {\n formattedValue += ` 'nonce-${nonce}'`\n }\n\n parts.push(`${directiveName} ${formattedValue}`)\n }\n\n return parts.join('; ')\n}\n\n/**\n * Create CSP middleware.\n *\n * Generates and sets Content-Security-Policy (or Content-Security-Policy-Report-Only)\n * headers on responses.\n *\n * @param options - CSP configuration options\n * @returns Middleware function\n *\n * @example\n * ```typescript\n * import { cspMiddleware } from '@cloudwerk/security/middleware'\n *\n * export const middleware = cspMiddleware({\n * directives: {\n * defaultSrc: [\"'self'\"],\n * scriptSrc: [\"'self'\", 'https://cdn.example.com'],\n * styleSrc: [\"'self'\", \"'unsafe-inline'\"],\n * imgSrc: [\"'self'\", 'data:', 'https:'],\n * },\n * })\n * ```\n *\n * @example\n * ```typescript\n * // Report-only mode for testing\n * export const middleware = cspMiddleware({\n * directives: {\n * defaultSrc: [\"'self'\"],\n * reportUri: '/api/csp-report',\n * },\n * reportOnly: true,\n * })\n * ```\n *\n * @example\n * ```typescript\n * // With nonce generation for inline scripts\n * export const middleware = cspMiddleware({\n * directives: {\n * defaultSrc: [\"'self'\"],\n * scriptSrc: [\"'self'\"],\n * },\n * useNonce: true,\n * })\n *\n * // In your page, access the nonce via context:\n * // const nonce = context.get('cspNonce')\n * // <script nonce={nonce}>...</script>\n * ```\n */\nexport function cspMiddleware(options: CSPOptions = {}): Middleware {\n const {\n directives = {},\n reportOnly = false,\n useNonce = false,\n nonceContextKey = 'cspNonce',\n } = options\n\n const headerName = reportOnly\n ? 'Content-Security-Policy-Report-Only'\n : 'Content-Security-Policy'\n\n return async (request, next) => {\n // Generate nonce if requested\n let nonce: string | undefined\n if (useNonce) {\n nonce = generateNonce()\n // Note: In a real implementation, we'd use getContext().set() to store the nonce\n // For now, we'll store it in a custom header that pages can read\n }\n\n const response = await next()\n\n // Generate CSP header\n const cspValue = generateCSPHeader(directives, nonce)\n\n if (!cspValue) {\n return response\n }\n\n // Clone headers and add CSP\n const headers = new Headers(response.headers)\n headers.set(headerName, cspValue)\n\n // If using nonce, add it as a custom header for pages to read\n if (nonce) {\n headers.set('X-CSP-Nonce', nonce)\n }\n\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers,\n })\n }\n}\n","/**\n * @cloudwerk/security - Origin Validation Middleware\n *\n * Validates Origin and Referer headers to prevent cross-origin attacks.\n */\n\nimport type { Middleware } from '@cloudwerk/core'\nimport type { OriginValidationOptions } from '../types.js'\n\n/** Default methods requiring origin validation */\nconst DEFAULT_METHODS = ['POST', 'PUT', 'PATCH', 'DELETE']\n\n/**\n * Check if an origin matches an allowed host.\n *\n * @param originHost - The host from the Origin header\n * @param allowedHost - The allowed host to check against\n * @param allowSubdomains - Whether to allow subdomains\n * @returns True if the origin matches\n */\nfunction hostMatches(\n originHost: string,\n allowedHost: string,\n allowSubdomains: boolean\n): boolean {\n if (originHost === allowedHost) {\n return true\n }\n\n if (allowSubdomains && originHost.endsWith(`.${allowedHost}`)) {\n return true\n }\n\n return false\n}\n\n/**\n * Validate an origin against allowed origins and hosts.\n *\n * @param origin - The Origin header value\n * @param options - Validation options\n * @returns True if the origin is allowed\n */\nfunction isOriginAllowed(origin: string, options: OriginValidationOptions): boolean {\n const { allowedOrigins = [], allowedHosts = [], allowSubdomains = false } = options\n\n // Check against allowed origins (exact match)\n if (allowedOrigins.includes(origin)) {\n return true\n }\n\n // Parse origin to get host\n let originHost: string\n try {\n const url = new URL(origin)\n originHost = url.host\n } catch {\n return false\n }\n\n // Check against allowed hosts\n for (const allowedHost of allowedHosts) {\n if (hostMatches(originHost, allowedHost, allowSubdomains)) {\n return true\n }\n }\n\n return false\n}\n\n/**\n * Create origin validation middleware.\n *\n * Validates that mutation requests (POST, PUT, PATCH, DELETE) come from\n * allowed origins. This helps prevent CSRF attacks by rejecting requests\n * from unknown origins.\n *\n * @param options - Validation options\n * @returns Middleware function\n *\n * @example\n * ```typescript\n * import { originValidationMiddleware } from '@cloudwerk/security/middleware'\n *\n * export const middleware = originValidationMiddleware({\n * allowedOrigins: ['https://myapp.com', 'https://admin.myapp.com'],\n * })\n * ```\n *\n * @example\n * ```typescript\n * // Allow all subdomains of a host\n * export const middleware = originValidationMiddleware({\n * allowedHosts: ['myapp.com'],\n * allowSubdomains: true,\n * })\n * ```\n */\nexport function originValidationMiddleware(\n options: OriginValidationOptions = {}\n): Middleware {\n const {\n allowedOrigins = [],\n allowedHosts = [],\n methods = DEFAULT_METHODS,\n excludePaths = [],\n rejectMissingOrigin = true,\n } = options\n\n // If no origins or hosts are configured, skip validation\n const hasConfig = allowedOrigins.length > 0 || allowedHosts.length > 0\n\n return async (request, next) => {\n // Skip if no configuration\n if (!hasConfig) {\n return next()\n }\n\n // Skip if method doesn't require validation\n if (!methods.includes(request.method)) {\n return next()\n }\n\n // Skip excluded paths\n const url = new URL(request.url)\n if (excludePaths.some((path) => url.pathname.startsWith(path))) {\n return next()\n }\n\n // Get Origin header (preferred) or fall back to Referer\n let origin = request.headers.get('Origin')\n\n if (!origin) {\n const referer = request.headers.get('Referer')\n if (referer) {\n try {\n const refererUrl = new URL(referer)\n origin = refererUrl.origin\n } catch {\n // Invalid referer URL\n }\n }\n }\n\n // Check if origin is missing\n if (!origin) {\n if (rejectMissingOrigin) {\n return Response.json(\n { error: 'Missing Origin header' },\n { status: 403 }\n )\n }\n // Allow if configured to not reject missing origins\n return next()\n }\n\n // Validate origin\n if (!isOriginAllowed(origin, options)) {\n return Response.json(\n { error: 'Origin not allowed' },\n { status: 403 }\n )\n }\n\n return next()\n }\n}\n","/**\n * @cloudwerk/security - X-Requested-With Validation Middleware\n *\n * Requires X-Requested-With header to force CORS preflight for cross-origin requests.\n */\n\nimport type { Middleware } from '@cloudwerk/core'\nimport type { RequestedWithOptions } from '../types.js'\n\n/** Default methods requiring X-Requested-With validation */\nconst DEFAULT_METHODS = ['POST', 'PUT', 'PATCH', 'DELETE']\n\n/** Default required header value */\nconst DEFAULT_REQUIRED_VALUE = 'XMLHttpRequest'\n\n/**\n * Create X-Requested-With validation middleware.\n *\n * Requires mutation requests to include an `X-Requested-With` header.\n * This forces CORS preflight for cross-origin requests, providing an\n * additional layer of protection against CSRF attacks.\n *\n * The X-Requested-With header is a custom header that cannot be set\n * cross-origin without a CORS preflight. By requiring it, we ensure\n * that cross-origin requests must pass CORS checks.\n *\n * @param options - Validation options\n * @returns Middleware function\n *\n * @example\n * ```typescript\n * import { requestedWithMiddleware } from '@cloudwerk/security/middleware'\n *\n * // Default: requires X-Requested-With: XMLHttpRequest\n * export const middleware = requestedWithMiddleware()\n * ```\n *\n * @example\n * ```typescript\n * // Custom header value\n * export const middleware = requestedWithMiddleware({\n * requiredValue: 'fetch',\n * excludePaths: ['/api/webhooks'],\n * })\n * ```\n */\nexport function requestedWithMiddleware(\n options: RequestedWithOptions = {}\n): Middleware {\n const {\n requiredValue = DEFAULT_REQUIRED_VALUE,\n methods = DEFAULT_METHODS,\n excludePaths = [],\n } = options\n\n return async (request, next) => {\n // Skip if method doesn't require validation\n if (!methods.includes(request.method)) {\n return next()\n }\n\n // Skip excluded paths\n const url = new URL(request.url)\n if (excludePaths.some((path) => url.pathname.startsWith(path))) {\n return next()\n }\n\n // Check X-Requested-With header\n const headerValue = request.headers.get('X-Requested-With')\n\n if (!headerValue) {\n return Response.json(\n { error: 'Missing X-Requested-With header' },\n { status: 403 }\n )\n }\n\n if (headerValue !== requiredValue) {\n return Response.json(\n { error: 'Invalid X-Requested-With header' },\n { status: 403 }\n )\n }\n\n return next()\n }\n}\n","/**\n * @cloudwerk/security - Combined Security Middleware\n *\n * All-in-one middleware that combines CSRF, security headers, CSP,\n * origin validation, and X-Requested-With validation.\n */\n\nimport type { Middleware } from '@cloudwerk/core'\nimport type { SecurityMiddlewareOptions } from '../types.js'\nimport { csrfMiddleware } from '../csrf/middleware.js'\nimport { securityHeadersMiddleware } from '../headers/security-headers.js'\nimport { cspMiddleware } from '../headers/csp.js'\nimport { originValidationMiddleware } from '../origin/middleware.js'\nimport { requestedWithMiddleware } from '../request/middleware.js'\n\n/**\n * Compose multiple middleware functions into one.\n *\n * Executes middleware in order, passing the response through each.\n */\nfunction composeMiddleware(middlewares: Middleware[]): Middleware {\n return async (request, next) => {\n // Build the middleware chain in reverse order\n let handler: () => Promise<Response> = next\n\n for (let i = middlewares.length - 1; i >= 0; i--) {\n const middleware = middlewares[i]\n const prevHandler = handler\n handler = async () => middleware(request, prevHandler)\n }\n\n return handler()\n }\n}\n\n/**\n * Create a combined security middleware.\n *\n * This middleware composes multiple security protections with sensible defaults:\n * - **csrf**: Enabled by default - validates CSRF tokens on mutation requests\n * - **requestedWith**: Enabled by default - requires X-Requested-With header\n * - **headers**: Enabled by default - sets security headers (nosniff, DENY, etc.)\n * - **csp**: Disabled by default - requires app-specific configuration\n * - **origin**: Disabled by default - requires allowedOrigins configuration\n *\n * @param options - Configuration options for each protection\n * @returns Middleware function\n *\n * @example\n * ```typescript\n * import { securityMiddleware } from '@cloudwerk/security/middleware'\n *\n * // Use with all defaults\n * export const middleware = securityMiddleware()\n * ```\n *\n * @example\n * ```typescript\n * // Full configuration\n * export const middleware = securityMiddleware({\n * allowedOrigins: ['https://myapp.com'],\n * csrf: {\n * excludePaths: ['/api/webhooks/stripe'],\n * },\n * csp: {\n * directives: {\n * defaultSrc: [\"'self'\"],\n * scriptSrc: [\"'self'\", 'https://cdn.example.com'],\n * },\n * reportOnly: true,\n * },\n * headers: {\n * frameOptions: 'SAMEORIGIN',\n * },\n * })\n * ```\n *\n * @example\n * ```typescript\n * // Disable specific protections\n * export const middleware = securityMiddleware({\n * csrf: false, // Disable CSRF\n * requestedWith: false, // Disable X-Requested-With\n * })\n * ```\n */\nexport function securityMiddleware(\n options: SecurityMiddlewareOptions = {}\n): Middleware {\n const middlewares: Middleware[] = []\n\n // Add security headers middleware (enabled by default)\n if (options.headers !== false) {\n const headersOptions = typeof options.headers === 'object' ? options.headers : {}\n middlewares.push(securityHeadersMiddleware(headersOptions))\n }\n\n // Add CSP middleware (disabled by default - requires configuration)\n if (options.csp && typeof options.csp === 'object') {\n middlewares.push(cspMiddleware(options.csp))\n }\n\n // Add origin validation middleware (disabled by default unless allowedOrigins provided)\n if (options.origin !== false) {\n const originOptions = typeof options.origin === 'object'\n ? { ...options.origin }\n : { allowedOrigins: options.allowedOrigins }\n\n // Merge shorthand allowedOrigins into origin options\n if (options.allowedOrigins && !originOptions.allowedOrigins) {\n originOptions.allowedOrigins = options.allowedOrigins\n }\n\n // Only add if there are actual origins/hosts configured\n if (originOptions.allowedOrigins?.length || originOptions.allowedHosts?.length) {\n middlewares.push(originValidationMiddleware(originOptions))\n }\n }\n\n // Add X-Requested-With middleware (enabled by default)\n if (options.requestedWith !== false) {\n const requestedWithOptions = typeof options.requestedWith === 'object'\n ? options.requestedWith\n : {}\n middlewares.push(requestedWithMiddleware(requestedWithOptions))\n }\n\n // Add CSRF middleware (enabled by default)\n if (options.csrf !== false) {\n const csrfOptions = typeof options.csrf === 'object' ? options.csrf : {}\n middlewares.push(csrfMiddleware(csrfOptions))\n }\n\n // If no middleware is enabled, just pass through\n if (middlewares.length === 0) {\n return async (_request: Request, next: () => Promise<Response>) => next()\n }\n\n // Compose all middleware\n return composeMiddleware(middlewares)\n}\n"],"mappings":"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","names":["DEFAULT_METHODS"]}
|
|
1
|
+
{"version":3,"sources":["../src/utils/cookie.ts","../src/utils/timing-safe.ts","../src/csrf/token.ts","../src/csrf/middleware.ts","../src/headers/security-headers.ts","../src/headers/csp.ts","../src/origin/middleware.ts","../src/request/middleware.ts","../src/combined/security-middleware.ts"],"sourcesContent":["/**\n * @cloudwerk/security - Cookie Utilities\n *\n * Utilities for parsing and serializing cookies.\n */\n\nimport type { CookieAttributes } from '../types.js'\n\n/**\n * Parse a cookie header string into key-value pairs.\n *\n * @param cookieHeader - The Cookie header value\n * @returns Record of cookie names to values\n *\n * @example\n * ```typescript\n * const cookies = parseCookies('session=abc123; theme=dark')\n * // { session: 'abc123', theme: 'dark' }\n * ```\n */\nexport function parseCookies(cookieHeader: string): Record<string, string> {\n const cookies: Record<string, string> = {}\n\n if (!cookieHeader) {\n return cookies\n }\n\n const pairs = cookieHeader.split(';')\n for (const pair of pairs) {\n const [name, ...valueParts] = pair.split('=')\n const trimmedName = name?.trim()\n if (trimmedName) {\n // Rejoin in case value contains '='\n const value = valueParts.join('=').trim()\n // Handle quoted values\n cookies[trimmedName] = value.startsWith('\"') && value.endsWith('\"')\n ? value.slice(1, -1)\n : value\n }\n }\n\n return cookies\n}\n\n/**\n * Serialize a cookie with name, value, and attributes into a Set-Cookie header value.\n *\n * @param name - Cookie name\n * @param value - Cookie value\n * @param attributes - Cookie attributes\n * @returns Set-Cookie header value\n *\n * @example\n * ```typescript\n * const setCookie = serializeCookie('session', 'abc123', {\n * httpOnly: true,\n * secure: true,\n * sameSite: 'lax',\n * maxAge: 86400,\n * path: '/',\n * })\n * // \"session=abc123; HttpOnly; Secure; SameSite=Lax; Max-Age=86400; Path=/\"\n * ```\n */\nexport function serializeCookie(\n name: string,\n value: string,\n attributes: CookieAttributes = {}\n): string {\n const parts: string[] = [`${encodeURIComponent(name)}=${encodeURIComponent(value)}`]\n\n if (attributes.domain) {\n parts.push(`Domain=${attributes.domain}`)\n }\n\n if (attributes.path) {\n parts.push(`Path=${attributes.path}`)\n }\n\n if (attributes.expires) {\n parts.push(`Expires=${attributes.expires.toUTCString()}`)\n }\n\n if (attributes.maxAge !== undefined) {\n parts.push(`Max-Age=${attributes.maxAge}`)\n }\n\n if (attributes.httpOnly) {\n parts.push('HttpOnly')\n }\n\n if (attributes.secure) {\n parts.push('Secure')\n }\n\n if (attributes.sameSite) {\n const sameSiteValue = attributes.sameSite.charAt(0).toUpperCase() + attributes.sameSite.slice(1)\n parts.push(`SameSite=${sameSiteValue}`)\n }\n\n return parts.join('; ')\n}\n","/**\n * @cloudwerk/security - Timing-Safe Utilities\n *\n * Utilities for preventing timing attacks.\n */\n\n/**\n * Perform a timing-safe string comparison.\n *\n * This prevents timing attacks by comparing all characters regardless\n * of where differences occur.\n *\n * @param a - First string\n * @param b - Second string\n * @returns True if strings are equal\n */\nexport function timingSafeEqual(a: string, b: string): boolean {\n if (a.length !== b.length) {\n return false\n }\n\n // Use XOR to compare without early exit\n let result = 0\n for (let i = 0; i < a.length; i++) {\n result |= a.charCodeAt(i) ^ b.charCodeAt(i)\n }\n\n return result === 0\n}\n","/**\n * @cloudwerk/security - CSRF Token Utilities\n *\n * Functions for generating, setting, and verifying CSRF tokens.\n */\n\nimport type { SetCsrfCookieOptions } from '../types.js'\nimport { serializeCookie, parseCookies } from '../utils/cookie.js'\nimport { timingSafeEqual } from '../utils/timing-safe.js'\n\n// ============================================================================\n// Constants\n// ============================================================================\n\n/** Default CSRF cookie name */\nexport const DEFAULT_CSRF_COOKIE_NAME = 'cloudwerk.csrf-token'\n\n/** Default CSRF header name */\nexport const DEFAULT_CSRF_HEADER_NAME = 'X-CSRF-Token'\n\n/** Default CSRF form field name */\nexport const DEFAULT_CSRF_FORM_FIELD_NAME = 'csrf_token'\n\n/** CSRF token length in bytes (32 bytes = 256 bits) */\nconst CSRF_TOKEN_BYTES = 32\n\n/** Default max age for CSRF cookie (24 hours) */\nconst DEFAULT_CSRF_MAX_AGE = 24 * 60 * 60\n\n// ============================================================================\n// Token Generation\n// ============================================================================\n\n/**\n * Generate a cryptographically secure CSRF token.\n *\n * Uses Web Crypto API for secure random number generation.\n *\n * @returns A URL-safe base64-encoded random token\n *\n * @example\n * ```typescript\n * import { generateCsrfToken } from '@cloudwerk/security'\n *\n * const token = generateCsrfToken()\n * // 'Yx8nK2pQ...' (43 characters)\n * ```\n */\nexport function generateCsrfToken(): string {\n const bytes = new Uint8Array(CSRF_TOKEN_BYTES)\n crypto.getRandomValues(bytes)\n\n // Convert to URL-safe base64\n const base64 = btoa(String.fromCharCode(...bytes))\n return base64.replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '')\n}\n\n// ============================================================================\n// Cookie Helpers\n// ============================================================================\n\n/**\n * Set a CSRF cookie on a response.\n *\n * Creates a new response with the CSRF cookie set. The cookie is accessible\n * to JavaScript (not httpOnly) so that SPA frameworks can read it and include\n * it in request headers.\n *\n * @param response - The response to add the cookie to\n * @param token - The CSRF token to set (generate with generateCsrfToken())\n * @param options - Cookie configuration options\n * @returns A new response with the Set-Cookie header added\n *\n * @example\n * ```typescript\n * import { generateCsrfToken, setCsrfCookie } from '@cloudwerk/security'\n *\n * export function GET(request: Request) {\n * const token = generateCsrfToken()\n * const response = new Response(JSON.stringify({ csrfToken: token }))\n * return setCsrfCookie(response, token)\n * }\n * ```\n */\nexport function setCsrfCookie(\n response: Response,\n token: string,\n options: SetCsrfCookieOptions = {}\n): Response {\n const {\n cookieName = DEFAULT_CSRF_COOKIE_NAME,\n path = '/',\n httpOnly = false, // Must be false to allow JS access\n secure = true,\n sameSite = 'lax',\n maxAge = DEFAULT_CSRF_MAX_AGE,\n } = options\n\n const cookieValue = serializeCookie(cookieName, token, {\n path,\n httpOnly,\n secure,\n sameSite,\n maxAge,\n })\n\n // Clone response and append cookie\n const headers = new Headers(response.headers)\n headers.append('Set-Cookie', cookieValue)\n\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers,\n })\n}\n\n/**\n * Get the CSRF token from a request's cookie.\n *\n * @param request - The request to extract the token from\n * @param cookieName - The cookie name to look for\n * @returns The CSRF token or null if not found\n */\nexport function getCsrfTokenFromCookie(\n request: Request,\n cookieName: string = DEFAULT_CSRF_COOKIE_NAME\n): string | null {\n const cookieHeader = request.headers.get('Cookie')\n if (!cookieHeader) return null\n\n const cookies = parseCookies(cookieHeader)\n return cookies[cookieName] ?? null\n}\n\n/**\n * Get the CSRF token from a request's header.\n *\n * @param request - The request to extract the token from\n * @param headerName - The header name to look for\n * @returns The CSRF token or null if not found\n */\nexport function getCsrfTokenFromHeader(\n request: Request,\n headerName: string = DEFAULT_CSRF_HEADER_NAME\n): string | null {\n return request.headers.get(headerName)\n}\n\n/**\n * Get the CSRF token from a request's form body.\n *\n * @param request - The request to extract the token from (will be cloned)\n * @param fieldName - The form field name to look for\n * @returns The CSRF token or null if not found\n */\nexport async function getCsrfTokenFromFormBody(\n request: Request,\n fieldName: string = DEFAULT_CSRF_FORM_FIELD_NAME\n): Promise<string | null> {\n const contentType = request.headers.get('Content-Type') || ''\n\n // Only check form data for form submissions\n if (!contentType.includes('application/x-www-form-urlencoded') &&\n !contentType.includes('multipart/form-data')) {\n return null\n }\n\n try {\n // Clone request to avoid consuming the body\n const clonedRequest = request.clone()\n const formData = await clonedRequest.formData()\n const token = formData.get(fieldName)\n return typeof token === 'string' ? token : null\n } catch {\n return null\n }\n}\n\n// ============================================================================\n// Token Verification\n// ============================================================================\n\n/**\n * Verify a CSRF token against the token in the cookie.\n *\n * Uses timing-safe comparison to prevent timing attacks.\n *\n * @param cookieToken - The token from the cookie\n * @param requestToken - The token from the request (header or form body)\n * @returns True if tokens match\n *\n * @example\n * ```typescript\n * import { verifyCsrfToken, getCsrfTokenFromCookie, getCsrfTokenFromHeader } from '@cloudwerk/security'\n *\n * const cookieToken = getCsrfTokenFromCookie(request)\n * const headerToken = getCsrfTokenFromHeader(request)\n *\n * if (cookieToken && headerToken && verifyCsrfToken(cookieToken, headerToken)) {\n * // Token is valid\n * }\n * ```\n */\nexport function verifyCsrfToken(cookieToken: string, requestToken: string): boolean {\n return timingSafeEqual(cookieToken, requestToken)\n}\n\n// ============================================================================\n// Token Rotation\n// ============================================================================\n\n/**\n * Rotate the CSRF token on a response.\n *\n * Generates a new CSRF token and sets it as a cookie. This should be called\n * after successful authentication to bind the CSRF token to the new session\n * and prevent session fixation attacks.\n *\n * @param response - The response to add the new CSRF cookie to\n * @param options - Cookie configuration options\n * @returns A new response with the rotated CSRF token cookie\n *\n * @example\n * ```typescript\n * import { rotateCsrfToken } from '@cloudwerk/security'\n *\n * // After successful login\n * export async function handleLogin(request: Request) {\n * const user = await validateCredentials(request)\n * const session = await createSession(user)\n *\n * let response = createAuthResponse(user, session)\n * response = rotateCsrfToken(response)\n * return response\n * }\n * ```\n */\nexport function rotateCsrfToken(\n response: Response,\n options: SetCsrfCookieOptions = {}\n): Response {\n const newToken = generateCsrfToken()\n return setCsrfCookie(response, newToken, options)\n}\n","/**\n * @cloudwerk/security - CSRF Middleware\n *\n * Provides CSRF (Cross-Site Request Forgery) protection for mutation requests.\n * Uses the double-submit cookie pattern for stateless CSRF protection.\n */\n\nimport type { Middleware } from '@cloudwerk/core'\nimport type { CSRFMiddlewareOptions } from '../types.js'\nimport {\n DEFAULT_CSRF_COOKIE_NAME,\n DEFAULT_CSRF_HEADER_NAME,\n DEFAULT_CSRF_FORM_FIELD_NAME,\n generateCsrfToken,\n getCsrfTokenFromCookie,\n getCsrfTokenFromHeader,\n getCsrfTokenFromFormBody,\n verifyCsrfToken,\n setCsrfCookie,\n} from './token.js'\n\n/** Default methods requiring CSRF validation */\nconst DEFAULT_CSRF_METHODS = ['POST', 'PUT', 'PATCH', 'DELETE']\n\n/**\n * Create CSRF protection middleware.\n *\n * Validates that mutation requests (POST, PUT, PATCH, DELETE) include a valid\n * CSRF token that matches the token in the cookie. Uses the double-submit\n * cookie pattern for stateless CSRF protection.\n *\n * The token can be provided via:\n * 1. Request header (X-CSRF-Token by default) - for AJAX requests\n * 2. Form field (csrf_token by default) - for traditional form submissions\n *\n * @param options - Middleware configuration options\n * @returns Middleware function\n *\n * @example\n * ```typescript\n * // In middleware.ts\n * import { csrfMiddleware } from '@cloudwerk/security/middleware'\n *\n * export const middleware = csrfMiddleware()\n * ```\n *\n * @example\n * ```typescript\n * // Exclude webhook paths\n * export const middleware = csrfMiddleware({\n * excludePaths: ['/api/webhooks/stripe', '/api/webhooks/github'],\n * })\n * ```\n */\nexport function csrfMiddleware(options: CSRFMiddlewareOptions = {}): Middleware {\n const {\n cookieName = DEFAULT_CSRF_COOKIE_NAME,\n headerName = DEFAULT_CSRF_HEADER_NAME,\n formFieldName = DEFAULT_CSRF_FORM_FIELD_NAME,\n methods = DEFAULT_CSRF_METHODS,\n excludePaths = [],\n } = options\n\n return async (request, next) => {\n const existingToken = getCsrfTokenFromCookie(request, cookieName)\n const isMutationMethod = methods.includes(request.method)\n\n // For safe methods (GET, HEAD, OPTIONS), set the cookie if missing\n if (!isMutationMethod) {\n const response = await next()\n\n // If no CSRF cookie exists, set one on the response\n // This ensures users get a token on their first request\n if (!existingToken) {\n const newToken = generateCsrfToken()\n return setCsrfCookie(response, newToken, { cookieName })\n }\n\n return response\n }\n\n // For mutation methods, we need to validate the CSRF token\n\n // Skip excluded paths\n const url = new URL(request.url)\n if (excludePaths.some((path) => url.pathname.startsWith(path))) {\n return next()\n }\n\n // Reject if no CSRF cookie (client never received a token)\n if (!existingToken) {\n return Response.json(\n { error: 'Missing CSRF token cookie' },\n { status: 403 }\n )\n }\n\n // Get token from header first, then fall back to form body\n let requestToken = getCsrfTokenFromHeader(request, headerName)\n\n if (!requestToken) {\n requestToken = await getCsrfTokenFromFormBody(request, formFieldName)\n }\n\n if (!requestToken) {\n return Response.json(\n { error: 'Missing CSRF token in request' },\n { status: 403 }\n )\n }\n\n // Compare tokens using timing-safe comparison\n if (!verifyCsrfToken(existingToken, requestToken)) {\n return Response.json(\n { error: 'Invalid CSRF token' },\n { status: 403 }\n )\n }\n\n return next()\n }\n}\n","/**\n * @cloudwerk/security - Security Headers Middleware\n *\n * Sets standard security headers on responses.\n */\n\nimport type { Middleware } from '@cloudwerk/core'\nimport type { SecurityHeadersOptions } from '../types.js'\n\n/**\n * Default security headers configuration.\n */\nconst DEFAULT_OPTIONS: Required<SecurityHeadersOptions> = {\n contentTypeOptions: 'nosniff',\n frameOptions: 'DENY',\n referrerPolicy: 'strict-origin-when-cross-origin',\n xssProtection: '0', // Disabled as modern browsers handle XSS\n permittedCrossDomainPolicies: 'none',\n dnsPrefetchControl: 'off',\n crossOriginOpenerPolicy: false,\n crossOriginEmbedderPolicy: false,\n crossOriginResourcePolicy: false,\n}\n\n/**\n * Create security headers middleware.\n *\n * Sets standard security headers on all responses:\n * - X-Content-Type-Options: nosniff\n * - X-Frame-Options: DENY\n * - Referrer-Policy: strict-origin-when-cross-origin\n * - X-XSS-Protection: 0 (disabled, modern browsers handle XSS)\n * - X-Permitted-Cross-Domain-Policies: none\n * - X-DNS-Prefetch-Control: off\n *\n * @param options - Header configuration options\n * @returns Middleware function\n *\n * @example\n * ```typescript\n * import { securityHeadersMiddleware } from '@cloudwerk/security/middleware'\n *\n * // Use defaults\n * export const middleware = securityHeadersMiddleware()\n *\n * // Custom configuration\n * export const middleware = securityHeadersMiddleware({\n * frameOptions: 'SAMEORIGIN',\n * crossOriginOpenerPolicy: 'same-origin',\n * })\n * ```\n */\nexport function securityHeadersMiddleware(\n options: SecurityHeadersOptions = {}\n): Middleware {\n const config = { ...DEFAULT_OPTIONS, ...options }\n\n return async (request, next) => {\n const response = await next()\n\n // Clone headers to modify\n const headers = new Headers(response.headers)\n\n // X-Content-Type-Options\n if (config.contentTypeOptions !== false) {\n headers.set('X-Content-Type-Options', config.contentTypeOptions)\n }\n\n // X-Frame-Options\n if (config.frameOptions !== false) {\n headers.set('X-Frame-Options', config.frameOptions)\n }\n\n // Referrer-Policy\n if (config.referrerPolicy !== false) {\n headers.set('Referrer-Policy', config.referrerPolicy)\n }\n\n // X-XSS-Protection\n if (config.xssProtection !== false) {\n headers.set('X-XSS-Protection', config.xssProtection)\n }\n\n // X-Permitted-Cross-Domain-Policies\n if (config.permittedCrossDomainPolicies !== false) {\n headers.set('X-Permitted-Cross-Domain-Policies', config.permittedCrossDomainPolicies)\n }\n\n // X-DNS-Prefetch-Control\n if (config.dnsPrefetchControl !== false) {\n headers.set('X-DNS-Prefetch-Control', config.dnsPrefetchControl)\n }\n\n // Cross-Origin-Opener-Policy\n if (config.crossOriginOpenerPolicy !== false) {\n headers.set('Cross-Origin-Opener-Policy', config.crossOriginOpenerPolicy)\n }\n\n // Cross-Origin-Embedder-Policy\n if (config.crossOriginEmbedderPolicy !== false) {\n headers.set('Cross-Origin-Embedder-Policy', config.crossOriginEmbedderPolicy)\n }\n\n // Cross-Origin-Resource-Policy\n if (config.crossOriginResourcePolicy !== false) {\n headers.set('Cross-Origin-Resource-Policy', config.crossOriginResourcePolicy)\n }\n\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers,\n })\n }\n}\n","/**\n * @cloudwerk/security - Content Security Policy Middleware\n *\n * Generates and sets Content-Security-Policy headers.\n */\n\nimport type { Middleware } from '@cloudwerk/core'\nimport type { CSPOptions, CSPDirectives, CSPDirectiveValue } from '../types.js'\n\n/**\n * Map of directive names to their CSP header names.\n */\nconst DIRECTIVE_MAP: Record<keyof CSPDirectives, string> = {\n defaultSrc: 'default-src',\n scriptSrc: 'script-src',\n styleSrc: 'style-src',\n imgSrc: 'img-src',\n fontSrc: 'font-src',\n connectSrc: 'connect-src',\n mediaSrc: 'media-src',\n objectSrc: 'object-src',\n prefetchSrc: 'prefetch-src',\n frameSrc: 'frame-src',\n workerSrc: 'worker-src',\n childSrc: 'child-src',\n frameAncestors: 'frame-ancestors',\n formAction: 'form-action',\n pluginTypes: 'plugin-types',\n baseUri: 'base-uri',\n sandbox: 'sandbox',\n requireSriFor: 'require-sri-for',\n reportUri: 'report-uri',\n reportTo: 'report-to',\n requireTrustedTypesFor: 'require-trusted-types-for',\n trustedTypes: 'trusted-types',\n upgradeInsecureRequests: 'upgrade-insecure-requests',\n blockAllMixedContent: 'block-all-mixed-content',\n manifestSrc: 'manifest-src',\n scriptSrcElem: 'script-src-elem',\n scriptSrcAttr: 'script-src-attr',\n styleSrcElem: 'style-src-elem',\n styleSrcAttr: 'style-src-attr',\n}\n\n/**\n * Generate a cryptographically secure nonce for CSP.\n *\n * @returns A base64-encoded random nonce\n */\nexport function generateNonce(): string {\n const bytes = new Uint8Array(16)\n crypto.getRandomValues(bytes)\n return btoa(String.fromCharCode(...bytes))\n}\n\n/**\n * Format a directive value for CSP header.\n */\nfunction formatDirectiveValue(value: CSPDirectiveValue): string {\n if (Array.isArray(value)) {\n return value.join(' ')\n }\n return value\n}\n\n/**\n * Generate a Content-Security-Policy header string from directives.\n *\n * @param directives - CSP directives object\n * @param nonce - Optional nonce to include in script-src and style-src\n * @returns CSP header string\n *\n * @example\n * ```typescript\n * const csp = generateCSPHeader({\n * defaultSrc: [\"'self'\"],\n * scriptSrc: [\"'self'\", 'https://cdn.example.com'],\n * styleSrc: [\"'self'\", \"'unsafe-inline'\"],\n * })\n * // \"default-src 'self'; script-src 'self' https://cdn.example.com; style-src 'self' 'unsafe-inline'\"\n * ```\n */\nexport function generateCSPHeader(\n directives: CSPDirectives,\n nonce?: string\n): string {\n const parts: string[] = []\n\n for (const [key, value] of Object.entries(directives)) {\n if (value === undefined || value === false) continue\n\n const directiveName = DIRECTIVE_MAP[key as keyof CSPDirectives]\n if (!directiveName) continue\n\n // Boolean directives (no value needed)\n if (value === true) {\n parts.push(directiveName)\n continue\n }\n\n // Add nonce to script-src and style-src if provided\n let formattedValue = formatDirectiveValue(value as CSPDirectiveValue)\n if (nonce && (key === 'scriptSrc' || key === 'styleSrc' ||\n key === 'scriptSrcElem' || key === 'styleSrcElem')) {\n formattedValue += ` 'nonce-${nonce}'`\n }\n\n parts.push(`${directiveName} ${formattedValue}`)\n }\n\n return parts.join('; ')\n}\n\n/**\n * Create CSP middleware.\n *\n * Generates and sets Content-Security-Policy (or Content-Security-Policy-Report-Only)\n * headers on responses.\n *\n * @param options - CSP configuration options\n * @returns Middleware function\n *\n * @example\n * ```typescript\n * import { cspMiddleware } from '@cloudwerk/security/middleware'\n *\n * export const middleware = cspMiddleware({\n * directives: {\n * defaultSrc: [\"'self'\"],\n * scriptSrc: [\"'self'\", 'https://cdn.example.com'],\n * styleSrc: [\"'self'\", \"'unsafe-inline'\"],\n * imgSrc: [\"'self'\", 'data:', 'https:'],\n * },\n * })\n * ```\n *\n * @example\n * ```typescript\n * // Report-only mode for testing\n * export const middleware = cspMiddleware({\n * directives: {\n * defaultSrc: [\"'self'\"],\n * reportUri: '/api/csp-report',\n * },\n * reportOnly: true,\n * })\n * ```\n *\n * @example\n * ```typescript\n * // With nonce generation for inline scripts\n * export const middleware = cspMiddleware({\n * directives: {\n * defaultSrc: [\"'self'\"],\n * scriptSrc: [\"'self'\"],\n * },\n * useNonce: true,\n * })\n *\n * // In your page, access the nonce via context:\n * // const nonce = context.get('cspNonce')\n * // <script nonce={nonce}>...</script>\n * ```\n */\nexport function cspMiddleware(options: CSPOptions = {}): Middleware {\n const {\n directives = {},\n reportOnly = false,\n useNonce = false,\n // TODO: Use context to store nonce instead of custom header\n nonceContextKey: _nonceContextKey = 'cspNonce',\n } = options\n\n const headerName = reportOnly\n ? 'Content-Security-Policy-Report-Only'\n : 'Content-Security-Policy'\n\n return async (request, next) => {\n // Generate nonce if requested\n let nonce: string | undefined\n if (useNonce) {\n nonce = generateNonce()\n // Note: In a real implementation, we'd use getContext().set() to store the nonce\n // For now, we'll store it in a custom header that pages can read\n }\n\n const response = await next()\n\n // Generate CSP header\n const cspValue = generateCSPHeader(directives, nonce)\n\n if (!cspValue) {\n return response\n }\n\n // Clone headers and add CSP\n const headers = new Headers(response.headers)\n headers.set(headerName, cspValue)\n\n // If using nonce, add it as a custom header for pages to read\n if (nonce) {\n headers.set('X-CSP-Nonce', nonce)\n }\n\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers,\n })\n }\n}\n","/**\n * @cloudwerk/security - Origin Validation Middleware\n *\n * Validates Origin and Referer headers to prevent cross-origin attacks.\n */\n\nimport type { Middleware } from '@cloudwerk/core'\nimport type { OriginValidationOptions } from '../types.js'\n\n/** Default methods requiring origin validation */\nconst DEFAULT_METHODS = ['POST', 'PUT', 'PATCH', 'DELETE']\n\n/**\n * Check if an origin matches an allowed host.\n *\n * @param originHost - The host from the Origin header\n * @param allowedHost - The allowed host to check against\n * @param allowSubdomains - Whether to allow subdomains\n * @returns True if the origin matches\n */\nfunction hostMatches(\n originHost: string,\n allowedHost: string,\n allowSubdomains: boolean\n): boolean {\n if (originHost === allowedHost) {\n return true\n }\n\n if (allowSubdomains && originHost.endsWith(`.${allowedHost}`)) {\n return true\n }\n\n return false\n}\n\n/**\n * Validate an origin against allowed origins and hosts.\n *\n * @param origin - The Origin header value\n * @param options - Validation options\n * @returns True if the origin is allowed\n */\nfunction isOriginAllowed(origin: string, options: OriginValidationOptions): boolean {\n const { allowedOrigins = [], allowedHosts = [], allowSubdomains = false } = options\n\n // Check against allowed origins (exact match)\n if (allowedOrigins.includes(origin)) {\n return true\n }\n\n // Parse origin to get host\n let originHost: string\n try {\n const url = new URL(origin)\n originHost = url.host\n } catch {\n return false\n }\n\n // Check against allowed hosts\n for (const allowedHost of allowedHosts) {\n if (hostMatches(originHost, allowedHost, allowSubdomains)) {\n return true\n }\n }\n\n return false\n}\n\n/**\n * Create origin validation middleware.\n *\n * Validates that mutation requests (POST, PUT, PATCH, DELETE) come from\n * allowed origins. This helps prevent CSRF attacks by rejecting requests\n * from unknown origins.\n *\n * @param options - Validation options\n * @returns Middleware function\n *\n * @example\n * ```typescript\n * import { originValidationMiddleware } from '@cloudwerk/security/middleware'\n *\n * export const middleware = originValidationMiddleware({\n * allowedOrigins: ['https://myapp.com', 'https://admin.myapp.com'],\n * })\n * ```\n *\n * @example\n * ```typescript\n * // Allow all subdomains of a host\n * export const middleware = originValidationMiddleware({\n * allowedHosts: ['myapp.com'],\n * allowSubdomains: true,\n * })\n * ```\n */\nexport function originValidationMiddleware(\n options: OriginValidationOptions = {}\n): Middleware {\n const {\n allowedOrigins = [],\n allowedHosts = [],\n methods = DEFAULT_METHODS,\n excludePaths = [],\n rejectMissingOrigin = true,\n } = options\n\n // If no origins or hosts are configured, skip validation\n const hasConfig = allowedOrigins.length > 0 || allowedHosts.length > 0\n\n return async (request, next) => {\n // Skip if no configuration\n if (!hasConfig) {\n return next()\n }\n\n // Skip if method doesn't require validation\n if (!methods.includes(request.method)) {\n return next()\n }\n\n // Skip excluded paths\n const url = new URL(request.url)\n if (excludePaths.some((path) => url.pathname.startsWith(path))) {\n return next()\n }\n\n // Get Origin header (preferred) or fall back to Referer\n let origin = request.headers.get('Origin')\n\n if (!origin) {\n const referer = request.headers.get('Referer')\n if (referer) {\n try {\n const refererUrl = new URL(referer)\n origin = refererUrl.origin\n } catch {\n // Invalid referer URL\n }\n }\n }\n\n // Check if origin is missing\n if (!origin) {\n if (rejectMissingOrigin) {\n return Response.json(\n { error: 'Missing Origin header' },\n { status: 403 }\n )\n }\n // Allow if configured to not reject missing origins\n return next()\n }\n\n // Validate origin\n if (!isOriginAllowed(origin, options)) {\n return Response.json(\n { error: 'Origin not allowed' },\n { status: 403 }\n )\n }\n\n return next()\n }\n}\n","/**\n * @cloudwerk/security - X-Requested-With Validation Middleware\n *\n * Requires X-Requested-With header to force CORS preflight for cross-origin requests.\n */\n\nimport type { Middleware } from '@cloudwerk/core'\nimport type { RequestedWithOptions } from '../types.js'\n\n/** Default methods requiring X-Requested-With validation */\nconst DEFAULT_METHODS = ['POST', 'PUT', 'PATCH', 'DELETE']\n\n/** Default required header value */\nconst DEFAULT_REQUIRED_VALUE = 'XMLHttpRequest'\n\n/**\n * Create X-Requested-With validation middleware.\n *\n * Requires mutation requests to include an `X-Requested-With` header.\n * This forces CORS preflight for cross-origin requests, providing an\n * additional layer of protection against CSRF attacks.\n *\n * The X-Requested-With header is a custom header that cannot be set\n * cross-origin without a CORS preflight. By requiring it, we ensure\n * that cross-origin requests must pass CORS checks.\n *\n * @param options - Validation options\n * @returns Middleware function\n *\n * @example\n * ```typescript\n * import { requestedWithMiddleware } from '@cloudwerk/security/middleware'\n *\n * // Default: requires X-Requested-With: XMLHttpRequest\n * export const middleware = requestedWithMiddleware()\n * ```\n *\n * @example\n * ```typescript\n * // Custom header value\n * export const middleware = requestedWithMiddleware({\n * requiredValue: 'fetch',\n * excludePaths: ['/api/webhooks'],\n * })\n * ```\n */\nexport function requestedWithMiddleware(\n options: RequestedWithOptions = {}\n): Middleware {\n const {\n requiredValue = DEFAULT_REQUIRED_VALUE,\n methods = DEFAULT_METHODS,\n excludePaths = [],\n } = options\n\n return async (request, next) => {\n // Skip if method doesn't require validation\n if (!methods.includes(request.method)) {\n return next()\n }\n\n // Skip excluded paths\n const url = new URL(request.url)\n if (excludePaths.some((path) => url.pathname.startsWith(path))) {\n return next()\n }\n\n // Check X-Requested-With header\n const headerValue = request.headers.get('X-Requested-With')\n\n if (!headerValue) {\n return Response.json(\n { error: 'Missing X-Requested-With header' },\n { status: 403 }\n )\n }\n\n if (headerValue !== requiredValue) {\n return Response.json(\n { error: 'Invalid X-Requested-With header' },\n { status: 403 }\n )\n }\n\n return next()\n }\n}\n","/**\n * @cloudwerk/security - Combined Security Middleware\n *\n * All-in-one middleware that combines CSRF, security headers, CSP,\n * origin validation, and X-Requested-With validation.\n */\n\nimport type { Middleware } from '@cloudwerk/core'\nimport type { SecurityMiddlewareOptions } from '../types.js'\nimport { csrfMiddleware } from '../csrf/middleware.js'\nimport { securityHeadersMiddleware } from '../headers/security-headers.js'\nimport { cspMiddleware } from '../headers/csp.js'\nimport { originValidationMiddleware } from '../origin/middleware.js'\nimport { requestedWithMiddleware } from '../request/middleware.js'\n\n/**\n * Compose multiple middleware functions into one.\n *\n * Executes middleware in order, passing the response through each.\n *\n * @example\n * ```typescript\n * import { composeMiddleware } from '@cloudwerk/security'\n * import { securityMiddleware } from '@cloudwerk/security/middleware'\n * import { authMiddleware } from '@cloudwerk/auth/middleware'\n *\n * export const middleware = composeMiddleware([\n * securityMiddleware(),\n * authMiddleware(),\n * ])\n * ```\n */\nexport function composeMiddleware(middlewares: Middleware[]): Middleware {\n return async (request, next) => {\n // Build the middleware chain in reverse order\n let handler: () => Promise<Response> = next\n\n for (let i = middlewares.length - 1; i >= 0; i--) {\n const middleware = middlewares[i]\n const prevHandler = handler\n handler = async () => middleware(request, prevHandler)\n }\n\n return handler()\n }\n}\n\n/**\n * Create a combined security middleware.\n *\n * This middleware composes multiple security protections with sensible defaults:\n * - **csrf**: Enabled by default - validates CSRF tokens on mutation requests\n * - **requestedWith**: Enabled by default - requires X-Requested-With header\n * - **headers**: Enabled by default - sets security headers (nosniff, DENY, etc.)\n * - **csp**: Disabled by default - requires app-specific configuration\n * - **origin**: Disabled by default - requires allowedOrigins configuration\n *\n * @param options - Configuration options for each protection\n * @returns Middleware function\n *\n * @example\n * ```typescript\n * import { securityMiddleware } from '@cloudwerk/security/middleware'\n *\n * // Use with all defaults\n * export const middleware = securityMiddleware()\n * ```\n *\n * @example\n * ```typescript\n * // Full configuration\n * export const middleware = securityMiddleware({\n * allowedOrigins: ['https://myapp.com'],\n * csrf: {\n * excludePaths: ['/api/webhooks/stripe'],\n * },\n * csp: {\n * directives: {\n * defaultSrc: [\"'self'\"],\n * scriptSrc: [\"'self'\", 'https://cdn.example.com'],\n * },\n * reportOnly: true,\n * },\n * headers: {\n * frameOptions: 'SAMEORIGIN',\n * },\n * })\n * ```\n *\n * @example\n * ```typescript\n * // Disable specific protections\n * export const middleware = securityMiddleware({\n * csrf: false, // Disable CSRF\n * requestedWith: false, // Disable X-Requested-With\n * })\n * ```\n */\nexport function securityMiddleware(\n options: SecurityMiddlewareOptions = {}\n): Middleware {\n const middlewares: Middleware[] = []\n\n // Add security headers middleware (enabled by default)\n if (options.headers !== false) {\n const headersOptions = typeof options.headers === 'object' ? options.headers : {}\n middlewares.push(securityHeadersMiddleware(headersOptions))\n }\n\n // Add CSP middleware (disabled by default - requires configuration)\n if (options.csp && typeof options.csp === 'object') {\n middlewares.push(cspMiddleware(options.csp))\n }\n\n // Add origin validation middleware (disabled by default unless allowedOrigins provided)\n if (options.origin !== false) {\n const originOptions = typeof options.origin === 'object'\n ? { ...options.origin }\n : { allowedOrigins: options.allowedOrigins }\n\n // Merge shorthand allowedOrigins into origin options\n if (options.allowedOrigins && !originOptions.allowedOrigins) {\n originOptions.allowedOrigins = options.allowedOrigins\n }\n\n // Only add if there are actual origins/hosts configured\n if (originOptions.allowedOrigins?.length || originOptions.allowedHosts?.length) {\n middlewares.push(originValidationMiddleware(originOptions))\n }\n }\n\n // Add X-Requested-With middleware (enabled by default)\n if (options.requestedWith !== false) {\n const requestedWithOptions = typeof options.requestedWith === 'object'\n ? options.requestedWith\n : {}\n middlewares.push(requestedWithMiddleware(requestedWithOptions))\n }\n\n // Add CSRF middleware (enabled by default)\n if (options.csrf !== false) {\n const csrfOptions = typeof options.csrf === 'object' ? options.csrf : {}\n middlewares.push(csrfMiddleware(csrfOptions))\n }\n\n // If no middleware is enabled, just pass through\n if (middlewares.length === 0) {\n return async (_request: Request, next: () => Promise<Response>) => next()\n }\n\n // Compose all middleware\n return composeMiddleware(middlewares)\n}\n"],"mappings":"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","names":["DEFAULT_METHODS"]}
|
package/dist/middleware.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { DEFAULT_CSRF_COOKIE_NAME, DEFAULT_CSRF_FORM_FIELD_NAME, DEFAULT_CSRF_HEADER_NAME, cspMiddleware, csrfMiddleware, generateCSPHeader, generateCsrfToken, generateNonce, getCsrfTokenFromCookie, getCsrfTokenFromFormBody, getCsrfTokenFromHeader, originValidationMiddleware, requestedWithMiddleware, rotateCsrfToken, securityHeadersMiddleware, securityMiddleware, setCsrfCookie, verifyCsrfToken } from './index.js';
|
|
1
|
+
export { DEFAULT_CSRF_COOKIE_NAME, DEFAULT_CSRF_FORM_FIELD_NAME, DEFAULT_CSRF_HEADER_NAME, composeMiddleware, cspMiddleware, csrfMiddleware, generateCSPHeader, generateCsrfToken, generateNonce, getCsrfTokenFromCookie, getCsrfTokenFromFormBody, getCsrfTokenFromHeader, originValidationMiddleware, requestedWithMiddleware, rotateCsrfToken, securityHeadersMiddleware, securityMiddleware, setCsrfCookie, verifyCsrfToken } from './index.js';
|
|
2
2
|
export { f as CSPDirectiveValue, d as CSPDirectives, c as CSPOptions, C as CSRFMiddlewareOptions, g as CookieAttributes, O as OriginValidationOptions, R as RequestedWithOptions, S as SecureFetchOptions, b as SecurityHeadersOptions, e as SecurityMiddlewareOptions, a as SetCsrfCookieOptions } from './types-DtbmXdeT.js';
|
|
3
3
|
import '@cloudwerk/core';
|
package/dist/middleware.js
CHANGED
|
@@ -133,15 +133,21 @@ function csrfMiddleware(options = {}) {
|
|
|
133
133
|
excludePaths = []
|
|
134
134
|
} = options;
|
|
135
135
|
return async (request, next) => {
|
|
136
|
-
|
|
137
|
-
|
|
136
|
+
const existingToken = getCsrfTokenFromCookie(request, cookieName);
|
|
137
|
+
const isMutationMethod = methods.includes(request.method);
|
|
138
|
+
if (!isMutationMethod) {
|
|
139
|
+
const response = await next();
|
|
140
|
+
if (!existingToken) {
|
|
141
|
+
const newToken = generateCsrfToken();
|
|
142
|
+
return setCsrfCookie(response, newToken, { cookieName });
|
|
143
|
+
}
|
|
144
|
+
return response;
|
|
138
145
|
}
|
|
139
146
|
const url = new URL(request.url);
|
|
140
147
|
if (excludePaths.some((path) => url.pathname.startsWith(path))) {
|
|
141
148
|
return next();
|
|
142
149
|
}
|
|
143
|
-
|
|
144
|
-
if (!cookieToken) {
|
|
150
|
+
if (!existingToken) {
|
|
145
151
|
return Response.json(
|
|
146
152
|
{ error: "Missing CSRF token cookie" },
|
|
147
153
|
{ status: 403 }
|
|
@@ -157,7 +163,7 @@ function csrfMiddleware(options = {}) {
|
|
|
157
163
|
{ status: 403 }
|
|
158
164
|
);
|
|
159
165
|
}
|
|
160
|
-
if (!verifyCsrfToken(
|
|
166
|
+
if (!verifyCsrfToken(existingToken, requestToken)) {
|
|
161
167
|
return Response.json(
|
|
162
168
|
{ error: "Invalid CSRF token" },
|
|
163
169
|
{ status: 403 }
|
|
@@ -286,7 +292,8 @@ function cspMiddleware(options = {}) {
|
|
|
286
292
|
directives = {},
|
|
287
293
|
reportOnly = false,
|
|
288
294
|
useNonce = false,
|
|
289
|
-
|
|
295
|
+
// TODO: Use context to store nonce instead of custom header
|
|
296
|
+
nonceContextKey: _nonceContextKey = "cspNonce"
|
|
290
297
|
} = options;
|
|
291
298
|
const headerName = reportOnly ? "Content-Security-Policy-Report-Only" : "Content-Security-Policy";
|
|
292
299
|
return async (request, next) => {
|
|
@@ -473,6 +480,7 @@ export {
|
|
|
473
480
|
DEFAULT_CSRF_COOKIE_NAME,
|
|
474
481
|
DEFAULT_CSRF_FORM_FIELD_NAME,
|
|
475
482
|
DEFAULT_CSRF_HEADER_NAME,
|
|
483
|
+
composeMiddleware,
|
|
476
484
|
cspMiddleware,
|
|
477
485
|
csrfMiddleware,
|
|
478
486
|
generateCSPHeader,
|
package/dist/middleware.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/utils/cookie.ts","../src/utils/timing-safe.ts","../src/csrf/token.ts","../src/csrf/middleware.ts","../src/headers/security-headers.ts","../src/headers/csp.ts","../src/origin/middleware.ts","../src/request/middleware.ts","../src/combined/security-middleware.ts"],"sourcesContent":["/**\n * @cloudwerk/security - Cookie Utilities\n *\n * Utilities for parsing and serializing cookies.\n */\n\nimport type { CookieAttributes } from '../types.js'\n\n/**\n * Parse a cookie header string into key-value pairs.\n *\n * @param cookieHeader - The Cookie header value\n * @returns Record of cookie names to values\n *\n * @example\n * ```typescript\n * const cookies = parseCookies('session=abc123; theme=dark')\n * // { session: 'abc123', theme: 'dark' }\n * ```\n */\nexport function parseCookies(cookieHeader: string): Record<string, string> {\n const cookies: Record<string, string> = {}\n\n if (!cookieHeader) {\n return cookies\n }\n\n const pairs = cookieHeader.split(';')\n for (const pair of pairs) {\n const [name, ...valueParts] = pair.split('=')\n const trimmedName = name?.trim()\n if (trimmedName) {\n // Rejoin in case value contains '='\n const value = valueParts.join('=').trim()\n // Handle quoted values\n cookies[trimmedName] = value.startsWith('\"') && value.endsWith('\"')\n ? value.slice(1, -1)\n : value\n }\n }\n\n return cookies\n}\n\n/**\n * Serialize a cookie with name, value, and attributes into a Set-Cookie header value.\n *\n * @param name - Cookie name\n * @param value - Cookie value\n * @param attributes - Cookie attributes\n * @returns Set-Cookie header value\n *\n * @example\n * ```typescript\n * const setCookie = serializeCookie('session', 'abc123', {\n * httpOnly: true,\n * secure: true,\n * sameSite: 'lax',\n * maxAge: 86400,\n * path: '/',\n * })\n * // \"session=abc123; HttpOnly; Secure; SameSite=Lax; Max-Age=86400; Path=/\"\n * ```\n */\nexport function serializeCookie(\n name: string,\n value: string,\n attributes: CookieAttributes = {}\n): string {\n const parts: string[] = [`${encodeURIComponent(name)}=${encodeURIComponent(value)}`]\n\n if (attributes.domain) {\n parts.push(`Domain=${attributes.domain}`)\n }\n\n if (attributes.path) {\n parts.push(`Path=${attributes.path}`)\n }\n\n if (attributes.expires) {\n parts.push(`Expires=${attributes.expires.toUTCString()}`)\n }\n\n if (attributes.maxAge !== undefined) {\n parts.push(`Max-Age=${attributes.maxAge}`)\n }\n\n if (attributes.httpOnly) {\n parts.push('HttpOnly')\n }\n\n if (attributes.secure) {\n parts.push('Secure')\n }\n\n if (attributes.sameSite) {\n const sameSiteValue = attributes.sameSite.charAt(0).toUpperCase() + attributes.sameSite.slice(1)\n parts.push(`SameSite=${sameSiteValue}`)\n }\n\n return parts.join('; ')\n}\n","/**\n * @cloudwerk/security - Timing-Safe Utilities\n *\n * Utilities for preventing timing attacks.\n */\n\n/**\n * Perform a timing-safe string comparison.\n *\n * This prevents timing attacks by comparing all characters regardless\n * of where differences occur.\n *\n * @param a - First string\n * @param b - Second string\n * @returns True if strings are equal\n */\nexport function timingSafeEqual(a: string, b: string): boolean {\n if (a.length !== b.length) {\n return false\n }\n\n // Use XOR to compare without early exit\n let result = 0\n for (let i = 0; i < a.length; i++) {\n result |= a.charCodeAt(i) ^ b.charCodeAt(i)\n }\n\n return result === 0\n}\n","/**\n * @cloudwerk/security - CSRF Token Utilities\n *\n * Functions for generating, setting, and verifying CSRF tokens.\n */\n\nimport type { SetCsrfCookieOptions } from '../types.js'\nimport { serializeCookie, parseCookies } from '../utils/cookie.js'\nimport { timingSafeEqual } from '../utils/timing-safe.js'\n\n// ============================================================================\n// Constants\n// ============================================================================\n\n/** Default CSRF cookie name */\nexport const DEFAULT_CSRF_COOKIE_NAME = 'cloudwerk.csrf-token'\n\n/** Default CSRF header name */\nexport const DEFAULT_CSRF_HEADER_NAME = 'X-CSRF-Token'\n\n/** Default CSRF form field name */\nexport const DEFAULT_CSRF_FORM_FIELD_NAME = 'csrf_token'\n\n/** CSRF token length in bytes (32 bytes = 256 bits) */\nconst CSRF_TOKEN_BYTES = 32\n\n/** Default max age for CSRF cookie (24 hours) */\nconst DEFAULT_CSRF_MAX_AGE = 24 * 60 * 60\n\n// ============================================================================\n// Token Generation\n// ============================================================================\n\n/**\n * Generate a cryptographically secure CSRF token.\n *\n * Uses Web Crypto API for secure random number generation.\n *\n * @returns A URL-safe base64-encoded random token\n *\n * @example\n * ```typescript\n * import { generateCsrfToken } from '@cloudwerk/security'\n *\n * const token = generateCsrfToken()\n * // 'Yx8nK2pQ...' (43 characters)\n * ```\n */\nexport function generateCsrfToken(): string {\n const bytes = new Uint8Array(CSRF_TOKEN_BYTES)\n crypto.getRandomValues(bytes)\n\n // Convert to URL-safe base64\n const base64 = btoa(String.fromCharCode(...bytes))\n return base64.replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '')\n}\n\n// ============================================================================\n// Cookie Helpers\n// ============================================================================\n\n/**\n * Set a CSRF cookie on a response.\n *\n * Creates a new response with the CSRF cookie set. The cookie is accessible\n * to JavaScript (not httpOnly) so that SPA frameworks can read it and include\n * it in request headers.\n *\n * @param response - The response to add the cookie to\n * @param token - The CSRF token to set (generate with generateCsrfToken())\n * @param options - Cookie configuration options\n * @returns A new response with the Set-Cookie header added\n *\n * @example\n * ```typescript\n * import { generateCsrfToken, setCsrfCookie } from '@cloudwerk/security'\n *\n * export function GET(request: Request) {\n * const token = generateCsrfToken()\n * const response = new Response(JSON.stringify({ csrfToken: token }))\n * return setCsrfCookie(response, token)\n * }\n * ```\n */\nexport function setCsrfCookie(\n response: Response,\n token: string,\n options: SetCsrfCookieOptions = {}\n): Response {\n const {\n cookieName = DEFAULT_CSRF_COOKIE_NAME,\n path = '/',\n httpOnly = false, // Must be false to allow JS access\n secure = true,\n sameSite = 'lax',\n maxAge = DEFAULT_CSRF_MAX_AGE,\n } = options\n\n const cookieValue = serializeCookie(cookieName, token, {\n path,\n httpOnly,\n secure,\n sameSite,\n maxAge,\n })\n\n // Clone response and append cookie\n const headers = new Headers(response.headers)\n headers.append('Set-Cookie', cookieValue)\n\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers,\n })\n}\n\n/**\n * Get the CSRF token from a request's cookie.\n *\n * @param request - The request to extract the token from\n * @param cookieName - The cookie name to look for\n * @returns The CSRF token or null if not found\n */\nexport function getCsrfTokenFromCookie(\n request: Request,\n cookieName: string = DEFAULT_CSRF_COOKIE_NAME\n): string | null {\n const cookieHeader = request.headers.get('Cookie')\n if (!cookieHeader) return null\n\n const cookies = parseCookies(cookieHeader)\n return cookies[cookieName] ?? null\n}\n\n/**\n * Get the CSRF token from a request's header.\n *\n * @param request - The request to extract the token from\n * @param headerName - The header name to look for\n * @returns The CSRF token or null if not found\n */\nexport function getCsrfTokenFromHeader(\n request: Request,\n headerName: string = DEFAULT_CSRF_HEADER_NAME\n): string | null {\n return request.headers.get(headerName)\n}\n\n/**\n * Get the CSRF token from a request's form body.\n *\n * @param request - The request to extract the token from (will be cloned)\n * @param fieldName - The form field name to look for\n * @returns The CSRF token or null if not found\n */\nexport async function getCsrfTokenFromFormBody(\n request: Request,\n fieldName: string = DEFAULT_CSRF_FORM_FIELD_NAME\n): Promise<string | null> {\n const contentType = request.headers.get('Content-Type') || ''\n\n // Only check form data for form submissions\n if (!contentType.includes('application/x-www-form-urlencoded') &&\n !contentType.includes('multipart/form-data')) {\n return null\n }\n\n try {\n // Clone request to avoid consuming the body\n const clonedRequest = request.clone()\n const formData = await clonedRequest.formData()\n const token = formData.get(fieldName)\n return typeof token === 'string' ? token : null\n } catch {\n return null\n }\n}\n\n// ============================================================================\n// Token Verification\n// ============================================================================\n\n/**\n * Verify a CSRF token against the token in the cookie.\n *\n * Uses timing-safe comparison to prevent timing attacks.\n *\n * @param cookieToken - The token from the cookie\n * @param requestToken - The token from the request (header or form body)\n * @returns True if tokens match\n *\n * @example\n * ```typescript\n * import { verifyCsrfToken, getCsrfTokenFromCookie, getCsrfTokenFromHeader } from '@cloudwerk/security'\n *\n * const cookieToken = getCsrfTokenFromCookie(request)\n * const headerToken = getCsrfTokenFromHeader(request)\n *\n * if (cookieToken && headerToken && verifyCsrfToken(cookieToken, headerToken)) {\n * // Token is valid\n * }\n * ```\n */\nexport function verifyCsrfToken(cookieToken: string, requestToken: string): boolean {\n return timingSafeEqual(cookieToken, requestToken)\n}\n\n// ============================================================================\n// Token Rotation\n// ============================================================================\n\n/**\n * Rotate the CSRF token on a response.\n *\n * Generates a new CSRF token and sets it as a cookie. This should be called\n * after successful authentication to bind the CSRF token to the new session\n * and prevent session fixation attacks.\n *\n * @param response - The response to add the new CSRF cookie to\n * @param options - Cookie configuration options\n * @returns A new response with the rotated CSRF token cookie\n *\n * @example\n * ```typescript\n * import { rotateCsrfToken } from '@cloudwerk/security'\n *\n * // After successful login\n * export async function handleLogin(request: Request) {\n * const user = await validateCredentials(request)\n * const session = await createSession(user)\n *\n * let response = createAuthResponse(user, session)\n * response = rotateCsrfToken(response)\n * return response\n * }\n * ```\n */\nexport function rotateCsrfToken(\n response: Response,\n options: SetCsrfCookieOptions = {}\n): Response {\n const newToken = generateCsrfToken()\n return setCsrfCookie(response, newToken, options)\n}\n","/**\n * @cloudwerk/security - CSRF Middleware\n *\n * Provides CSRF (Cross-Site Request Forgery) protection for mutation requests.\n * Uses the double-submit cookie pattern for stateless CSRF protection.\n */\n\nimport type { Middleware } from '@cloudwerk/core'\nimport type { CSRFMiddlewareOptions } from '../types.js'\nimport {\n DEFAULT_CSRF_COOKIE_NAME,\n DEFAULT_CSRF_HEADER_NAME,\n DEFAULT_CSRF_FORM_FIELD_NAME,\n getCsrfTokenFromCookie,\n getCsrfTokenFromHeader,\n getCsrfTokenFromFormBody,\n verifyCsrfToken,\n} from './token.js'\n\n/** Default methods requiring CSRF validation */\nconst DEFAULT_CSRF_METHODS = ['POST', 'PUT', 'PATCH', 'DELETE']\n\n/**\n * Create CSRF protection middleware.\n *\n * Validates that mutation requests (POST, PUT, PATCH, DELETE) include a valid\n * CSRF token that matches the token in the cookie. Uses the double-submit\n * cookie pattern for stateless CSRF protection.\n *\n * The token can be provided via:\n * 1. Request header (X-CSRF-Token by default) - for AJAX requests\n * 2. Form field (csrf_token by default) - for traditional form submissions\n *\n * @param options - Middleware configuration options\n * @returns Middleware function\n *\n * @example\n * ```typescript\n * // In middleware.ts\n * import { csrfMiddleware } from '@cloudwerk/security/middleware'\n *\n * export const middleware = csrfMiddleware()\n * ```\n *\n * @example\n * ```typescript\n * // Exclude webhook paths\n * export const middleware = csrfMiddleware({\n * excludePaths: ['/api/webhooks/stripe', '/api/webhooks/github'],\n * })\n * ```\n */\nexport function csrfMiddleware(options: CSRFMiddlewareOptions = {}): Middleware {\n const {\n cookieName = DEFAULT_CSRF_COOKIE_NAME,\n headerName = DEFAULT_CSRF_HEADER_NAME,\n formFieldName = DEFAULT_CSRF_FORM_FIELD_NAME,\n methods = DEFAULT_CSRF_METHODS,\n excludePaths = [],\n } = options\n\n return async (request, next) => {\n // Skip if method doesn't require CSRF validation\n if (!methods.includes(request.method)) {\n return next()\n }\n\n // Skip excluded paths\n const url = new URL(request.url)\n if (excludePaths.some((path) => url.pathname.startsWith(path))) {\n return next()\n }\n\n // Get token from cookie\n const cookieToken = getCsrfTokenFromCookie(request, cookieName)\n if (!cookieToken) {\n return Response.json(\n { error: 'Missing CSRF token cookie' },\n { status: 403 }\n )\n }\n\n // Get token from header or form body\n let requestToken = getCsrfTokenFromHeader(request, headerName)\n\n // If not in header, check form body\n if (!requestToken) {\n requestToken = await getCsrfTokenFromFormBody(request, formFieldName)\n }\n\n if (!requestToken) {\n return Response.json(\n { error: 'Missing CSRF token in request' },\n { status: 403 }\n )\n }\n\n // Compare tokens using timing-safe comparison\n if (!verifyCsrfToken(cookieToken, requestToken)) {\n return Response.json(\n { error: 'Invalid CSRF token' },\n { status: 403 }\n )\n }\n\n return next()\n }\n}\n","/**\n * @cloudwerk/security - Security Headers Middleware\n *\n * Sets standard security headers on responses.\n */\n\nimport type { Middleware } from '@cloudwerk/core'\nimport type { SecurityHeadersOptions } from '../types.js'\n\n/**\n * Default security headers configuration.\n */\nconst DEFAULT_OPTIONS: Required<SecurityHeadersOptions> = {\n contentTypeOptions: 'nosniff',\n frameOptions: 'DENY',\n referrerPolicy: 'strict-origin-when-cross-origin',\n xssProtection: '0', // Disabled as modern browsers handle XSS\n permittedCrossDomainPolicies: 'none',\n dnsPrefetchControl: 'off',\n crossOriginOpenerPolicy: false,\n crossOriginEmbedderPolicy: false,\n crossOriginResourcePolicy: false,\n}\n\n/**\n * Create security headers middleware.\n *\n * Sets standard security headers on all responses:\n * - X-Content-Type-Options: nosniff\n * - X-Frame-Options: DENY\n * - Referrer-Policy: strict-origin-when-cross-origin\n * - X-XSS-Protection: 0 (disabled, modern browsers handle XSS)\n * - X-Permitted-Cross-Domain-Policies: none\n * - X-DNS-Prefetch-Control: off\n *\n * @param options - Header configuration options\n * @returns Middleware function\n *\n * @example\n * ```typescript\n * import { securityHeadersMiddleware } from '@cloudwerk/security/middleware'\n *\n * // Use defaults\n * export const middleware = securityHeadersMiddleware()\n *\n * // Custom configuration\n * export const middleware = securityHeadersMiddleware({\n * frameOptions: 'SAMEORIGIN',\n * crossOriginOpenerPolicy: 'same-origin',\n * })\n * ```\n */\nexport function securityHeadersMiddleware(\n options: SecurityHeadersOptions = {}\n): Middleware {\n const config = { ...DEFAULT_OPTIONS, ...options }\n\n return async (request, next) => {\n const response = await next()\n\n // Clone headers to modify\n const headers = new Headers(response.headers)\n\n // X-Content-Type-Options\n if (config.contentTypeOptions !== false) {\n headers.set('X-Content-Type-Options', config.contentTypeOptions)\n }\n\n // X-Frame-Options\n if (config.frameOptions !== false) {\n headers.set('X-Frame-Options', config.frameOptions)\n }\n\n // Referrer-Policy\n if (config.referrerPolicy !== false) {\n headers.set('Referrer-Policy', config.referrerPolicy)\n }\n\n // X-XSS-Protection\n if (config.xssProtection !== false) {\n headers.set('X-XSS-Protection', config.xssProtection)\n }\n\n // X-Permitted-Cross-Domain-Policies\n if (config.permittedCrossDomainPolicies !== false) {\n headers.set('X-Permitted-Cross-Domain-Policies', config.permittedCrossDomainPolicies)\n }\n\n // X-DNS-Prefetch-Control\n if (config.dnsPrefetchControl !== false) {\n headers.set('X-DNS-Prefetch-Control', config.dnsPrefetchControl)\n }\n\n // Cross-Origin-Opener-Policy\n if (config.crossOriginOpenerPolicy !== false) {\n headers.set('Cross-Origin-Opener-Policy', config.crossOriginOpenerPolicy)\n }\n\n // Cross-Origin-Embedder-Policy\n if (config.crossOriginEmbedderPolicy !== false) {\n headers.set('Cross-Origin-Embedder-Policy', config.crossOriginEmbedderPolicy)\n }\n\n // Cross-Origin-Resource-Policy\n if (config.crossOriginResourcePolicy !== false) {\n headers.set('Cross-Origin-Resource-Policy', config.crossOriginResourcePolicy)\n }\n\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers,\n })\n }\n}\n","/**\n * @cloudwerk/security - Content Security Policy Middleware\n *\n * Generates and sets Content-Security-Policy headers.\n */\n\nimport type { Middleware } from '@cloudwerk/core'\nimport type { CSPOptions, CSPDirectives, CSPDirectiveValue } from '../types.js'\n\n/**\n * Map of directive names to their CSP header names.\n */\nconst DIRECTIVE_MAP: Record<keyof CSPDirectives, string> = {\n defaultSrc: 'default-src',\n scriptSrc: 'script-src',\n styleSrc: 'style-src',\n imgSrc: 'img-src',\n fontSrc: 'font-src',\n connectSrc: 'connect-src',\n mediaSrc: 'media-src',\n objectSrc: 'object-src',\n prefetchSrc: 'prefetch-src',\n frameSrc: 'frame-src',\n workerSrc: 'worker-src',\n childSrc: 'child-src',\n frameAncestors: 'frame-ancestors',\n formAction: 'form-action',\n pluginTypes: 'plugin-types',\n baseUri: 'base-uri',\n sandbox: 'sandbox',\n requireSriFor: 'require-sri-for',\n reportUri: 'report-uri',\n reportTo: 'report-to',\n requireTrustedTypesFor: 'require-trusted-types-for',\n trustedTypes: 'trusted-types',\n upgradeInsecureRequests: 'upgrade-insecure-requests',\n blockAllMixedContent: 'block-all-mixed-content',\n manifestSrc: 'manifest-src',\n scriptSrcElem: 'script-src-elem',\n scriptSrcAttr: 'script-src-attr',\n styleSrcElem: 'style-src-elem',\n styleSrcAttr: 'style-src-attr',\n}\n\n/**\n * Generate a cryptographically secure nonce for CSP.\n *\n * @returns A base64-encoded random nonce\n */\nexport function generateNonce(): string {\n const bytes = new Uint8Array(16)\n crypto.getRandomValues(bytes)\n return btoa(String.fromCharCode(...bytes))\n}\n\n/**\n * Format a directive value for CSP header.\n */\nfunction formatDirectiveValue(value: CSPDirectiveValue): string {\n if (Array.isArray(value)) {\n return value.join(' ')\n }\n return value\n}\n\n/**\n * Generate a Content-Security-Policy header string from directives.\n *\n * @param directives - CSP directives object\n * @param nonce - Optional nonce to include in script-src and style-src\n * @returns CSP header string\n *\n * @example\n * ```typescript\n * const csp = generateCSPHeader({\n * defaultSrc: [\"'self'\"],\n * scriptSrc: [\"'self'\", 'https://cdn.example.com'],\n * styleSrc: [\"'self'\", \"'unsafe-inline'\"],\n * })\n * // \"default-src 'self'; script-src 'self' https://cdn.example.com; style-src 'self' 'unsafe-inline'\"\n * ```\n */\nexport function generateCSPHeader(\n directives: CSPDirectives,\n nonce?: string\n): string {\n const parts: string[] = []\n\n for (const [key, value] of Object.entries(directives)) {\n if (value === undefined || value === false) continue\n\n const directiveName = DIRECTIVE_MAP[key as keyof CSPDirectives]\n if (!directiveName) continue\n\n // Boolean directives (no value needed)\n if (value === true) {\n parts.push(directiveName)\n continue\n }\n\n // Add nonce to script-src and style-src if provided\n let formattedValue = formatDirectiveValue(value as CSPDirectiveValue)\n if (nonce && (key === 'scriptSrc' || key === 'styleSrc' ||\n key === 'scriptSrcElem' || key === 'styleSrcElem')) {\n formattedValue += ` 'nonce-${nonce}'`\n }\n\n parts.push(`${directiveName} ${formattedValue}`)\n }\n\n return parts.join('; ')\n}\n\n/**\n * Create CSP middleware.\n *\n * Generates and sets Content-Security-Policy (or Content-Security-Policy-Report-Only)\n * headers on responses.\n *\n * @param options - CSP configuration options\n * @returns Middleware function\n *\n * @example\n * ```typescript\n * import { cspMiddleware } from '@cloudwerk/security/middleware'\n *\n * export const middleware = cspMiddleware({\n * directives: {\n * defaultSrc: [\"'self'\"],\n * scriptSrc: [\"'self'\", 'https://cdn.example.com'],\n * styleSrc: [\"'self'\", \"'unsafe-inline'\"],\n * imgSrc: [\"'self'\", 'data:', 'https:'],\n * },\n * })\n * ```\n *\n * @example\n * ```typescript\n * // Report-only mode for testing\n * export const middleware = cspMiddleware({\n * directives: {\n * defaultSrc: [\"'self'\"],\n * reportUri: '/api/csp-report',\n * },\n * reportOnly: true,\n * })\n * ```\n *\n * @example\n * ```typescript\n * // With nonce generation for inline scripts\n * export const middleware = cspMiddleware({\n * directives: {\n * defaultSrc: [\"'self'\"],\n * scriptSrc: [\"'self'\"],\n * },\n * useNonce: true,\n * })\n *\n * // In your page, access the nonce via context:\n * // const nonce = context.get('cspNonce')\n * // <script nonce={nonce}>...</script>\n * ```\n */\nexport function cspMiddleware(options: CSPOptions = {}): Middleware {\n const {\n directives = {},\n reportOnly = false,\n useNonce = false,\n nonceContextKey = 'cspNonce',\n } = options\n\n const headerName = reportOnly\n ? 'Content-Security-Policy-Report-Only'\n : 'Content-Security-Policy'\n\n return async (request, next) => {\n // Generate nonce if requested\n let nonce: string | undefined\n if (useNonce) {\n nonce = generateNonce()\n // Note: In a real implementation, we'd use getContext().set() to store the nonce\n // For now, we'll store it in a custom header that pages can read\n }\n\n const response = await next()\n\n // Generate CSP header\n const cspValue = generateCSPHeader(directives, nonce)\n\n if (!cspValue) {\n return response\n }\n\n // Clone headers and add CSP\n const headers = new Headers(response.headers)\n headers.set(headerName, cspValue)\n\n // If using nonce, add it as a custom header for pages to read\n if (nonce) {\n headers.set('X-CSP-Nonce', nonce)\n }\n\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers,\n })\n }\n}\n","/**\n * @cloudwerk/security - Origin Validation Middleware\n *\n * Validates Origin and Referer headers to prevent cross-origin attacks.\n */\n\nimport type { Middleware } from '@cloudwerk/core'\nimport type { OriginValidationOptions } from '../types.js'\n\n/** Default methods requiring origin validation */\nconst DEFAULT_METHODS = ['POST', 'PUT', 'PATCH', 'DELETE']\n\n/**\n * Check if an origin matches an allowed host.\n *\n * @param originHost - The host from the Origin header\n * @param allowedHost - The allowed host to check against\n * @param allowSubdomains - Whether to allow subdomains\n * @returns True if the origin matches\n */\nfunction hostMatches(\n originHost: string,\n allowedHost: string,\n allowSubdomains: boolean\n): boolean {\n if (originHost === allowedHost) {\n return true\n }\n\n if (allowSubdomains && originHost.endsWith(`.${allowedHost}`)) {\n return true\n }\n\n return false\n}\n\n/**\n * Validate an origin against allowed origins and hosts.\n *\n * @param origin - The Origin header value\n * @param options - Validation options\n * @returns True if the origin is allowed\n */\nfunction isOriginAllowed(origin: string, options: OriginValidationOptions): boolean {\n const { allowedOrigins = [], allowedHosts = [], allowSubdomains = false } = options\n\n // Check against allowed origins (exact match)\n if (allowedOrigins.includes(origin)) {\n return true\n }\n\n // Parse origin to get host\n let originHost: string\n try {\n const url = new URL(origin)\n originHost = url.host\n } catch {\n return false\n }\n\n // Check against allowed hosts\n for (const allowedHost of allowedHosts) {\n if (hostMatches(originHost, allowedHost, allowSubdomains)) {\n return true\n }\n }\n\n return false\n}\n\n/**\n * Create origin validation middleware.\n *\n * Validates that mutation requests (POST, PUT, PATCH, DELETE) come from\n * allowed origins. This helps prevent CSRF attacks by rejecting requests\n * from unknown origins.\n *\n * @param options - Validation options\n * @returns Middleware function\n *\n * @example\n * ```typescript\n * import { originValidationMiddleware } from '@cloudwerk/security/middleware'\n *\n * export const middleware = originValidationMiddleware({\n * allowedOrigins: ['https://myapp.com', 'https://admin.myapp.com'],\n * })\n * ```\n *\n * @example\n * ```typescript\n * // Allow all subdomains of a host\n * export const middleware = originValidationMiddleware({\n * allowedHosts: ['myapp.com'],\n * allowSubdomains: true,\n * })\n * ```\n */\nexport function originValidationMiddleware(\n options: OriginValidationOptions = {}\n): Middleware {\n const {\n allowedOrigins = [],\n allowedHosts = [],\n methods = DEFAULT_METHODS,\n excludePaths = [],\n rejectMissingOrigin = true,\n } = options\n\n // If no origins or hosts are configured, skip validation\n const hasConfig = allowedOrigins.length > 0 || allowedHosts.length > 0\n\n return async (request, next) => {\n // Skip if no configuration\n if (!hasConfig) {\n return next()\n }\n\n // Skip if method doesn't require validation\n if (!methods.includes(request.method)) {\n return next()\n }\n\n // Skip excluded paths\n const url = new URL(request.url)\n if (excludePaths.some((path) => url.pathname.startsWith(path))) {\n return next()\n }\n\n // Get Origin header (preferred) or fall back to Referer\n let origin = request.headers.get('Origin')\n\n if (!origin) {\n const referer = request.headers.get('Referer')\n if (referer) {\n try {\n const refererUrl = new URL(referer)\n origin = refererUrl.origin\n } catch {\n // Invalid referer URL\n }\n }\n }\n\n // Check if origin is missing\n if (!origin) {\n if (rejectMissingOrigin) {\n return Response.json(\n { error: 'Missing Origin header' },\n { status: 403 }\n )\n }\n // Allow if configured to not reject missing origins\n return next()\n }\n\n // Validate origin\n if (!isOriginAllowed(origin, options)) {\n return Response.json(\n { error: 'Origin not allowed' },\n { status: 403 }\n )\n }\n\n return next()\n }\n}\n","/**\n * @cloudwerk/security - X-Requested-With Validation Middleware\n *\n * Requires X-Requested-With header to force CORS preflight for cross-origin requests.\n */\n\nimport type { Middleware } from '@cloudwerk/core'\nimport type { RequestedWithOptions } from '../types.js'\n\n/** Default methods requiring X-Requested-With validation */\nconst DEFAULT_METHODS = ['POST', 'PUT', 'PATCH', 'DELETE']\n\n/** Default required header value */\nconst DEFAULT_REQUIRED_VALUE = 'XMLHttpRequest'\n\n/**\n * Create X-Requested-With validation middleware.\n *\n * Requires mutation requests to include an `X-Requested-With` header.\n * This forces CORS preflight for cross-origin requests, providing an\n * additional layer of protection against CSRF attacks.\n *\n * The X-Requested-With header is a custom header that cannot be set\n * cross-origin without a CORS preflight. By requiring it, we ensure\n * that cross-origin requests must pass CORS checks.\n *\n * @param options - Validation options\n * @returns Middleware function\n *\n * @example\n * ```typescript\n * import { requestedWithMiddleware } from '@cloudwerk/security/middleware'\n *\n * // Default: requires X-Requested-With: XMLHttpRequest\n * export const middleware = requestedWithMiddleware()\n * ```\n *\n * @example\n * ```typescript\n * // Custom header value\n * export const middleware = requestedWithMiddleware({\n * requiredValue: 'fetch',\n * excludePaths: ['/api/webhooks'],\n * })\n * ```\n */\nexport function requestedWithMiddleware(\n options: RequestedWithOptions = {}\n): Middleware {\n const {\n requiredValue = DEFAULT_REQUIRED_VALUE,\n methods = DEFAULT_METHODS,\n excludePaths = [],\n } = options\n\n return async (request, next) => {\n // Skip if method doesn't require validation\n if (!methods.includes(request.method)) {\n return next()\n }\n\n // Skip excluded paths\n const url = new URL(request.url)\n if (excludePaths.some((path) => url.pathname.startsWith(path))) {\n return next()\n }\n\n // Check X-Requested-With header\n const headerValue = request.headers.get('X-Requested-With')\n\n if (!headerValue) {\n return Response.json(\n { error: 'Missing X-Requested-With header' },\n { status: 403 }\n )\n }\n\n if (headerValue !== requiredValue) {\n return Response.json(\n { error: 'Invalid X-Requested-With header' },\n { status: 403 }\n )\n }\n\n return next()\n }\n}\n","/**\n * @cloudwerk/security - Combined Security Middleware\n *\n * All-in-one middleware that combines CSRF, security headers, CSP,\n * origin validation, and X-Requested-With validation.\n */\n\nimport type { Middleware } from '@cloudwerk/core'\nimport type { SecurityMiddlewareOptions } from '../types.js'\nimport { csrfMiddleware } from '../csrf/middleware.js'\nimport { securityHeadersMiddleware } from '../headers/security-headers.js'\nimport { cspMiddleware } from '../headers/csp.js'\nimport { originValidationMiddleware } from '../origin/middleware.js'\nimport { requestedWithMiddleware } from '../request/middleware.js'\n\n/**\n * Compose multiple middleware functions into one.\n *\n * Executes middleware in order, passing the response through each.\n */\nfunction composeMiddleware(middlewares: Middleware[]): Middleware {\n return async (request, next) => {\n // Build the middleware chain in reverse order\n let handler: () => Promise<Response> = next\n\n for (let i = middlewares.length - 1; i >= 0; i--) {\n const middleware = middlewares[i]\n const prevHandler = handler\n handler = async () => middleware(request, prevHandler)\n }\n\n return handler()\n }\n}\n\n/**\n * Create a combined security middleware.\n *\n * This middleware composes multiple security protections with sensible defaults:\n * - **csrf**: Enabled by default - validates CSRF tokens on mutation requests\n * - **requestedWith**: Enabled by default - requires X-Requested-With header\n * - **headers**: Enabled by default - sets security headers (nosniff, DENY, etc.)\n * - **csp**: Disabled by default - requires app-specific configuration\n * - **origin**: Disabled by default - requires allowedOrigins configuration\n *\n * @param options - Configuration options for each protection\n * @returns Middleware function\n *\n * @example\n * ```typescript\n * import { securityMiddleware } from '@cloudwerk/security/middleware'\n *\n * // Use with all defaults\n * export const middleware = securityMiddleware()\n * ```\n *\n * @example\n * ```typescript\n * // Full configuration\n * export const middleware = securityMiddleware({\n * allowedOrigins: ['https://myapp.com'],\n * csrf: {\n * excludePaths: ['/api/webhooks/stripe'],\n * },\n * csp: {\n * directives: {\n * defaultSrc: [\"'self'\"],\n * scriptSrc: [\"'self'\", 'https://cdn.example.com'],\n * },\n * reportOnly: true,\n * },\n * headers: {\n * frameOptions: 'SAMEORIGIN',\n * },\n * })\n * ```\n *\n * @example\n * ```typescript\n * // Disable specific protections\n * export const middleware = securityMiddleware({\n * csrf: false, // Disable CSRF\n * requestedWith: false, // Disable X-Requested-With\n * })\n * ```\n */\nexport function securityMiddleware(\n options: SecurityMiddlewareOptions = {}\n): Middleware {\n const middlewares: Middleware[] = []\n\n // Add security headers middleware (enabled by default)\n if (options.headers !== false) {\n const headersOptions = typeof options.headers === 'object' ? options.headers : {}\n middlewares.push(securityHeadersMiddleware(headersOptions))\n }\n\n // Add CSP middleware (disabled by default - requires configuration)\n if (options.csp && typeof options.csp === 'object') {\n middlewares.push(cspMiddleware(options.csp))\n }\n\n // Add origin validation middleware (disabled by default unless allowedOrigins provided)\n if (options.origin !== false) {\n const originOptions = typeof options.origin === 'object'\n ? { ...options.origin }\n : { allowedOrigins: options.allowedOrigins }\n\n // Merge shorthand allowedOrigins into origin options\n if (options.allowedOrigins && !originOptions.allowedOrigins) {\n originOptions.allowedOrigins = options.allowedOrigins\n }\n\n // Only add if there are actual origins/hosts configured\n if (originOptions.allowedOrigins?.length || originOptions.allowedHosts?.length) {\n middlewares.push(originValidationMiddleware(originOptions))\n }\n }\n\n // Add X-Requested-With middleware (enabled by default)\n if (options.requestedWith !== false) {\n const requestedWithOptions = typeof options.requestedWith === 'object'\n ? options.requestedWith\n : {}\n middlewares.push(requestedWithMiddleware(requestedWithOptions))\n }\n\n // Add CSRF middleware (enabled by default)\n if (options.csrf !== false) {\n const csrfOptions = typeof options.csrf === 'object' ? options.csrf : {}\n middlewares.push(csrfMiddleware(csrfOptions))\n }\n\n // If no middleware is enabled, just pass through\n if (middlewares.length === 0) {\n return async (_request: Request, next: () => Promise<Response>) => next()\n }\n\n // Compose all middleware\n return composeMiddleware(middlewares)\n}\n"],"mappings":"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","names":["DEFAULT_METHODS"]}
|
|
1
|
+
{"version":3,"sources":["../src/utils/cookie.ts","../src/utils/timing-safe.ts","../src/csrf/token.ts","../src/csrf/middleware.ts","../src/headers/security-headers.ts","../src/headers/csp.ts","../src/origin/middleware.ts","../src/request/middleware.ts","../src/combined/security-middleware.ts"],"sourcesContent":["/**\n * @cloudwerk/security - Cookie Utilities\n *\n * Utilities for parsing and serializing cookies.\n */\n\nimport type { CookieAttributes } from '../types.js'\n\n/**\n * Parse a cookie header string into key-value pairs.\n *\n * @param cookieHeader - The Cookie header value\n * @returns Record of cookie names to values\n *\n * @example\n * ```typescript\n * const cookies = parseCookies('session=abc123; theme=dark')\n * // { session: 'abc123', theme: 'dark' }\n * ```\n */\nexport function parseCookies(cookieHeader: string): Record<string, string> {\n const cookies: Record<string, string> = {}\n\n if (!cookieHeader) {\n return cookies\n }\n\n const pairs = cookieHeader.split(';')\n for (const pair of pairs) {\n const [name, ...valueParts] = pair.split('=')\n const trimmedName = name?.trim()\n if (trimmedName) {\n // Rejoin in case value contains '='\n const value = valueParts.join('=').trim()\n // Handle quoted values\n cookies[trimmedName] = value.startsWith('\"') && value.endsWith('\"')\n ? value.slice(1, -1)\n : value\n }\n }\n\n return cookies\n}\n\n/**\n * Serialize a cookie with name, value, and attributes into a Set-Cookie header value.\n *\n * @param name - Cookie name\n * @param value - Cookie value\n * @param attributes - Cookie attributes\n * @returns Set-Cookie header value\n *\n * @example\n * ```typescript\n * const setCookie = serializeCookie('session', 'abc123', {\n * httpOnly: true,\n * secure: true,\n * sameSite: 'lax',\n * maxAge: 86400,\n * path: '/',\n * })\n * // \"session=abc123; HttpOnly; Secure; SameSite=Lax; Max-Age=86400; Path=/\"\n * ```\n */\nexport function serializeCookie(\n name: string,\n value: string,\n attributes: CookieAttributes = {}\n): string {\n const parts: string[] = [`${encodeURIComponent(name)}=${encodeURIComponent(value)}`]\n\n if (attributes.domain) {\n parts.push(`Domain=${attributes.domain}`)\n }\n\n if (attributes.path) {\n parts.push(`Path=${attributes.path}`)\n }\n\n if (attributes.expires) {\n parts.push(`Expires=${attributes.expires.toUTCString()}`)\n }\n\n if (attributes.maxAge !== undefined) {\n parts.push(`Max-Age=${attributes.maxAge}`)\n }\n\n if (attributes.httpOnly) {\n parts.push('HttpOnly')\n }\n\n if (attributes.secure) {\n parts.push('Secure')\n }\n\n if (attributes.sameSite) {\n const sameSiteValue = attributes.sameSite.charAt(0).toUpperCase() + attributes.sameSite.slice(1)\n parts.push(`SameSite=${sameSiteValue}`)\n }\n\n return parts.join('; ')\n}\n","/**\n * @cloudwerk/security - Timing-Safe Utilities\n *\n * Utilities for preventing timing attacks.\n */\n\n/**\n * Perform a timing-safe string comparison.\n *\n * This prevents timing attacks by comparing all characters regardless\n * of where differences occur.\n *\n * @param a - First string\n * @param b - Second string\n * @returns True if strings are equal\n */\nexport function timingSafeEqual(a: string, b: string): boolean {\n if (a.length !== b.length) {\n return false\n }\n\n // Use XOR to compare without early exit\n let result = 0\n for (let i = 0; i < a.length; i++) {\n result |= a.charCodeAt(i) ^ b.charCodeAt(i)\n }\n\n return result === 0\n}\n","/**\n * @cloudwerk/security - CSRF Token Utilities\n *\n * Functions for generating, setting, and verifying CSRF tokens.\n */\n\nimport type { SetCsrfCookieOptions } from '../types.js'\nimport { serializeCookie, parseCookies } from '../utils/cookie.js'\nimport { timingSafeEqual } from '../utils/timing-safe.js'\n\n// ============================================================================\n// Constants\n// ============================================================================\n\n/** Default CSRF cookie name */\nexport const DEFAULT_CSRF_COOKIE_NAME = 'cloudwerk.csrf-token'\n\n/** Default CSRF header name */\nexport const DEFAULT_CSRF_HEADER_NAME = 'X-CSRF-Token'\n\n/** Default CSRF form field name */\nexport const DEFAULT_CSRF_FORM_FIELD_NAME = 'csrf_token'\n\n/** CSRF token length in bytes (32 bytes = 256 bits) */\nconst CSRF_TOKEN_BYTES = 32\n\n/** Default max age for CSRF cookie (24 hours) */\nconst DEFAULT_CSRF_MAX_AGE = 24 * 60 * 60\n\n// ============================================================================\n// Token Generation\n// ============================================================================\n\n/**\n * Generate a cryptographically secure CSRF token.\n *\n * Uses Web Crypto API for secure random number generation.\n *\n * @returns A URL-safe base64-encoded random token\n *\n * @example\n * ```typescript\n * import { generateCsrfToken } from '@cloudwerk/security'\n *\n * const token = generateCsrfToken()\n * // 'Yx8nK2pQ...' (43 characters)\n * ```\n */\nexport function generateCsrfToken(): string {\n const bytes = new Uint8Array(CSRF_TOKEN_BYTES)\n crypto.getRandomValues(bytes)\n\n // Convert to URL-safe base64\n const base64 = btoa(String.fromCharCode(...bytes))\n return base64.replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '')\n}\n\n// ============================================================================\n// Cookie Helpers\n// ============================================================================\n\n/**\n * Set a CSRF cookie on a response.\n *\n * Creates a new response with the CSRF cookie set. The cookie is accessible\n * to JavaScript (not httpOnly) so that SPA frameworks can read it and include\n * it in request headers.\n *\n * @param response - The response to add the cookie to\n * @param token - The CSRF token to set (generate with generateCsrfToken())\n * @param options - Cookie configuration options\n * @returns A new response with the Set-Cookie header added\n *\n * @example\n * ```typescript\n * import { generateCsrfToken, setCsrfCookie } from '@cloudwerk/security'\n *\n * export function GET(request: Request) {\n * const token = generateCsrfToken()\n * const response = new Response(JSON.stringify({ csrfToken: token }))\n * return setCsrfCookie(response, token)\n * }\n * ```\n */\nexport function setCsrfCookie(\n response: Response,\n token: string,\n options: SetCsrfCookieOptions = {}\n): Response {\n const {\n cookieName = DEFAULT_CSRF_COOKIE_NAME,\n path = '/',\n httpOnly = false, // Must be false to allow JS access\n secure = true,\n sameSite = 'lax',\n maxAge = DEFAULT_CSRF_MAX_AGE,\n } = options\n\n const cookieValue = serializeCookie(cookieName, token, {\n path,\n httpOnly,\n secure,\n sameSite,\n maxAge,\n })\n\n // Clone response and append cookie\n const headers = new Headers(response.headers)\n headers.append('Set-Cookie', cookieValue)\n\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers,\n })\n}\n\n/**\n * Get the CSRF token from a request's cookie.\n *\n * @param request - The request to extract the token from\n * @param cookieName - The cookie name to look for\n * @returns The CSRF token or null if not found\n */\nexport function getCsrfTokenFromCookie(\n request: Request,\n cookieName: string = DEFAULT_CSRF_COOKIE_NAME\n): string | null {\n const cookieHeader = request.headers.get('Cookie')\n if (!cookieHeader) return null\n\n const cookies = parseCookies(cookieHeader)\n return cookies[cookieName] ?? null\n}\n\n/**\n * Get the CSRF token from a request's header.\n *\n * @param request - The request to extract the token from\n * @param headerName - The header name to look for\n * @returns The CSRF token or null if not found\n */\nexport function getCsrfTokenFromHeader(\n request: Request,\n headerName: string = DEFAULT_CSRF_HEADER_NAME\n): string | null {\n return request.headers.get(headerName)\n}\n\n/**\n * Get the CSRF token from a request's form body.\n *\n * @param request - The request to extract the token from (will be cloned)\n * @param fieldName - The form field name to look for\n * @returns The CSRF token or null if not found\n */\nexport async function getCsrfTokenFromFormBody(\n request: Request,\n fieldName: string = DEFAULT_CSRF_FORM_FIELD_NAME\n): Promise<string | null> {\n const contentType = request.headers.get('Content-Type') || ''\n\n // Only check form data for form submissions\n if (!contentType.includes('application/x-www-form-urlencoded') &&\n !contentType.includes('multipart/form-data')) {\n return null\n }\n\n try {\n // Clone request to avoid consuming the body\n const clonedRequest = request.clone()\n const formData = await clonedRequest.formData()\n const token = formData.get(fieldName)\n return typeof token === 'string' ? token : null\n } catch {\n return null\n }\n}\n\n// ============================================================================\n// Token Verification\n// ============================================================================\n\n/**\n * Verify a CSRF token against the token in the cookie.\n *\n * Uses timing-safe comparison to prevent timing attacks.\n *\n * @param cookieToken - The token from the cookie\n * @param requestToken - The token from the request (header or form body)\n * @returns True if tokens match\n *\n * @example\n * ```typescript\n * import { verifyCsrfToken, getCsrfTokenFromCookie, getCsrfTokenFromHeader } from '@cloudwerk/security'\n *\n * const cookieToken = getCsrfTokenFromCookie(request)\n * const headerToken = getCsrfTokenFromHeader(request)\n *\n * if (cookieToken && headerToken && verifyCsrfToken(cookieToken, headerToken)) {\n * // Token is valid\n * }\n * ```\n */\nexport function verifyCsrfToken(cookieToken: string, requestToken: string): boolean {\n return timingSafeEqual(cookieToken, requestToken)\n}\n\n// ============================================================================\n// Token Rotation\n// ============================================================================\n\n/**\n * Rotate the CSRF token on a response.\n *\n * Generates a new CSRF token and sets it as a cookie. This should be called\n * after successful authentication to bind the CSRF token to the new session\n * and prevent session fixation attacks.\n *\n * @param response - The response to add the new CSRF cookie to\n * @param options - Cookie configuration options\n * @returns A new response with the rotated CSRF token cookie\n *\n * @example\n * ```typescript\n * import { rotateCsrfToken } from '@cloudwerk/security'\n *\n * // After successful login\n * export async function handleLogin(request: Request) {\n * const user = await validateCredentials(request)\n * const session = await createSession(user)\n *\n * let response = createAuthResponse(user, session)\n * response = rotateCsrfToken(response)\n * return response\n * }\n * ```\n */\nexport function rotateCsrfToken(\n response: Response,\n options: SetCsrfCookieOptions = {}\n): Response {\n const newToken = generateCsrfToken()\n return setCsrfCookie(response, newToken, options)\n}\n","/**\n * @cloudwerk/security - CSRF Middleware\n *\n * Provides CSRF (Cross-Site Request Forgery) protection for mutation requests.\n * Uses the double-submit cookie pattern for stateless CSRF protection.\n */\n\nimport type { Middleware } from '@cloudwerk/core'\nimport type { CSRFMiddlewareOptions } from '../types.js'\nimport {\n DEFAULT_CSRF_COOKIE_NAME,\n DEFAULT_CSRF_HEADER_NAME,\n DEFAULT_CSRF_FORM_FIELD_NAME,\n generateCsrfToken,\n getCsrfTokenFromCookie,\n getCsrfTokenFromHeader,\n getCsrfTokenFromFormBody,\n verifyCsrfToken,\n setCsrfCookie,\n} from './token.js'\n\n/** Default methods requiring CSRF validation */\nconst DEFAULT_CSRF_METHODS = ['POST', 'PUT', 'PATCH', 'DELETE']\n\n/**\n * Create CSRF protection middleware.\n *\n * Validates that mutation requests (POST, PUT, PATCH, DELETE) include a valid\n * CSRF token that matches the token in the cookie. Uses the double-submit\n * cookie pattern for stateless CSRF protection.\n *\n * The token can be provided via:\n * 1. Request header (X-CSRF-Token by default) - for AJAX requests\n * 2. Form field (csrf_token by default) - for traditional form submissions\n *\n * @param options - Middleware configuration options\n * @returns Middleware function\n *\n * @example\n * ```typescript\n * // In middleware.ts\n * import { csrfMiddleware } from '@cloudwerk/security/middleware'\n *\n * export const middleware = csrfMiddleware()\n * ```\n *\n * @example\n * ```typescript\n * // Exclude webhook paths\n * export const middleware = csrfMiddleware({\n * excludePaths: ['/api/webhooks/stripe', '/api/webhooks/github'],\n * })\n * ```\n */\nexport function csrfMiddleware(options: CSRFMiddlewareOptions = {}): Middleware {\n const {\n cookieName = DEFAULT_CSRF_COOKIE_NAME,\n headerName = DEFAULT_CSRF_HEADER_NAME,\n formFieldName = DEFAULT_CSRF_FORM_FIELD_NAME,\n methods = DEFAULT_CSRF_METHODS,\n excludePaths = [],\n } = options\n\n return async (request, next) => {\n const existingToken = getCsrfTokenFromCookie(request, cookieName)\n const isMutationMethod = methods.includes(request.method)\n\n // For safe methods (GET, HEAD, OPTIONS), set the cookie if missing\n if (!isMutationMethod) {\n const response = await next()\n\n // If no CSRF cookie exists, set one on the response\n // This ensures users get a token on their first request\n if (!existingToken) {\n const newToken = generateCsrfToken()\n return setCsrfCookie(response, newToken, { cookieName })\n }\n\n return response\n }\n\n // For mutation methods, we need to validate the CSRF token\n\n // Skip excluded paths\n const url = new URL(request.url)\n if (excludePaths.some((path) => url.pathname.startsWith(path))) {\n return next()\n }\n\n // Reject if no CSRF cookie (client never received a token)\n if (!existingToken) {\n return Response.json(\n { error: 'Missing CSRF token cookie' },\n { status: 403 }\n )\n }\n\n // Get token from header first, then fall back to form body\n let requestToken = getCsrfTokenFromHeader(request, headerName)\n\n if (!requestToken) {\n requestToken = await getCsrfTokenFromFormBody(request, formFieldName)\n }\n\n if (!requestToken) {\n return Response.json(\n { error: 'Missing CSRF token in request' },\n { status: 403 }\n )\n }\n\n // Compare tokens using timing-safe comparison\n if (!verifyCsrfToken(existingToken, requestToken)) {\n return Response.json(\n { error: 'Invalid CSRF token' },\n { status: 403 }\n )\n }\n\n return next()\n }\n}\n","/**\n * @cloudwerk/security - Security Headers Middleware\n *\n * Sets standard security headers on responses.\n */\n\nimport type { Middleware } from '@cloudwerk/core'\nimport type { SecurityHeadersOptions } from '../types.js'\n\n/**\n * Default security headers configuration.\n */\nconst DEFAULT_OPTIONS: Required<SecurityHeadersOptions> = {\n contentTypeOptions: 'nosniff',\n frameOptions: 'DENY',\n referrerPolicy: 'strict-origin-when-cross-origin',\n xssProtection: '0', // Disabled as modern browsers handle XSS\n permittedCrossDomainPolicies: 'none',\n dnsPrefetchControl: 'off',\n crossOriginOpenerPolicy: false,\n crossOriginEmbedderPolicy: false,\n crossOriginResourcePolicy: false,\n}\n\n/**\n * Create security headers middleware.\n *\n * Sets standard security headers on all responses:\n * - X-Content-Type-Options: nosniff\n * - X-Frame-Options: DENY\n * - Referrer-Policy: strict-origin-when-cross-origin\n * - X-XSS-Protection: 0 (disabled, modern browsers handle XSS)\n * - X-Permitted-Cross-Domain-Policies: none\n * - X-DNS-Prefetch-Control: off\n *\n * @param options - Header configuration options\n * @returns Middleware function\n *\n * @example\n * ```typescript\n * import { securityHeadersMiddleware } from '@cloudwerk/security/middleware'\n *\n * // Use defaults\n * export const middleware = securityHeadersMiddleware()\n *\n * // Custom configuration\n * export const middleware = securityHeadersMiddleware({\n * frameOptions: 'SAMEORIGIN',\n * crossOriginOpenerPolicy: 'same-origin',\n * })\n * ```\n */\nexport function securityHeadersMiddleware(\n options: SecurityHeadersOptions = {}\n): Middleware {\n const config = { ...DEFAULT_OPTIONS, ...options }\n\n return async (request, next) => {\n const response = await next()\n\n // Clone headers to modify\n const headers = new Headers(response.headers)\n\n // X-Content-Type-Options\n if (config.contentTypeOptions !== false) {\n headers.set('X-Content-Type-Options', config.contentTypeOptions)\n }\n\n // X-Frame-Options\n if (config.frameOptions !== false) {\n headers.set('X-Frame-Options', config.frameOptions)\n }\n\n // Referrer-Policy\n if (config.referrerPolicy !== false) {\n headers.set('Referrer-Policy', config.referrerPolicy)\n }\n\n // X-XSS-Protection\n if (config.xssProtection !== false) {\n headers.set('X-XSS-Protection', config.xssProtection)\n }\n\n // X-Permitted-Cross-Domain-Policies\n if (config.permittedCrossDomainPolicies !== false) {\n headers.set('X-Permitted-Cross-Domain-Policies', config.permittedCrossDomainPolicies)\n }\n\n // X-DNS-Prefetch-Control\n if (config.dnsPrefetchControl !== false) {\n headers.set('X-DNS-Prefetch-Control', config.dnsPrefetchControl)\n }\n\n // Cross-Origin-Opener-Policy\n if (config.crossOriginOpenerPolicy !== false) {\n headers.set('Cross-Origin-Opener-Policy', config.crossOriginOpenerPolicy)\n }\n\n // Cross-Origin-Embedder-Policy\n if (config.crossOriginEmbedderPolicy !== false) {\n headers.set('Cross-Origin-Embedder-Policy', config.crossOriginEmbedderPolicy)\n }\n\n // Cross-Origin-Resource-Policy\n if (config.crossOriginResourcePolicy !== false) {\n headers.set('Cross-Origin-Resource-Policy', config.crossOriginResourcePolicy)\n }\n\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers,\n })\n }\n}\n","/**\n * @cloudwerk/security - Content Security Policy Middleware\n *\n * Generates and sets Content-Security-Policy headers.\n */\n\nimport type { Middleware } from '@cloudwerk/core'\nimport type { CSPOptions, CSPDirectives, CSPDirectiveValue } from '../types.js'\n\n/**\n * Map of directive names to their CSP header names.\n */\nconst DIRECTIVE_MAP: Record<keyof CSPDirectives, string> = {\n defaultSrc: 'default-src',\n scriptSrc: 'script-src',\n styleSrc: 'style-src',\n imgSrc: 'img-src',\n fontSrc: 'font-src',\n connectSrc: 'connect-src',\n mediaSrc: 'media-src',\n objectSrc: 'object-src',\n prefetchSrc: 'prefetch-src',\n frameSrc: 'frame-src',\n workerSrc: 'worker-src',\n childSrc: 'child-src',\n frameAncestors: 'frame-ancestors',\n formAction: 'form-action',\n pluginTypes: 'plugin-types',\n baseUri: 'base-uri',\n sandbox: 'sandbox',\n requireSriFor: 'require-sri-for',\n reportUri: 'report-uri',\n reportTo: 'report-to',\n requireTrustedTypesFor: 'require-trusted-types-for',\n trustedTypes: 'trusted-types',\n upgradeInsecureRequests: 'upgrade-insecure-requests',\n blockAllMixedContent: 'block-all-mixed-content',\n manifestSrc: 'manifest-src',\n scriptSrcElem: 'script-src-elem',\n scriptSrcAttr: 'script-src-attr',\n styleSrcElem: 'style-src-elem',\n styleSrcAttr: 'style-src-attr',\n}\n\n/**\n * Generate a cryptographically secure nonce for CSP.\n *\n * @returns A base64-encoded random nonce\n */\nexport function generateNonce(): string {\n const bytes = new Uint8Array(16)\n crypto.getRandomValues(bytes)\n return btoa(String.fromCharCode(...bytes))\n}\n\n/**\n * Format a directive value for CSP header.\n */\nfunction formatDirectiveValue(value: CSPDirectiveValue): string {\n if (Array.isArray(value)) {\n return value.join(' ')\n }\n return value\n}\n\n/**\n * Generate a Content-Security-Policy header string from directives.\n *\n * @param directives - CSP directives object\n * @param nonce - Optional nonce to include in script-src and style-src\n * @returns CSP header string\n *\n * @example\n * ```typescript\n * const csp = generateCSPHeader({\n * defaultSrc: [\"'self'\"],\n * scriptSrc: [\"'self'\", 'https://cdn.example.com'],\n * styleSrc: [\"'self'\", \"'unsafe-inline'\"],\n * })\n * // \"default-src 'self'; script-src 'self' https://cdn.example.com; style-src 'self' 'unsafe-inline'\"\n * ```\n */\nexport function generateCSPHeader(\n directives: CSPDirectives,\n nonce?: string\n): string {\n const parts: string[] = []\n\n for (const [key, value] of Object.entries(directives)) {\n if (value === undefined || value === false) continue\n\n const directiveName = DIRECTIVE_MAP[key as keyof CSPDirectives]\n if (!directiveName) continue\n\n // Boolean directives (no value needed)\n if (value === true) {\n parts.push(directiveName)\n continue\n }\n\n // Add nonce to script-src and style-src if provided\n let formattedValue = formatDirectiveValue(value as CSPDirectiveValue)\n if (nonce && (key === 'scriptSrc' || key === 'styleSrc' ||\n key === 'scriptSrcElem' || key === 'styleSrcElem')) {\n formattedValue += ` 'nonce-${nonce}'`\n }\n\n parts.push(`${directiveName} ${formattedValue}`)\n }\n\n return parts.join('; ')\n}\n\n/**\n * Create CSP middleware.\n *\n * Generates and sets Content-Security-Policy (or Content-Security-Policy-Report-Only)\n * headers on responses.\n *\n * @param options - CSP configuration options\n * @returns Middleware function\n *\n * @example\n * ```typescript\n * import { cspMiddleware } from '@cloudwerk/security/middleware'\n *\n * export const middleware = cspMiddleware({\n * directives: {\n * defaultSrc: [\"'self'\"],\n * scriptSrc: [\"'self'\", 'https://cdn.example.com'],\n * styleSrc: [\"'self'\", \"'unsafe-inline'\"],\n * imgSrc: [\"'self'\", 'data:', 'https:'],\n * },\n * })\n * ```\n *\n * @example\n * ```typescript\n * // Report-only mode for testing\n * export const middleware = cspMiddleware({\n * directives: {\n * defaultSrc: [\"'self'\"],\n * reportUri: '/api/csp-report',\n * },\n * reportOnly: true,\n * })\n * ```\n *\n * @example\n * ```typescript\n * // With nonce generation for inline scripts\n * export const middleware = cspMiddleware({\n * directives: {\n * defaultSrc: [\"'self'\"],\n * scriptSrc: [\"'self'\"],\n * },\n * useNonce: true,\n * })\n *\n * // In your page, access the nonce via context:\n * // const nonce = context.get('cspNonce')\n * // <script nonce={nonce}>...</script>\n * ```\n */\nexport function cspMiddleware(options: CSPOptions = {}): Middleware {\n const {\n directives = {},\n reportOnly = false,\n useNonce = false,\n // TODO: Use context to store nonce instead of custom header\n nonceContextKey: _nonceContextKey = 'cspNonce',\n } = options\n\n const headerName = reportOnly\n ? 'Content-Security-Policy-Report-Only'\n : 'Content-Security-Policy'\n\n return async (request, next) => {\n // Generate nonce if requested\n let nonce: string | undefined\n if (useNonce) {\n nonce = generateNonce()\n // Note: In a real implementation, we'd use getContext().set() to store the nonce\n // For now, we'll store it in a custom header that pages can read\n }\n\n const response = await next()\n\n // Generate CSP header\n const cspValue = generateCSPHeader(directives, nonce)\n\n if (!cspValue) {\n return response\n }\n\n // Clone headers and add CSP\n const headers = new Headers(response.headers)\n headers.set(headerName, cspValue)\n\n // If using nonce, add it as a custom header for pages to read\n if (nonce) {\n headers.set('X-CSP-Nonce', nonce)\n }\n\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers,\n })\n }\n}\n","/**\n * @cloudwerk/security - Origin Validation Middleware\n *\n * Validates Origin and Referer headers to prevent cross-origin attacks.\n */\n\nimport type { Middleware } from '@cloudwerk/core'\nimport type { OriginValidationOptions } from '../types.js'\n\n/** Default methods requiring origin validation */\nconst DEFAULT_METHODS = ['POST', 'PUT', 'PATCH', 'DELETE']\n\n/**\n * Check if an origin matches an allowed host.\n *\n * @param originHost - The host from the Origin header\n * @param allowedHost - The allowed host to check against\n * @param allowSubdomains - Whether to allow subdomains\n * @returns True if the origin matches\n */\nfunction hostMatches(\n originHost: string,\n allowedHost: string,\n allowSubdomains: boolean\n): boolean {\n if (originHost === allowedHost) {\n return true\n }\n\n if (allowSubdomains && originHost.endsWith(`.${allowedHost}`)) {\n return true\n }\n\n return false\n}\n\n/**\n * Validate an origin against allowed origins and hosts.\n *\n * @param origin - The Origin header value\n * @param options - Validation options\n * @returns True if the origin is allowed\n */\nfunction isOriginAllowed(origin: string, options: OriginValidationOptions): boolean {\n const { allowedOrigins = [], allowedHosts = [], allowSubdomains = false } = options\n\n // Check against allowed origins (exact match)\n if (allowedOrigins.includes(origin)) {\n return true\n }\n\n // Parse origin to get host\n let originHost: string\n try {\n const url = new URL(origin)\n originHost = url.host\n } catch {\n return false\n }\n\n // Check against allowed hosts\n for (const allowedHost of allowedHosts) {\n if (hostMatches(originHost, allowedHost, allowSubdomains)) {\n return true\n }\n }\n\n return false\n}\n\n/**\n * Create origin validation middleware.\n *\n * Validates that mutation requests (POST, PUT, PATCH, DELETE) come from\n * allowed origins. This helps prevent CSRF attacks by rejecting requests\n * from unknown origins.\n *\n * @param options - Validation options\n * @returns Middleware function\n *\n * @example\n * ```typescript\n * import { originValidationMiddleware } from '@cloudwerk/security/middleware'\n *\n * export const middleware = originValidationMiddleware({\n * allowedOrigins: ['https://myapp.com', 'https://admin.myapp.com'],\n * })\n * ```\n *\n * @example\n * ```typescript\n * // Allow all subdomains of a host\n * export const middleware = originValidationMiddleware({\n * allowedHosts: ['myapp.com'],\n * allowSubdomains: true,\n * })\n * ```\n */\nexport function originValidationMiddleware(\n options: OriginValidationOptions = {}\n): Middleware {\n const {\n allowedOrigins = [],\n allowedHosts = [],\n methods = DEFAULT_METHODS,\n excludePaths = [],\n rejectMissingOrigin = true,\n } = options\n\n // If no origins or hosts are configured, skip validation\n const hasConfig = allowedOrigins.length > 0 || allowedHosts.length > 0\n\n return async (request, next) => {\n // Skip if no configuration\n if (!hasConfig) {\n return next()\n }\n\n // Skip if method doesn't require validation\n if (!methods.includes(request.method)) {\n return next()\n }\n\n // Skip excluded paths\n const url = new URL(request.url)\n if (excludePaths.some((path) => url.pathname.startsWith(path))) {\n return next()\n }\n\n // Get Origin header (preferred) or fall back to Referer\n let origin = request.headers.get('Origin')\n\n if (!origin) {\n const referer = request.headers.get('Referer')\n if (referer) {\n try {\n const refererUrl = new URL(referer)\n origin = refererUrl.origin\n } catch {\n // Invalid referer URL\n }\n }\n }\n\n // Check if origin is missing\n if (!origin) {\n if (rejectMissingOrigin) {\n return Response.json(\n { error: 'Missing Origin header' },\n { status: 403 }\n )\n }\n // Allow if configured to not reject missing origins\n return next()\n }\n\n // Validate origin\n if (!isOriginAllowed(origin, options)) {\n return Response.json(\n { error: 'Origin not allowed' },\n { status: 403 }\n )\n }\n\n return next()\n }\n}\n","/**\n * @cloudwerk/security - X-Requested-With Validation Middleware\n *\n * Requires X-Requested-With header to force CORS preflight for cross-origin requests.\n */\n\nimport type { Middleware } from '@cloudwerk/core'\nimport type { RequestedWithOptions } from '../types.js'\n\n/** Default methods requiring X-Requested-With validation */\nconst DEFAULT_METHODS = ['POST', 'PUT', 'PATCH', 'DELETE']\n\n/** Default required header value */\nconst DEFAULT_REQUIRED_VALUE = 'XMLHttpRequest'\n\n/**\n * Create X-Requested-With validation middleware.\n *\n * Requires mutation requests to include an `X-Requested-With` header.\n * This forces CORS preflight for cross-origin requests, providing an\n * additional layer of protection against CSRF attacks.\n *\n * The X-Requested-With header is a custom header that cannot be set\n * cross-origin without a CORS preflight. By requiring it, we ensure\n * that cross-origin requests must pass CORS checks.\n *\n * @param options - Validation options\n * @returns Middleware function\n *\n * @example\n * ```typescript\n * import { requestedWithMiddleware } from '@cloudwerk/security/middleware'\n *\n * // Default: requires X-Requested-With: XMLHttpRequest\n * export const middleware = requestedWithMiddleware()\n * ```\n *\n * @example\n * ```typescript\n * // Custom header value\n * export const middleware = requestedWithMiddleware({\n * requiredValue: 'fetch',\n * excludePaths: ['/api/webhooks'],\n * })\n * ```\n */\nexport function requestedWithMiddleware(\n options: RequestedWithOptions = {}\n): Middleware {\n const {\n requiredValue = DEFAULT_REQUIRED_VALUE,\n methods = DEFAULT_METHODS,\n excludePaths = [],\n } = options\n\n return async (request, next) => {\n // Skip if method doesn't require validation\n if (!methods.includes(request.method)) {\n return next()\n }\n\n // Skip excluded paths\n const url = new URL(request.url)\n if (excludePaths.some((path) => url.pathname.startsWith(path))) {\n return next()\n }\n\n // Check X-Requested-With header\n const headerValue = request.headers.get('X-Requested-With')\n\n if (!headerValue) {\n return Response.json(\n { error: 'Missing X-Requested-With header' },\n { status: 403 }\n )\n }\n\n if (headerValue !== requiredValue) {\n return Response.json(\n { error: 'Invalid X-Requested-With header' },\n { status: 403 }\n )\n }\n\n return next()\n }\n}\n","/**\n * @cloudwerk/security - Combined Security Middleware\n *\n * All-in-one middleware that combines CSRF, security headers, CSP,\n * origin validation, and X-Requested-With validation.\n */\n\nimport type { Middleware } from '@cloudwerk/core'\nimport type { SecurityMiddlewareOptions } from '../types.js'\nimport { csrfMiddleware } from '../csrf/middleware.js'\nimport { securityHeadersMiddleware } from '../headers/security-headers.js'\nimport { cspMiddleware } from '../headers/csp.js'\nimport { originValidationMiddleware } from '../origin/middleware.js'\nimport { requestedWithMiddleware } from '../request/middleware.js'\n\n/**\n * Compose multiple middleware functions into one.\n *\n * Executes middleware in order, passing the response through each.\n *\n * @example\n * ```typescript\n * import { composeMiddleware } from '@cloudwerk/security'\n * import { securityMiddleware } from '@cloudwerk/security/middleware'\n * import { authMiddleware } from '@cloudwerk/auth/middleware'\n *\n * export const middleware = composeMiddleware([\n * securityMiddleware(),\n * authMiddleware(),\n * ])\n * ```\n */\nexport function composeMiddleware(middlewares: Middleware[]): Middleware {\n return async (request, next) => {\n // Build the middleware chain in reverse order\n let handler: () => Promise<Response> = next\n\n for (let i = middlewares.length - 1; i >= 0; i--) {\n const middleware = middlewares[i]\n const prevHandler = handler\n handler = async () => middleware(request, prevHandler)\n }\n\n return handler()\n }\n}\n\n/**\n * Create a combined security middleware.\n *\n * This middleware composes multiple security protections with sensible defaults:\n * - **csrf**: Enabled by default - validates CSRF tokens on mutation requests\n * - **requestedWith**: Enabled by default - requires X-Requested-With header\n * - **headers**: Enabled by default - sets security headers (nosniff, DENY, etc.)\n * - **csp**: Disabled by default - requires app-specific configuration\n * - **origin**: Disabled by default - requires allowedOrigins configuration\n *\n * @param options - Configuration options for each protection\n * @returns Middleware function\n *\n * @example\n * ```typescript\n * import { securityMiddleware } from '@cloudwerk/security/middleware'\n *\n * // Use with all defaults\n * export const middleware = securityMiddleware()\n * ```\n *\n * @example\n * ```typescript\n * // Full configuration\n * export const middleware = securityMiddleware({\n * allowedOrigins: ['https://myapp.com'],\n * csrf: {\n * excludePaths: ['/api/webhooks/stripe'],\n * },\n * csp: {\n * directives: {\n * defaultSrc: [\"'self'\"],\n * scriptSrc: [\"'self'\", 'https://cdn.example.com'],\n * },\n * reportOnly: true,\n * },\n * headers: {\n * frameOptions: 'SAMEORIGIN',\n * },\n * })\n * ```\n *\n * @example\n * ```typescript\n * // Disable specific protections\n * export const middleware = securityMiddleware({\n * csrf: false, // Disable CSRF\n * requestedWith: false, // Disable X-Requested-With\n * })\n * ```\n */\nexport function securityMiddleware(\n options: SecurityMiddlewareOptions = {}\n): Middleware {\n const middlewares: Middleware[] = []\n\n // Add security headers middleware (enabled by default)\n if (options.headers !== false) {\n const headersOptions = typeof options.headers === 'object' ? options.headers : {}\n middlewares.push(securityHeadersMiddleware(headersOptions))\n }\n\n // Add CSP middleware (disabled by default - requires configuration)\n if (options.csp && typeof options.csp === 'object') {\n middlewares.push(cspMiddleware(options.csp))\n }\n\n // Add origin validation middleware (disabled by default unless allowedOrigins provided)\n if (options.origin !== false) {\n const originOptions = typeof options.origin === 'object'\n ? { ...options.origin }\n : { allowedOrigins: options.allowedOrigins }\n\n // Merge shorthand allowedOrigins into origin options\n if (options.allowedOrigins && !originOptions.allowedOrigins) {\n originOptions.allowedOrigins = options.allowedOrigins\n }\n\n // Only add if there are actual origins/hosts configured\n if (originOptions.allowedOrigins?.length || originOptions.allowedHosts?.length) {\n middlewares.push(originValidationMiddleware(originOptions))\n }\n }\n\n // Add X-Requested-With middleware (enabled by default)\n if (options.requestedWith !== false) {\n const requestedWithOptions = typeof options.requestedWith === 'object'\n ? options.requestedWith\n : {}\n middlewares.push(requestedWithMiddleware(requestedWithOptions))\n }\n\n // Add CSRF middleware (enabled by default)\n if (options.csrf !== false) {\n const csrfOptions = typeof options.csrf === 'object' ? options.csrf : {}\n middlewares.push(csrfMiddleware(csrfOptions))\n }\n\n // If no middleware is enabled, just pass through\n if (middlewares.length === 0) {\n return async (_request: Request, next: () => Promise<Response>) => next()\n }\n\n // Compose all middleware\n return composeMiddleware(middlewares)\n}\n"],"mappings":"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","names":["DEFAULT_METHODS"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cloudwerk/security",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Security middleware for Cloudwerk - CSRF, CSP, security headers, origin validation",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"dist"
|
|
27
27
|
],
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@cloudwerk/core": "0.15.
|
|
29
|
+
"@cloudwerk/core": "0.15.3"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"typescript": "^5.4.0",
|