import type { Context, Next } from 'hono' const SAFE_METHODS = new Set(['GET', 'HEAD', 'OPTIONS']) /** * CSRF protection middleware. * * For state-changing methods (POST/PUT/DELETE/PATCH), validates the Origin * header against the request host and any explicitly allowed origins. * * Non-browser clients (curl, CLI, SDK) that don't send Origin are allowed * through — they rely on Bearer/API-key authentication instead. */ export async function csrfCheck(c: Context, next: Next) { if (SAFE_METHODS.has(c.req.method)) { await next() return } const origin = c.req.header('Origin') if (!origin) { // Non-browser clients (no Origin header) — pass through; // authentication is enforced by downstream middleware. await next() return } let host: string try { host = new URL(origin).origin } catch { return c.json({ error: 'CSRF check failed' }, 403) } // Build the allowed origins list: same-origin + ALLOWED_ORIGINS env const requestUrl = new URL(c.req.url) const selfOrigin = requestUrl.origin const allowed: string[] = [selfOrigin] // Support ALLOWED_ORIGINS env variable (comma-separated) const envOrigins = (c.env?.ALLOWED_ORIGINS as string | undefined) ?? (typeof process !== 'undefined' ? process.env.ALLOWED_ORIGINS : undefined) if (envOrigins) { for (const o of envOrigins.split(',')) { const trimmed = o.trim() if (trimmed) allowed.push(trimmed) } } if (!allowed.includes(host)) { return c.json({ error: 'CSRF check failed' }, 403) } await next() }