mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-06 23:49:41 +02:00
fix(http): raw writeHead routes lost every header the security hook set
`reply.raw.writeHead()` writes straight to the Node response and bypasses Fastify's header store, so everything the `onRequest` security hook granted is silently dropped on every route that answers that way. The visible symptom is CORS. The hook emits `Access-Control-Allow-Origin` for localhost origins, so a page served from a local dev server may call every `/api` endpoint cross-origin — except the four below, whose requests fail. The security headers (`X-Content-Type-Options`, `X-Frame-Options`, CSP) were being lost the same way. Affected: `GET /api/events`, and `file-raw` / `tail-file` / `download` in file-routes.ts. Each now spreads the inherited headers first and lets its own headers win over them. Tests drive a real WebServer and compare `/api/events` against `/api/status` for the same Origin — the point of the fix being that the SSE route stops being the odd one out. Verified in both directions: with the fix removed, 3 of the 5 fail. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -805,7 +805,24 @@ export class WebServer extends EventEmitter {
|
||||
const clientId =
|
||||
typeof query.clientId === 'string' && SSE_CLIENT_ID_RE.test(query.clientId) ? query.clientId : undefined;
|
||||
|
||||
// Carry over the headers the security hook already set on this reply.
|
||||
//
|
||||
// writeHead goes straight to the Node response and bypasses Fastify's header
|
||||
// store, so everything the onRequest hook granted is silently dropped —
|
||||
// including the Access-Control-Allow-Origin it emits for localhost origins.
|
||||
// The result is an internal contradiction: a localhost page may call every
|
||||
// /api endpoint cross-origin, but its EventSource fails CORS. The security
|
||||
// headers (nosniff, frame-options, CSP) were lost the same way.
|
||||
//
|
||||
// The other raw-writeHead routes live in file-routes.ts and share a helper;
|
||||
// this one keeps its own copy so the server does not import from a route
|
||||
// module it registers.
|
||||
const inherited: Record<string, number | string | string[]> = {};
|
||||
for (const [name, value] of Object.entries(reply.getHeaders())) {
|
||||
if (value !== undefined) inherited[name] = value;
|
||||
}
|
||||
reply.raw.writeHead(200, {
|
||||
...inherited,
|
||||
'Content-Type': 'text/event-stream',
|
||||
'Cache-Control': 'no-cache',
|
||||
Connection: 'keep-alive',
|
||||
|
||||
Reference in New Issue
Block a user