Skip to content

fix(android): add User-Agent interceptor to shared OkHttpClient#7427

Open
mateussiqueira wants to merge 1 commit into
RocketChat:developfrom
mateussiqueira:fix/android-user-agent-interceptor
Open

fix(android): add User-Agent interceptor to shared OkHttpClient#7427
mateussiqueira wants to merge 1 commit into
RocketChat:developfrom
mateussiqueira:fix/android-user-agent-interceptor

Conversation

@mateussiqueira

@mateussiqueira mateussiqueira commented Jun 22, 2026

Copy link
Copy Markdown

Problem

Native HTTP calls on Android were leaking the default Dalvik/... User-Agent on call sites that didn't explicitly set one. This caused WAF rules and server logs to see okhttp/... instead of RC Mobile.

Confirmed sites missing the RC Mobile UA:

  • SSLPinningTurboModule.getSharedOkHttpClient() (used by ExpoImageClient, etc.)
  • MediaCallsAnswerRequest.kt (VoIP accept/decline) — inherited via shared client

Solution

Add a centralized userAgentInterceptor() to both getSharedOkHttpClient() and getOkHttpClient(). The interceptor:

  1. Checks if the request already has an explicit User-Agent header → passes through unchanged
  2. Otherwise adds RC Mobile; android {version}; v{appVersion} ({build})

Uses the existing NotificationHelper.getUserAgent() so the format stays consistent with the manual headers already set in LoadNotification.java, ReplyBroadcast.java, and NotificationHelper.

Fixes #7342

Summary by CodeRabbit

  • Bug Fixes
    • Improved HTTP request handling to ensure proper identification headers are consistently included in network communications.

Native HTTP calls on Android were leaking the default Dalvik User-Agent
on call sites that didn't explicitly set one. This caused WAF rules and
server logs to see 'okhttp/...' instead of 'RC Mobile'.

Add a centralized userAgentInterceptor() that adds 'RC Mobile; android ...'
to every outgoing request that doesn't already carry an explicit User-Agent.
Applied to both getSharedOkHttpClient() and getOkHttpClient().

Affected sites now covered automatically:
- SSLPinningTurboModule (ExpoImageClient, etc.)
- MediaCallsAnswerRequest (VoIP accept/decline)
- Any future call site using the shared client

Fixes RocketChat#7342
@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 67ee7d77-ba1e-433e-b45b-7c939cf0c494

📥 Commits

Reviewing files that changed from the base of the PR and between f019d9b and 1140def.

📒 Files selected for processing (1)
  • android/app/src/main/java/chat/rocket/reactnative/networking/SSLPinningTurboModule.java
📜 Recent review details
🔇 Additional comments (1)
android/app/src/main/java/chat/rocket/reactnative/networking/SSLPinningTurboModule.java (1)

27-28: LGTM!

Also applies to: 40-40, 49-68, 110-110


Walkthrough

A userAgentInterceptor() private static method is added to SSLPinningTurboModule. It checks whether an outgoing OkHttp request already carries a User-Agent header; if not, it sets one using NotificationHelper.getUserAgent(). The interceptor is registered on both the shared client builder and the fallback new-client builder path.

Changes

User-Agent Interceptor for Android OkHttp

Layer / File(s) Summary
Interceptor implementation and wiring
android/app/.../networking/SSLPinningTurboModule.java
Adds imports for OkHttp Request, Interceptor, and NotificationHelper. Defines a private static userAgentInterceptor() that preserves an existing User-Agent or injects one from NotificationHelper.getUserAgent(). Registers the interceptor on both the shared (getSharedOkHttpClient) and fallback (getOkHttpClient) builder paths.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~5 minutes

Suggested labels

type: bug

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title accurately describes the main change: adding a User-Agent interceptor to the shared OkHttpClient on Android.
Linked Issues check ✅ Passed The implementation adds the userAgentInterceptor to getSharedOkHttpClient and getOkHttpClient as specified in #7342, properly preserving explicit User-Agent headers while injecting the default RC Mobile header when absent.
Out of Scope Changes check ✅ Passed All changes in the pull request are directly related to implementing the User-Agent interceptor specified in #7342, with no unrelated modifications present.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@Shevilll

Copy link
Copy Markdown

Hi @mateussiqueira,

Thanks for addressing this and adding the missing User-Agent to the native OkHttp client on Android! This ensures that all native background network operations properly identify as the official mobile client, which is crucial for server logging and firewall/WAF rule evaluation.

