fix(server): strip compression headers in generic OpenCode proxy (#795)

Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
This commit is contained in:
jwcrystal
2026-04-01 09:45:19 +03:00
committed by GitHub
co-authored by Bohdan Triapitsyn
parent 060b37777c
commit 58d7713581
2 changed files with 114 additions and 0 deletions
+61
View File
@@ -0,0 +1,61 @@
const filteredRequestHeaders = new Set([
'host',
'connection',
'content-length',
'transfer-encoding',
'keep-alive',
'te',
'trailer',
'upgrade',
'accept-encoding',
]);
const filteredResponseHeaders = new Set([
'connection',
'content-length',
'transfer-encoding',
'keep-alive',
'te',
'trailer',
'upgrade',
'www-authenticate',
'content-encoding',
]);
export const collectForwardProxyHeaders = (requestHeaders, authHeaders = {}) => {
const headers = {};
for (const [key, value] of Object.entries(requestHeaders || {})) {
if (!value) continue;
const normalizedKey = key.toLowerCase();
if (filteredRequestHeaders.has(normalizedKey)) continue;
headers[normalizedKey] = Array.isArray(value) ? value.join(', ') : String(value);
}
if (authHeaders.Authorization) {
headers.Authorization = authHeaders.Authorization;
}
return headers;
};
export const shouldForwardProxyResponseHeader = (key) => {
if (typeof key !== 'string' || key.trim().length === 0) {
return false;
}
return !filteredResponseHeaders.has(key.toLowerCase());
};
export const applyForwardProxyResponseHeaders = (responseHeaders, response) => {
if (!responseHeaders || typeof response?.setHeader !== 'function') {
return;
}
for (const [key, value] of responseHeaders.entries()) {
if (!shouldForwardProxyResponseHeader(key)) {
continue;
}
response.setHeader(key, value);
}
};
+53
View File
@@ -0,0 +1,53 @@
import { describe, expect, it } from 'bun:test';
import {
applyForwardProxyResponseHeaders,
collectForwardProxyHeaders,
shouldForwardProxyResponseHeader,
} from './proxy-headers.js';
describe('OpenCode proxy header handling', () => {
it('drops accept-encoding from forwarded request headers', () => {
const headers = collectForwardProxyHeaders({
accept: 'application/json',
'accept-encoding': 'gzip, deflate, br',
connection: 'keep-alive',
});
expect(headers.accept).toBe('application/json');
expect(headers['accept-encoding']).toBeUndefined();
});
it('drops content-encoding from forwarded response headers', () => {
expect(shouldForwardProxyResponseHeader('content-encoding')).toBe(false);
expect(shouldForwardProxyResponseHeader('Content-Encoding')).toBe(false);
});
it('still keeps ordinary response headers', () => {
expect(shouldForwardProxyResponseHeader('content-type')).toBe(true);
expect(shouldForwardProxyResponseHeader('etag')).toBe(true);
});
it('applies upstream response headers to express response without content-encoding', () => {
const applied = [];
const response = {
setHeader(key, value) {
applied.push([key, value]);
},
};
applyForwardProxyResponseHeaders(
new Headers({
'content-type': 'application/json',
etag: 'W/"abc"',
'content-encoding': 'gzip',
}),
response,
);
expect(applied).toEqual([
['content-type', 'application/json'],
['etag', 'W/"abc"'],
]);
});
});