Skip to content

fix(responseInterceptor): remove disallowed headers when response headers contains trailer#830

Open
ziboilihua wants to merge 3 commits intochimurai:masterfrom
ziboilihua:master
Open

fix(responseInterceptor): remove disallowed headers when response headers contains trailer#830
ziboilihua wants to merge 3 commits intochimurai:masterfrom
ziboilihua:master

Conversation

@ziboilihua
Copy link
Copy Markdown

@ziboilihua ziboilihua commented Sep 22, 2022

Description

Process will exit when proxy response header both contains trailer and disallow headers eg.content-length

Motivation and Context

How has this been tested?

I've created e2e test for this case, and all unit tests passed.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)

Checklist:

  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.

Summary by CodeRabbit

  • Bug Fixes
    • Improved response header handling when HTTP Trailer headers are present: incompatible headers are now excluded and content-length is only set when no trailers are sent, preventing incorrect/ambiguous response headers.
  • Tests
    • Added an end-to-end test to verify proxied responses with trailers do not include disallowed headers.

res: http.ServerResponse
) => Promise<Buffer | string>;

const TrailerDisallowHeaders: string[] = [
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a link to verify the completeness of these headers?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

…ins trailer

Signed-off-by: zibo <ziboilihua@gmail.com>
Signed-off-by: zibo <ziboilihua@gmail.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 21, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4f5ddfda-f512-418d-af20-de40f69997fb

📥 Commits

Reviewing files that changed from the base of the PR and between 677e0fc and d852f09.

📒 Files selected for processing (1)
  • test/e2e/response-interceptor.spec.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/e2e/response-interceptor.spec.ts

📝 Walkthrough

Walkthrough

Updated response interceptor to omit certain headers when the upstream response includes a Trailer header and to avoid setting content-length in that case; added an E2E test verifying host is removed from proxied responses with trailers.

Changes

Cohort / File(s) Summary
Response Trailer Header Handling
src/handlers/response-interceptor.ts
Added TrailerDisallowHeaders constant listing headers that must not be present when Trailer is set. When proxyRes.headers.trailer is defined, skip setting content-length and remove each disallowed header from the outgoing response before writing/ending.
E2E Test Coverage
test/e2e/response-interceptor.spec.ts
Registered a mock endpoint returning trailer: X-Stream-Error and transfer-encoding: chunked; added test asserting the proxied response does not include the host header when Trailer is specified.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I nibble at headers in the stream so fleet,
When Trailer's declared, I hide what we meet,
No content-length, some names must depart,
The proxy hops lightly — a careful art. 🥕

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: removing disallowed headers when the response contains a trailer header, which directly matches the primary objective and code modifications.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@chimurai chimurai changed the title fix(http-proxy): remove disallowed headers when response headers contains trailer fix(responseInterceptor): remove disallowed headers when response headers contains trailer Apr 21, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/handlers/response-interceptor.ts`:
- Around line 69-76: The trailer branch currently strips headers via
TrailerDisallowHeaders but fails to update or remove the stale content-length
copied earlier (see copyHeaders) even though the interceptor mutates
interceptedBuffer; modify the branch guarded by proxyRes.headers.trailer to
explicitly remove or recalculate the content-length header on res (remove
content-length or set it to Buffer.byteLength(interceptedBuffer,'utf8')) and
ensure TrailerDisallowHeaders handling still runs; update tests to exercise an
upstream response that includes both Trailer and Content-Length so this branch
behavior is validated (refer to TrailerDisallowHeaders, copyHeaders,
interceptedBuffer, and proxyRes.headers.trailer).
- Around line 16-31: Remove the incorrect TrailerDisallowHeaders constant and
change the logic that strips headers when an upstream trailer is present so it
only removes the content-length header; specifically, delete
TrailerDisallowHeaders and update the code block that iterates/removes headers
(the section that currently references TrailerDisallowHeaders) to only delete
'content-length' from the response headers when trailers exist, leaving
content-type, set-cookie, cache-control, authorization, content-range, etc.
untouched.

In `@test/e2e/response-interceptor.spec.ts`:
- Line 69: Rename the test description string in the it(...) call in
response-interceptor.spec.ts to a grammatically correct, concise message:
replace "should not contains disallow headers to trailer in response headers
http://httpbin.org/response-headers" with something like "should not include
trailer-disallowed headers in response" (remove the URL and fix grammar); update
the it(...) title where it appears so test output and logs reflect the new
description.
- Around line 69-74: The test currently doesn't hit the trailer code path
because targetServer only mocks /json and /image; add a mock handler on
targetServer (e.g., targetServer.forGet('/response-headers') or '/with-trailer')
that replies 200 with headers "Trailer: X-Stream-Error" and "Content-Length: 1"
and a simple body, then call agent.get(...) against that path and assert the
proxied response completes with status 200 and a non-empty body; this exercises
the TrailerDisallowHeaders logic (refer to TrailerDisallowHeaders) and ensures
the proxy doesn't crash when upstream sends both Trailer and Content-Length —
update the test in response-interceptor.spec.ts to register the mock on
targetServer (or within the test) and replace the weak host header assertion
with status/body assertions using agent and targetServer.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: cf4d4004-7b3b-4c52-9fa7-0bdab8e3d4b8

📥 Commits

Reviewing files that changed from the base of the PR and between 3184776 and 677e0fc.

📒 Files selected for processing (2)
  • src/handlers/response-interceptor.ts
  • test/e2e/response-interceptor.spec.ts

Comment on lines +16 to +31
/**
* Disallow headers when response contains trailer
* source: https://developer.mozilla.org/docs/Web/HTTP/Headers/Trailer
*/
const TrailerDisallowHeaders: string[] = [
'content-length',
'host',
'content-type',
'authorization',
'cache-control',
'max-forwards',
'te',
'set-cookie',
'content-encoding',
'content-range',
];
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

MDN Trailer header disallowed headers meaning declared as trailer vs response headers

💡 Result:

The Trailer header indicates which HTTP fields (headers) the sender plans to include in the trailer section at the end of a chunked message body. It is sent in the header section (initial headers block) of the HTTP message. Disallowed headers in trailers: Certain headers cannot be used as trailer fields because they must be processed before the body (e.g., for routing, framing, authentication). MDN lists: Content-Encoding, Content-Type, Content-Range, Trailer; authentication headers (e.g., Authorization, Set-Cookie); message framing (Transfer-Encoding, Content-Length); routing (Host); request modifiers (Cache-Control, Max-Forwards, TE). This matches RFC 9110 §6.5.1 limitations on trailers. "Declared as trailer" means listed in the Trailer header (e.g., Trailer: Server-Timing), signaling those fields will appear in the trailers section, not the initial response headers. Trailer fields are processed/stored separately from header fields (RFC 9110 §6.5). They cannot override or duplicate critical headers needed pre-body. Example: HTTP/1.1 200 OK Transfer-Encoding: chunked Trailer: Server-Timing [body] Server-Timing: custom-metric;dur=123.4 Server-Timing is declared via Trailer header and appears only in trailers, not response headers.

Citations:


🏁 Script executed:

cat -n src/handlers/response-interceptor.ts | head -50

Repository: chimurai/http-proxy-middleware

Length of output: 1872


🏁 Script executed:

cat -n src/handlers/response-interceptor.ts | sed -n '40,150p'

Repository: chimurai/http-proxy-middleware

Length of output: 4550


The TrailerDisallowHeaders list is misapplied—these headers cannot be declared as trailers, not stripped from responses.

Per MDN and RFC 9110, the disallowed headers list specifies which header names cannot appear in the trailer section at the end of a chunked response. The current code incorrectly strips these headers from the response itself whenever an upstream trailer is present (lines 73–75).

This breaks legitimate responses: content-type, set-cookie, cache-control, authorization, and content-range are removed from the response headers, causing cookies to be lost, content-type to be missing, and auth to fail in downstream clients.

The actual issue is that content-length is incompatible with trailers (which require chunked transfer encoding). The fix should only remove content-length when a trailer is present, not the entire list of headers.

Suggested fix
-      // some headers are disallowed when response headers contains trailer
-      if (proxyRes.headers.trailer === undefined) {
-        res.setHeader('content-length', Buffer.byteLength(interceptedBuffer, 'utf8'));
-      } else {
-        TrailerDisallowHeaders.forEach((value) => {
-          res.removeHeader(value);
-        });
-      }
+      // content-length is incompatible with trailers (which require chunked
+      // transfer encoding), so only set it when no trailer is declared.
+      if (proxyRes.headers.trailer === undefined) {
+        res.setHeader('content-length', Buffer.byteLength(interceptedBuffer, 'utf8'));
+      } else {
+        res.removeHeader('content-length');
+      }

Remove the TrailerDisallowHeaders constant (lines 20–31) as it is no longer needed.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/handlers/response-interceptor.ts` around lines 16 - 31, Remove the
incorrect TrailerDisallowHeaders constant and change the logic that strips
headers when an upstream trailer is present so it only removes the
content-length header; specifically, delete TrailerDisallowHeaders and update
the code block that iterates/removes headers (the section that currently
references TrailerDisallowHeaders) to only delete 'content-length' from the
response headers when trailers exist, leaving content-type, set-cookie,
cache-control, authorization, content-range, etc. untouched.

Comment on lines +69 to +76
// some headers are disallowed when response headers contains trailer
if (proxyRes.headers.trailer === undefined) {
res.setHeader('content-length', Buffer.byteLength(interceptedBuffer, 'utf8'));
} else {
TrailerDisallowHeaders.forEach((value) => {
res.removeHeader(value);
});
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Also note: the interceptor mutates the response buffer, so the upstream content-length copied in copyHeaders is stale when a trailer is present.

In the trailer branch, neither content-length is updated nor removed under the narrower fix unless explicitly handled. If you keep the broader header-stripping approach, make sure this branch is exercised by a test that actually triggers a response carrying both Trailer and Content-Length from the upstream — the new E2E test (see test/e2e/response-interceptor.spec.ts) does not do this.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/handlers/response-interceptor.ts` around lines 69 - 76, The trailer
branch currently strips headers via TrailerDisallowHeaders but fails to update
or remove the stale content-length copied earlier (see copyHeaders) even though
the interceptor mutates interceptedBuffer; modify the branch guarded by
proxyRes.headers.trailer to explicitly remove or recalculate the content-length
header on res (remove content-length or set it to
Buffer.byteLength(interceptedBuffer,'utf8')) and ensure TrailerDisallowHeaders
handling still runs; update tests to exercise an upstream response that includes
both Trailer and Content-Length so this branch behavior is validated (refer to
TrailerDisallowHeaders, copyHeaders, interceptedBuffer, and
proxyRes.headers.trailer).

Comment thread test/e2e/response-interceptor.spec.ts Outdated
expect(response.body.favorite).toEqual('叉燒包');
});

it('should not contains disallow headers to trailer in response headers http://httpbin.org/response-headers', async () => {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Minor: test name grammar.

"should not contains disallow headers to trailer in response headers" → e.g., "should not include trailer-disallowed headers in response". Also remove the trailing http://httpbin.org/response-headers URL from the test name.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/e2e/response-interceptor.spec.ts` at line 69, Rename the test
description string in the it(...) call in response-interceptor.spec.ts to a
grammatically correct, concise message: replace "should not contains disallow
headers to trailer in response headers http://httpbin.org/response-headers" with
something like "should not include trailer-disallowed headers in response"
(remove the URL and fix grammar); update the it(...) title where it appears so
test output and logs reflect the new description.

Comment thread test/e2e/response-interceptor.spec.ts Outdated
Comment on lines +69 to +74
it('should not contains disallow headers to trailer in response headers http://httpbin.org/response-headers', async () => {
const response = await agent
.get('/response-headers?Trailer=X-Stream-Error&Host=localhost')
.expect(200);
expect(response.header['host']).toBeUndefined();
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

This test does not actually exercise the trailer code path.

Two problems:

  1. The targetServer configured in this beforeEach only mocks /json and /image (lines 26-32). A request to /response-headers?Trailer=...&Host=... will not match any mockttp handler and will not return 200 — the .expect(200) should fail. The path /response-headers looks borrowed from httpbin.org, but mockttp won't emulate it.
  2. Even if it returned 200, the assertion expect(response.header['host']).toBeUndefined() is non-discriminating — HTTP responses virtually never carry a Host header, so this passes regardless of whether the new TrailerDisallowHeaders logic runs.

To meaningfully cover the bug (process exit when upstream sends both Trailer and Content-Length), mock an upstream response that includes a trailer header and assert the proxied request completes successfully. For example:

Suggested test
-    it('should not contains disallow headers to trailer in response headers http://httpbin.org/response-headers', async () => {
-      const response = await agent
-        .get('/response-headers?Trailer=X-Stream-Error&Host=localhost')
-        .expect(200);
-      expect(response.header['host']).toBeUndefined();
-    });
+    it('should not crash when upstream response contains a Trailer header', async () => {
+      await targetServer.forGet('/with-trailer').thenReply(200, JSON.stringify({ ok: true }), {
+        'content-type': 'application/json; charset=utf-8',
+        trailer: 'X-Stream-Error',
+      });
+
+      const response = await agent.get('/with-trailer').expect(200);
+      expect(response.body.foo).toEqual('bar');
+    });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/e2e/response-interceptor.spec.ts` around lines 69 - 74, The test
currently doesn't hit the trailer code path because targetServer only mocks
/json and /image; add a mock handler on targetServer (e.g.,
targetServer.forGet('/response-headers') or '/with-trailer') that replies 200
with headers "Trailer: X-Stream-Error" and "Content-Length: 1" and a simple
body, then call agent.get(...) against that path and assert the proxied response
completes with status 200 and a non-empty body; this exercises the
TrailerDisallowHeaders logic (refer to TrailerDisallowHeaders) and ensures the
proxy doesn't crash when upstream sends both Trailer and Content-Length — update
the test in response-interceptor.spec.ts to register the mock on targetServer
(or within the test) and replace the weak host header assertion with status/body
assertions using agent and targetServer.

Copy link
Copy Markdown
Owner

@chimurai chimurai left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TrailerDisallowHeaders currently is used to remove these headers from the response.

MDN documentation says the headers defined TrailerDisallowHeaders can not be used as value in the Trailer field

Trailer: header-names

Can you please provide a reproduction of the problem you were trying solve?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants