security: add baseline security headers to gateway HTTP responses (#10526)

* security: add baseline security headers to gateway HTTP responses

All responses from the gateway HTTP server now include
X-Content-Type-Options: nosniff and Referrer-Policy: no-referrer.

These headers are applied early in handleRequest, before any
handler runs, ensuring coverage for every response including
error pages and 404s.

Headers that restrict framing (X-Frame-Options, CSP
frame-ancestors) are intentionally omitted at this global level
because the canvas host and A2UI handlers serve content that may
be loaded inside frames.

* fix: apply security headers before WebSocket upgrade check

Move setDefaultSecurityHeaders() above the WebSocket early-return so
the headers are set on every HTTP response path including upgrades.

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
This commit is contained in:
Abdel Fane
2026-02-19 03:28:24 -08:00
committed by GitHub
parent 57102cbec9
commit e955582c8f
2 changed files with 14 additions and 1 deletions

View File

@@ -2,6 +2,17 @@ import type { IncomingMessage, ServerResponse } from "node:http";
import type { GatewayAuthResult } from "./auth.js";
import { readJsonBody } from "./hooks.js";
/**
* Apply baseline security headers that are safe for all response types (API JSON,
* HTML pages, static assets, SSE streams). Headers that restrict framing or set a
* Content-Security-Policy are intentionally omitted here because some handlers
* (canvas host, A2UI) serve content that may be loaded inside frames.
*/
export function setDefaultSecurityHeaders(res: ServerResponse) {
res.setHeader("X-Content-Type-Options", "nosniff");
res.setHeader("Referrer-Policy", "no-referrer");
}
export function sendJson(res: ServerResponse, status: number, body: unknown) {
res.statusCode = status;
res.setHeader("Content-Type", "application/json; charset=utf-8");

View File

@@ -48,7 +48,7 @@ import {
resolveHookChannel,
resolveHookDeliver,
} from "./hooks.js";
import { sendGatewayAuthFailure } from "./http-common.js";
import { sendGatewayAuthFailure, setDefaultSecurityHeaders } from "./http-common.js";
import { getBearerToken, getHeader } from "./http-utils.js";
import { isPrivateOrLoopbackAddress, resolveGatewayClientIp } from "./net.js";
import { handleOpenAiHttpRequest } from "./openai-http.js";
@@ -474,6 +474,8 @@ export function createGatewayHttpServer(opts: {
});
async function handleRequest(req: IncomingMessage, res: ServerResponse) {
setDefaultSecurityHeaders(res);
// Don't interfere with WebSocket upgrades; ws handles the 'upgrade' event.
if (String(req.headers.upgrade ?? "").toLowerCase() === "websocket") {
return;