Summary
Since jersey-mp-rest-client 3.1.11, headers returned by a registered InboundHeadersProvider can be copied automatically into an outbound request—even when no ClientHeadersFactory is registered.
The behavior is nondeterministic: different Rest Client instances in the same JVM can either propagate or omit the headers.
This may expose inbound Authorization, Cookie, forwarding, or gateway headers to a different destination.
Expected behavior
Inbound headers should only be available as the first argument to an explicitly configured ClientHeadersFactory. Without such a factory, they should not be propagated.
Headers added with RestClientBuilder.header(...) should remain separate from inbound headers.
Actual behavior
Jersey 3.1.11 stores the internal DefaultInboundHeaderProvider and application-provided InboundHeadersProvider instances in the same HashSet.
MethodModel.resolveCustomHeaders() accumulates provider results into one map. When DefaultInboundHeaderProvider is encountered, its update() method copies that entire accumulated map into outbound headers.
Therefore:
- Application provider first: inbound headers are copied outbound.
- Default provider first: only builder-provided headers are copied.
Reproducer
import java.net.URI;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.client.ClientRequestContext;
import jakarta.ws.rs.client.ClientRequestFilter;
import jakarta.ws.rs.core.Response;
import org.eclipse.microprofile.rest.client.RestClientBuilder;
import org.glassfish.jersey.microprofile.restclient.InboundHeadersProvider;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
class InboundHeadersProviderTest {
private static final String HEADER = "X-Inbound-Only";
private static final String RESULT = "X-Test-Leaked";
@Test
void inboundHeadersMustNotBeCopiedAutomatically() throws Exception {
Set<Boolean> observed = new HashSet<>();
for (int i = 0; i < 100; i++) {
TestClient client = RestClientBuilder.newBuilder()
.baseUri(URI.create("http://example.invalid"))
.register(new TestInboundHeadersProvider())
.register(new InspectingFilter())
.build(TestClient.class);
try {
try (Response response = client.invoke()) {
observed.add(Boolean.parseBoolean(
response.getHeaderString(RESULT)));
}
} finally {
((AutoCloseable) client).close();
}
}
assertEquals(Set.of(false), observed,
() -> "observed=" + observed);
}
@Path("/")
interface TestClient {
@GET
Response invoke();
}
static final class TestInboundHeadersProvider
implements InboundHeadersProvider {
@Override
public Map<String, List<String>> inboundHeaders() {
return Map.of(HEADER, List.of("must-not-be-sent"));
}
@Override
public int hashCode() {
return 0;
}
}
static final class InspectingFilter implements ClientRequestFilter {
@Override
public void filter(ClientRequestContext context) {
boolean leaked = context.getHeaders().containsKey(HEADER);
context.abortWith(Response.ok()
.header(RESULT, leaked)
.build());
}
}
}
Summary
Since jersey-mp-rest-client 3.1.11, headers returned by a registered InboundHeadersProvider can be copied automatically into an outbound request—even when no ClientHeadersFactory is registered.
The behavior is nondeterministic: different Rest Client instances in the same JVM can either propagate or omit the headers.
This may expose inbound Authorization, Cookie, forwarding, or gateway headers to a different destination.
Expected behavior
Inbound headers should only be available as the first argument to an explicitly configured ClientHeadersFactory. Without such a factory, they should not be propagated.
Headers added with
RestClientBuilder.header(...)should remain separate from inbound headers.Actual behavior
Jersey 3.1.11 stores the internal DefaultInboundHeaderProvider and application-provided InboundHeadersProvider instances in the same HashSet.
MethodModel.resolveCustomHeaders() accumulates provider results into one map. When DefaultInboundHeaderProvider is encountered, its update() method copies that entire accumulated map into outbound headers.
Therefore:
Reproducer