From bebd7b563e9958ccf6e58068574739462d67cffd Mon Sep 17 00:00:00 2001 From: Carlos Escalante Date: Tue, 9 Jun 2026 19:13:32 -0600 Subject: [PATCH] Add security headers and proxy error handling to Hono server Every response gets X-Frame-Options, X-Content-Type-Options, and Referrer-Policy (SEC-07/FE-08). Backend proxy failures now return a logged 502 instead of an opaque uncaught error (FE-23). Co-Authored-By: Claude Fable 5 --- frontend/server.ts | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/frontend/server.ts b/frontend/server.ts index d81e7b3..d0e2b00 100644 --- a/frontend/server.ts +++ b/frontend/server.ts @@ -324,6 +324,27 @@ class SuppressRenderToolTextMiddleware extends (Middleware as any) { const app = new Hono(); +// Security headers on every response. Responses returned by fetch() (the +// backend proxy) carry immutable headers, so rebuild the Response instead of +// mutating in place. +const SECURITY_HEADERS: Record = { + "X-Frame-Options": "DENY", + "X-Content-Type-Options": "nosniff", + "Referrer-Policy": "strict-origin-when-cross-origin", +}; + +app.use("*", async (c, next) => { + await next(); + const res = c.res; + const headers = new Headers(res.headers); + for (const [k, v] of Object.entries(SECURITY_HEADERS)) headers.set(k, v); + c.res = new Response(res.body, { + status: res.status, + statusText: res.statusText, + headers, + }); +}); + app.all("/api/copilotkit/*", async (c) => { const cookieHeader = c.req.header("cookie") ?? ""; const match = cookieHeader.match(/(?:^|;\s*)ws_token=([^;]+)/); @@ -398,7 +419,12 @@ const proxyToBackend = async (c: import("hono").Context) => { // @ts-expect-error undici requires duplex for streamed bodies init.duplex = "half"; } - return fetch(target, init); + try { + return await fetch(target, init); + } catch (err) { + console.error(`[proxy] ${method} ${url.pathname} → backend failed:`, err); + return c.json({ error: "Backend unavailable" }, 502); + } }; app.all("/api/v1/*", proxyToBackend);