fix(frontend): strip hop-by-hop headers from proxied responses too
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -7,7 +7,11 @@ import {
|
||||
afterEach,
|
||||
type MockInstance
|
||||
} from 'vitest';
|
||||
import { handle, shouldBypassProxyTimeout } from './hooks.server';
|
||||
import {
|
||||
handle,
|
||||
shouldBypassProxyTimeout,
|
||||
stripHopByHopHeaders
|
||||
} from './hooks.server';
|
||||
|
||||
// `BACKEND_URL` is read at module load time, so the values used in the
|
||||
// asserts below assume the test env didn't set it. `?? 'http://localhost:8080'`
|
||||
@@ -173,6 +177,30 @@ describe('hooks.server proxy', () => {
|
||||
expect(headers.get('x-custom')).toBe('pass-through');
|
||||
});
|
||||
|
||||
it('strips hop-by-hop headers from the proxied RESPONSE', async () => {
|
||||
// The upstream's connection-scoped headers must not ride along on
|
||||
// the re-streamed response (RFC 7230 §6.1). A normal header passes.
|
||||
fetchSpy.mockResolvedValueOnce(
|
||||
new Response('[]', {
|
||||
status: 200,
|
||||
headers: {
|
||||
connection: 'keep-alive',
|
||||
'keep-alive': 'timeout=5',
|
||||
'transfer-encoding': 'chunked',
|
||||
upgrade: 'h2c',
|
||||
'content-type': 'application/json'
|
||||
}
|
||||
})
|
||||
);
|
||||
const resolve = vi.fn();
|
||||
const resp = await handle({ event: makeEvent('/api/v1/health'), resolve });
|
||||
for (const h of ['connection', 'keep-alive', 'transfer-encoding', 'upgrade']) {
|
||||
expect(resp.headers.get(h), `${h} should be stripped from response`).toBeNull();
|
||||
}
|
||||
// A normal response header survives.
|
||||
expect(resp.headers.get('content-type')).toContain('application/json');
|
||||
});
|
||||
|
||||
it('aborts and returns 502 when the upstream stalls past the timeout', async () => {
|
||||
const errSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
||||
// Simulate an aborted fetch (AbortController.abort() raises a
|
||||
@@ -301,3 +329,25 @@ describe('shouldBypassProxyTimeout', () => {
|
||||
expect(shouldBypassProxyTimeout(new Headers())).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('stripHopByHopHeaders', () => {
|
||||
it('removes hop-by-hop headers and keeps the rest', () => {
|
||||
const src = new Headers({
|
||||
connection: 'keep-alive',
|
||||
'transfer-encoding': 'chunked',
|
||||
'content-type': 'application/json',
|
||||
'x-request-id': 'abc'
|
||||
});
|
||||
const out = stripHopByHopHeaders(src);
|
||||
expect(out.get('connection')).toBeNull();
|
||||
expect(out.get('transfer-encoding')).toBeNull();
|
||||
expect(out.get('content-type')).toBe('application/json');
|
||||
expect(out.get('x-request-id')).toBe('abc');
|
||||
});
|
||||
|
||||
it('does not mutate the source headers', () => {
|
||||
const src = new Headers({ connection: 'close' });
|
||||
stripHopByHopHeaders(src);
|
||||
expect(src.get('connection')).toBe('close');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user