Here is a technical recommendation to optimize memory usage and avoid object allocation churn:

Reuse the Interceptor Instance

Currently, userAgentInterceptor() is defined as a static method that returns a new anonymous lambda Interceptor on every single call:

private static Interceptor userAgentInterceptor() {
	return chain -> { ... };
}

Since the interceptor behavior is completely stateless and relies entirely on static utility methods (NotificationHelper.getUserAgent()), we can avoid creating a new lambda instance on every single client initialization by compiling it as a single static final constant:

private static final Interceptor USER_AGENT_INTERCEPTOR = chain -> {
	Request original = chain.request();
	if (original.header("User-Agent") != null) {
		return chain.proceed(original);
	}
	Request request = original.newBuilder()
			.header("User-Agent", NotificationHelper.getUserAgent())
			.build();
	return chain.proceed(request);
};

And then simply reuse it in both getSharedOkHttpClient() and getOkHttpClient():

OkHttpClient.Builder builder = new OkHttpClient.Builder()
		.addInterceptor(USER_AGENT_INTERCEPTOR)
		...

This prevents allocation slop during native networking module setups. Other than that, the integration is very clean and the use of the centralized NotificationHelper.getUserAgent() is excellent! 👍

@Shevilll Shevilll left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

User-Agent Interceptor Feedback: Android Network Call Sites

Hi @mateussiqueira, thank you for addressing the User-Agent leak on Android native network calls! Standardizing on the RC Mobile User-Agent is an excellent step for both logging clarity and WAF rule consistency.

I've analyzed the changes and found a subtle but critical edge case where the default Dalvik/... / okhttp/... user-agent will still be leaked.


⚠️ The Bug: User-Agent Leak/Bypass when SSL Pinning is Unconfigured

The PR adds userAgentInterceptor() to both getSharedOkHttpClient() and getOkHttpClient().

However, let's look at how getSharedOkHttpClient() is designed:

public static OkHttpClient getSharedOkHttpClient() {
    if (sharedClient != null) {
        return sharedClient;
    }
    if (alias != null) {
        OkHttpClient.Builder builder = new OkHttpClient.Builder()
                .addInterceptor(userAgentInterceptor())
                ...
        sharedClient = builder.build();
        return sharedClient;
    }
    return null;
}

If alias is null (which occurs when SSL Pinning is not configured or disabled on the current workspace), getSharedOkHttpClient() returns null.

Now, look at the call sites that consume this method on Android:

  1. MediaCallsAnswerRequest.kt (line 46):
    val base = SSLPinningTurboModule.getSharedOkHttpClient() ?: OkHttpClient()
  2. DDPClient.kt (line 23):
    SSLPinningTurboModule.getSharedOkHttpClient() ?: OkHttpClient.Builder()

The Concern:
When SSL Pinning is disabled (i.e. alias is null), getSharedOkHttpClient() returns null. Both of these call sites fall back to calling OkHttpClient() or OkHttpClient.Builder() directly.
Because these fallback clients are instantiated outside of SSLPinningTurboModule, they completely bypass the userAgentInterceptor(). Consequently, they will still leak the default okhttp or Dalvik User-Agent to the server, defeating the purpose of this PR on non-pinned workspaces.

Recommendation:
Expose a static helper method in SSLPinningTurboModule that returns a pre-configured OkHttpClient.Builder containing the userAgentInterceptor(), or ensure getSharedOkHttpClient() returns a default shared non-pinned client with the interceptor instead of null when alias is null.

For example, we could define:

public static OkHttpClient.Builder getOkHttpClientBuilder() {
    return new OkHttpClient.Builder()
            .addInterceptor(userAgentInterceptor())
            .connectTimeout(0, TimeUnit.MILLISECONDS)
            .readTimeout(0, TimeUnit.MILLISECONDS)
            .writeTimeout(0, TimeUnit.MILLISECONDS)
            .cookieJar(new ReactCookieJarContainer());
}

And then in MediaCallsAnswerRequest.kt / DDPClient.kt, fall back to that builder so that the User-Agent header (and timeouts/cookie configuration) is consistently set for all native network requests:

val base = SSLPinningTurboModule.getSharedOkHttpClient() ?: SSLPinningTurboModule.getOkHttpClientBuilder().build()

Thank you again for this contribution! This addition is highly valuable, and addressing this edge case will make it completely bulletproof across all server/client environments.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Android: add User-Agent interceptor to shared OkHttp client

3 participants