Skip to content

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

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

fix(android): add User-Agent interceptor to shared OkHttpClient#7427
mateussiqueira wants to merge 2 commits 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 so a default User-Agent is always included when a request doesn’t already set one.
    • Ensured VoIP networking uses the same header/request customization across both shared and fallback HTTP clients, preventing inconsistent request metadata.

@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: 5f357ea1-747e-443f-b600-6e22b1eeaf74

📥 Commits

Reviewing files that changed from the base of the PR and between cfa0874 and cc5cfd5.

📒 Files selected for processing (3)
  • android/app/src/main/java/chat/rocket/reactnative/networking/SSLPinningTurboModule.java
  • android/app/src/main/java/chat/rocket/reactnative/voip/DDPClient.kt
  • android/app/src/main/java/chat/rocket/reactnative/voip/MediaCallsAnswerRequest.kt
🚧 Files skipped from review as they are similar to previous changes (3)
  • android/app/src/main/java/chat/rocket/reactnative/voip/DDPClient.kt
  • android/app/src/main/java/chat/rocket/reactnative/networking/SSLPinningTurboModule.java
  • android/app/src/main/java/chat/rocket/reactnative/voip/MediaCallsAnswerRequest.kt

Walkthrough

Android OkHttp client creation now uses a shared builder that adds a default User-Agent header when one is missing. Two Android call sites now build their fallback clients from that builder path.

Changes

User-Agent defaulting for Android OkHttp

Layer / File(s) Summary
Interceptor and builder wiring
android/app/src/main/java/chat/rocket/reactnative/networking/SSLPinningTurboModule.java
Adds OkHttp request/interceptor imports and NotificationHelper, defines a static interceptor that preserves an existing User-Agent or injects one from NotificationHelper.getUserAgent(), and registers it through getOkHttpClientBuilder() for shared and fallback client creation.
Fallback client builders
android/app/src/main/java/chat/rocket/reactnative/voip/DDPClient.kt, android/app/src/main/java/chat/rocket/reactnative/voip/MediaCallsAnswerRequest.kt
Switches fallback OkHttp client construction to use SSLPinningTurboModule.getOkHttpClientBuilder() before building the client, so these requests inherit the shared User-Agent behavior.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

Suggested reviewers

  • OtavioStasiak

Suggested labels

type: bug

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding a default User-Agent interceptor to the shared Android OkHttp client.
Linked Issues check ✅ Passed The PR centralizes User-Agent injection in the shared OkHttp client and updates fallback builders, covering the affected native call paths in #7342.
Out of Scope Changes check ✅ Passed The changes stay within the linked issue scope and only extend the same OkHttp client fix to fallback client creation paths.

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.

@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.

mateussiqueira added a commit to mateussiqueira/Rocket.Chat.ReactNative that referenced this pull request Jun 26, 2026
- Convert userAgentInterceptor() method to static final constant
  USER_AGENT_INTERCEPTOR to avoid allocating new lambda per client init
- Add public getOkHttpClientBuilder() that returns a pre-configured
  builder with the interceptor, timeouts, and cookie jar
- Update getSharedOkHttpClient() and getOkHttpClient() to use the new
  builder method (DRY)
- Fix User-Agent leak when SSL Pinning is unconfigured: update fallbacks
  in MediaCallsAnswerRequest.kt and DDPClient.kt to use
  getOkHttpClientBuilder() instead of plain OkHttpClient()/Builder()

Previously, when alias was null (no SSL Pinning), getSharedOkHttpClient()
returned null and callers fell back to OkHttpClient() which bypassed the
interceptor entirely, leaking the default okhttp/Dalvik User-Agent.

Addresses review feedback from @Shevilll on PR RocketChat#7427
@mateussiqueira

Copy link
Copy Markdown
Author

Thanks for the thorough review, @Shevilll! Both points addressed in the latest push:

  1. Interceptor reuseuserAgentInterceptor() replaced with private static final Interceptor USER_AGENT_INTERCEPTOR. No more lambda allocation on each client init.

  2. Fallback leak when SSL Pinning is unconfigured — Added a new public static OkHttpClient.Builder getOkHttpClientBuilder() that returns a pre-configured builder with the interceptor, timeouts, and cookie jar. Updated the fallbacks in both MediaCallsAnswerRequest.kt and DDPClient.kt to use it instead of plain OkHttpClient() / OkHttpClient.Builder().

Now when alias is null, the fallback clients also carry the RC Mobile User-Agent. The shared builder config is DRY across getSharedOkHttpClient(), getOkHttpClient(), and the two external call sites.

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
- Convert userAgentInterceptor() method to static final constant
  USER_AGENT_INTERCEPTOR to avoid allocating new lambda per client init
- Add public getOkHttpClientBuilder() that returns a pre-configured
  builder with the interceptor, timeouts, and cookie jar
- Update getSharedOkHttpClient() and getOkHttpClient() to use the new
  builder method (DRY)
- Fix User-Agent leak when SSL Pinning is unconfigured: update fallbacks
  in MediaCallsAnswerRequest.kt and DDPClient.kt to use
  getOkHttpClientBuilder() instead of plain OkHttpClient()/Builder()

Previously, when alias was null (no SSL Pinning), getSharedOkHttpClient()
returned null and callers fell back to OkHttpClient() which bypassed the
interceptor entirely, leaking the default okhttp/Dalvik User-Agent.

Addresses review feedback from @Shevilll on PR RocketChat#7427
@mateussiqueira mateussiqueira force-pushed the fix/android-user-agent-interceptor branch from cfa0874 to cc5cfd5 Compare June 26, 2026 11:40
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