does `proxy_add_header_map_value` host call add duplicate entries?
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)
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"
}
}
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.
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"] }} }
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! 😄
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