eclipse-ee4j / eclipse-ee4j/jersey

jersey-mp-rest-client 3.1.11 nondeterministically copies inbound provider headers into outbound requests

Open
#6,101 1 comment 0 reactions 0 assignees View on GitHub
bug
Dominant language
Java
Stars
730
Forks
382
PR merge metrics
No merged PRs in 30d

Description

## 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

```java
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 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> 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());
}
}
}
```

Contributor guide

Open the contributing guide

Research direction

Start at MethodModel.resolveCustomHeaders() and DefaultInboundHeaderProvider.update(), using the supplied InboundHeadersProviderTest reproducer to trace how provider results reach outbound headers. Add regression coverage for multiple provider-registration orders and verify that inbound-only headers are never copied without a ClientHeadersFactory, while builder-provided headers remain separate.

Written by the indexing model from the issue text.

Assessment

Tech stack
java
Domain
api, security
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.