Skip to content

Commit 28807a1

Browse files
committed
fix(okhttp): strip query params from recorded URLs by default to prevent sensitive data leakage
1 parent ed91c57 commit 28807a1

4 files changed

Lines changed: 68 additions & 1 deletion

File tree

rollbar-okhttp/README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ dependencies {
3535
NetworkTelemetryRecorder recorder = new NetworkTelemetryRecorder() {
3636
@Override
3737
public void recordNetworkEvent(Level level, String method, String url, String statusCode) {
38+
// url has query parameters stripped by default (see Security section below)
3839
rollbar.recordNetworkEventFor(level, method, url, statusCode);
3940
}
4041

@@ -73,3 +74,19 @@ The interceptor will automatically record telemetry events to Rollbar without in
7374
| Response status `< 400` | No telemetry recorded, response returned normally |
7475
| Response status `>= 400` | Records a network telemetry event with `Level.CRITICAL` |
7576
| Connection failure / timeout | Records an error event, then rethrows the `IOException` |
77+
78+
## Security
79+
80+
URL query parameters often carry sensitive data such as API keys (`?api_key=...`), OAuth tokens (`?access_token=...`), or PII. To prevent accidental leakage to Rollbar, the interceptor **strips query parameters by default** before passing the URL to `NetworkTelemetryRecorder`.
81+
82+
For example, a request to `https://api.example.com/charge?token=sk_live_secret` will be recorded as `https://api.example.com/charge`.
83+
84+
If your URLs do not contain sensitive query parameters and you need them for debugging, you can opt in to the full URL by supplying a custom sanitizer:
85+
86+
```java
87+
OkHttpClient client = new OkHttpClient.Builder()
88+
.addInterceptor(new RollbarOkHttpInterceptor(recorder, HttpUrl::toString))
89+
.build();
90+
```
91+
92+
When using a custom sanitizer, you are responsible for ensuring that sensitive query parameters are removed before the URL reaches Rollbar.

rollbar-okhttp/src/main/java/com/rollbar/okhttp/NetworkTelemetryRecorder.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
import com.rollbar.api.payload.data.Level;
44

55
public interface NetworkTelemetryRecorder {
6+
/**
7+
* @param url the request URL with query parameters stripped by default; supply a custom
8+
* sanitizer to {@link RollbarOkHttpInterceptor} to change this behavior.
9+
*/
610
void recordNetworkEvent(Level level, String method, String url, String statusCode);
711

812
void recordErrorEvent(Exception exception);

rollbar-okhttp/src/main/java/com/rollbar/okhttp/RollbarOkHttpInterceptor.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@
33
import com.rollbar.api.payload.data.Level;
44

55
import java.io.IOException;
6+
import java.util.Objects;
7+
import java.util.function.Function;
68
import java.util.logging.Logger;
79

10+
import okhttp3.HttpUrl;
811
import okhttp3.Interceptor;
912
import okhttp3.Request;
1013
import okhttp3.Response;
@@ -13,10 +16,19 @@ public class RollbarOkHttpInterceptor implements Interceptor {
1316

1417
private static final Logger LOGGER = Logger.getLogger(RollbarOkHttpInterceptor.class.getName());
1518

19+
private static final Function<HttpUrl, String> DEFAULT_URL_SANITIZER =
20+
url -> url.newBuilder().query(null).build().toString();
21+
1622
private final NetworkTelemetryRecorder recorder;
23+
private final Function<HttpUrl, String> urlSanitizer;
1724

1825
public RollbarOkHttpInterceptor(NetworkTelemetryRecorder recorder) {
26+
this(recorder, DEFAULT_URL_SANITIZER);
27+
}
28+
29+
public RollbarOkHttpInterceptor(NetworkTelemetryRecorder recorder, Function<HttpUrl, String> urlSanitizer) {
1930
this.recorder = recorder;
31+
this.urlSanitizer = Objects.requireNonNull(urlSanitizer, "urlSanitizer must not be null");
2032
}
2133

2234
@Override
@@ -31,7 +43,7 @@ public Response intercept(Chain chain) throws IOException {
3143
recorder.recordNetworkEvent(
3244
Level.CRITICAL,
3345
request.method(),
34-
request.url().toString(),
46+
urlSanitizer.apply(request.url()),
3547
String.valueOf(response.code()));
3648
} catch (Exception recorderException) {
3749
LOGGER.log(java.util.logging.Level.WARNING,

rollbar-okhttp/src/test/java/com/rollbar/okhttp/RollbarOkHttpInterceptorTest.java

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import java.io.IOException;
1515

1616
import static org.junit.jupiter.api.Assertions.*;
17+
import static org.mockito.ArgumentMatchers.argThat;
1718
import static org.mockito.Mockito.*;
1819

1920
class RollbarOkHttpInterceptorTest {
@@ -174,4 +175,37 @@ void recorderThrowsOnConnectionFailure_originalIOExceptionPropagates() {
174175
assertThrows(IOException.class, () -> client.newCall(request).execute());
175176
verify(recorder).recordErrorEvent(any(IOException.class));
176177
}
178+
179+
@Test
180+
void defaultSanitizer_stripsQueryParamsFromUrl() throws IOException {
181+
server.enqueue(new MockResponse().setResponseCode(500));
182+
183+
Request request = new Request.Builder()
184+
.url(server.url("/sensitive?token=sk_live_secret&email=user@example.com"))
185+
.build();
186+
Response response = client.newCall(request).execute();
187+
response.close();
188+
189+
verify(recorder).recordNetworkEvent(
190+
eq(Level.CRITICAL), eq("GET"),
191+
argThat(url -> url.contains("/sensitive") && !url.contains("secret") && !url.contains("email")),
192+
eq("500"));
193+
}
194+
195+
@Test
196+
void customSanitizer_isAppliedToUrl() throws IOException {
197+
server.enqueue(new MockResponse().setResponseCode(500));
198+
199+
OkHttpClient customClient = new OkHttpClient.Builder()
200+
.addInterceptor(new RollbarOkHttpInterceptor(recorder, url -> "Updated String"))
201+
.build();
202+
203+
Request request = new Request.Builder()
204+
.url(server.url("/path?secret=abc"))
205+
.build();
206+
Response response = customClient.newCall(request).execute();
207+
response.close();
208+
209+
verify(recorder).recordNetworkEvent(eq(Level.CRITICAL), eq("GET"), eq("Updated String"), eq("500"));
210+
}
177211
}

0 commit comments

Comments
 (0)