spec icon indicating copy to clipboard operation
spec copied to clipboard

does `proxy_add_header_map_value` host call add duplicate entries?

Open conduition opened this issue 6 months ago • 3 comments

Hi :wave: I'm a security researcher working on auditing a project which uses a proxy-wasm plugin with Envoy to read and modify HTTP requests' headers before they reach an actual API endpoint. I hoped if you could help me answer a question about the proxy_add_header_map_value host call.

Let's say the proxy receives an HTTP request containing a header X-My-Header: somevalue. Inside the proxy-wasm guest, i invoke the following host call inside the proxy_on_request_headers callback.

proxy_add_header_map_value(HEADERS, key_data = "X-My-Header"`, value_data = "differentvalue")

Which header value(s) does the downstream destination server receive?

If both header values are forwarded, which comes first? (if defined)

conduition avatar Jun 18 '25 19:06 conduition

Hello, i tested this with the rust sdk and the results are like this.

set_http_request_header overwrites your initial header, add_http_request_header will append it to the initial value, separated by a comma.

➜ curl http://localhost:10000/headers -H "X-My-Header: initial value"
{
  "headers": {
    "Accept": "*/*",
    "Host": "localhost:10000",
    "User-Agent": "curl/8.7.1",
    "X-Envoy-Expected-Rq-Timeout-Ms": "15000",
    "X-My-Header": "different-value"
  }
}
➜ curl http://localhost:10000/headers -H "X-My-Header: initial value"
{
  "headers": {
    "Accept": "*/*",
    "Host": "localhost:10000",
    "User-Agent": "curl/8.7.1",
    "X-Envoy-Expected-Rq-Timeout-Ms": "15000",
    "X-My-Header": "initial value,different-value"
  }
}

Code

antonengelhardt avatar Jun 19 '25 08:06 antonengelhardt

Interesting. Is that what the proxy does at the HTTP protocol level as well? If so that would mean the proxy transforms the header

X-My-Header: initial value

to

X-My-Header: initial value,different-value

It's not clear from your example whether this is the case, or if the header is duplicated at the protocol level, e.g.

X-My-Header: initial value
X-My-Header: different-value

... your HTTP server might simply be parsing and merging the header values with comma-separation.

conduition avatar Jun 19 '25 14:06 conduition

I have a request-logger project, that i built with actix, and it simply logs requests for example to debug webhooks etc. Using this, the previous results are identical, when using the same request (curl http://localhost:10000/headers -H "X-My-Header: initial value).

When using add_http_request_header:

requestlogger-1  | [2025-06-19T21:52:38Z INFO  request_logger] Headers: HeaderMap { inner: {"accept": Value { inner: ["*/*"] }, "host": Value { inner: ["localhost:10000"] }, "x-request-id": Value { inner: ["5a66ff70-d0de-4810-9cc2-32c2df0fae59"] }, "user-agent": Value { inner: ["curl/8.7.1"] }, "x-my-header": Value { inner: ["initial value", "different-value"] }, "x-forwarded-proto": Value { inner: ["http"] }, "x-envoy-expected-rq-timeout-ms": Value { inner: ["15000"] }} }

When using set_http_request_header, the header is overwritten:

requestlogger-1  | [2025-06-19T21:53:48Z INFO  request_logger] Headers: HeaderMap { inner: {"x-envoy-expected-rq-timeout-ms": Value { inner: ["15000"] }, "x-my-header": Value { inner: ["different-value"] }, "x-request-id": Value { inner: ["e5749ffb-8503-400d-a06b-810e37ab80de"] }, "x-forwarded-proto": Value { inner: ["http"] }, "host": Value { inner: ["localhost:10000"] }, "accept": Value { inner: ["*/*"] }, "user-agent": Value { inner: ["curl/8.7.1"] }} }

antonengelhardt avatar Jun 19 '25 21:06 antonengelhardt

I think this answers my question. Judging from this header line in your printout:

"x-my-header": Value { inner: ["initial value", "different-value"] },

...it strongly suggests that at the protocol level, the proxy is adding a second duplicate header, rather than modifying the original. E.g.

X-My-Header: initial value
X-My-Header: different-value

Thank you! 😄

conduition avatar Jun 20 '25 16:06 conduition

Just as a note,

  • I'm not an official maintainer of proxy-wasm,
  • i tested this with the rust sdk and two debugging backends,
  • it might be that other proxy-wasm sdks have a different behavior,
  • it might also be that my envoy version (1.31) is causing this and newer versions have a different behavior

antonengelhardt avatar Jun 20 '25 20:06 antonengelhardt