Repository navigation
fix(mobile): trust user-installed Android CA certificates - #25730
Conversation
Android apps targeting API 24+ ignore CAs in the user store, so an environment behind a private CA that Chrome loads fine failed in Add environment with a transport error. The cleartext plugin now also writes a network security config that trusts system and user CAs and keeps cleartext permitted, since Android ignores usesCleartextTraffic once a config exists. Fixes #5639 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ApprovabilityVerdict: Needs Human Approval Macroscope's review found this PR requires human approval — Android now trusts user-installed CAs app-wide while preserving existing cleartext support. Human review must confirm that this broader default trust boundary is intended, rather than restricting private-CA trust to selected endpoints. You can add or adjust custom eligibility rules. Learn more. |
|
No actionable comments were generated in the recent review.
|
| Layer / File(s) | Summary |
|---|---|
Define and apply Android network security apps/mobile/plugins/withAndroidNetworkSecurityConfig.cjs |
The plugin defines system and user trust anchors, updates the manifest to reference the network security configuration, and writes the XML resource. |
Activate and document the network security plugin apps/mobile/app.config.ts, apps/mobile/plugins/withAndroidCleartextTraffic.cjs, docs/user/remote-access.md |
The Expo plugin list now uses the new network security plugin. The previous cleartext-only plugin is removed. The guide explains that Android users must install a private CA as a CA certificate. |
Priority:
Estimated code review effort: 2 (Simple) | ~10 minutes
Change: Bug fix · Severity of issue fixed: Medium
Suggested reviewers: t3dotgg
Merge Risk: 
Minimal · up to 9e3e0
-
The Android configuration adds user-installed CA trust while retaining cleartext support. No merge-blocking issue was identified.
Pre-merge checks: 
4
-
Full details: Linked Issues check
-
Explanation
Issue
#5639requires Android HTTPS traffic to trust user-installed CA certificates and retain cleartext support.withAndroidNetworkSecurityConfig.cjswritesnetwork_security_config.xmlwithsystemandusertrust anchors andcleartextTrafficPermitted="true". It setsandroid:networkSecurityConfigand registers the plugin inapps/mobile/app.config.ts. The PR reports generated-config checks, APK inspection, runtime private-CA pairing, and cleartext verification. The added Android guidance is consistent with the issue.
Full details: Out of Scope Changes check
-
Explanation
The changes remain within issue
#5639. The implementation replaces the existing Android cleartext plugin with the Android network security plugin. The plugin changes only the Android manifest and XML resource. The added documentation addresses private-CA setup for the Android app. No unrelated platform or product behavior changes are shown.
Full details: Description check
-
Explanation
The pull request description includes all required sections: Problem, Change, Scope and approval, and Verification. It identifies the linked issue, explains the focused Android configuration change and its app-wide effect, documents validation commands and runtime evidence, includes before/after screenshots and recordings, and states known limitations and unverified cases.
-
Finishing Touches
-
📝 Generate docstrings
-
- Commit to this branch
- Create a new PR
🧪 Generate unit tests (beta)
-
- Commit to this branch
- Create a new PR
-
- Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Comment @coderabbitai help to get the list of available commands.
|
Taking over this one |
…k security config Android ignores android:usesCleartextTraffic on API 24+ once a network security config is set, and minSdk is 24; the config's base-config already permits cleartext. Document installing a private CA for the Android app. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # docs/user/remote-access.md
Problem
Android rejects HTTPS environments signed by a CA installed in the Android user certificate store. On a Pixel 9 running Android API 36, Chrome trusts the same endpoint while the base T3 app fails environment discovery with a Transport error and a TLS
CERTIFICATE_UNKNOWNalert.Change
Generate an Android network security configuration that trusts system and user certificate stores, and reference it from the manifest. Preserve existing cleartext HTTP support in the configuration because Android ignores
usesCleartextTrafficonce a network security configuration is present. This applies user-installed CA trust app-wide and requires a new native Android client build.Scope and approval
Fixes #5639
GitCafe: #5639
Triaged bug via the automated issue flow. The change is limited to the existing Android network configuration plugin and its registration.
Verification
expo config --type introspectandexpo prebuild --platform android --no-install: generated manifest references the XML configuration; generated XML trusts system and user roots and permits cleartext.vp run check:changed: format, lint, both knip passes and both affected-package typechecks passed on final integrated head235e42ec4a. The worktree is clean.Pixel 9, Android API 36, portrait 1080×2424, light appearance, font scale 1.0. Base source: 9bc0c33438; native fix: 853aceb5a1. Head native fingerprint
e9cabfd8aa6930799024d4e47fefcd0c8220fc7d, APK SHA-256643269934f0ae53037e5fcb094303b67cb121034a4262916a941dc7695c1dcd7. Captures use the debug development client.Before (left): Chrome trusts the private CA, but base T3 fails discovery. After (right): rebuilt T3 connects and receives a fresh provider status response.
Full evidence index and timelines, fixture matrix, sanitized transport observations, proxy driver, and reproduction drivers. Pairing credentials are masked in recordings.
No render paths changed; the comparison demonstrates connection behavior, with the changed connection state and provider response visible. Independent WSS rejection with the private CA absent could not be reached because HTTPS descriptor preflight rejects first. A first private-CA refresh encountered a transient SocketReadError; subsequent fresh reads and reconnect/read passed. A separate release APK was not run. iOS is unaffected by this Android-only plugin change.
After merging main, the primary agent repeated a provider refresh successfully on the isolated system-CA route. Final integrated client capture. The Android plugin/config bytes remain identical to the captured native-fix commit. Final integrated source: 235e42ec4a. Independent proof audit: sufficient.
Final scoped check log · Final cached native-fingerprint check.
Implementation: Claude Opus 5.5 through Claude Code; triage, integration and proof: GPT-6.1-sol through Codex in T3 Code.
Takeover
Taken over from @juliusmarminge's automated issue flow. The plugin shape was already right and is kept. The plugin no longer sets
android:usesCleartextTraffic: with minSdk 24, Android ignores it once a network security config exists, and the config'sbase-configalready permits cleartext (C1 below shows HTTP/WS still work). A short note indocs/user/remote-access.mdexplains installing a private CA on Android. No tests: a plugin unit test would only restate the XML. The proof is on-device. The "both manifest attributes" line above describes the original commit; onlyandroid:networkSecurityConfigremains.Evidence
Real Android app on a pooled Pixel 9, Android 16 (API 36), portrait 1080×2424, light theme, font scale 1.0. Status-bar time is masked and pairing codes are redacted. Builds: base 71ba6b374c (native fingerprint
ed36a9cc) vs head bbb9042e92 (0b110f6b). One isolated backend with synthetic data serves every row. The final head 9e3e0af976 adds only docs and a merge of main, with the same plugin and config.http://Before (left, B1): the base app rejects the private-CA endpoint. After (right, U1): the head app is connected.
What else changed: no rendered UI code changed. The two sides differ only in connection outcome and the screen it leads to (pairing sheet vs environment details), endpoint text, and synthetic canary names. The private root was regenerated for the B1/N1 retakes; U1 used the original. N1 has two labelled cuts and rejects at the HTTPS preflight, so a separate WSS rejection is never attempted. All captures use development APKs. native-artifact-proof.txt shows the APK's merged manifest and its resolved system + user + cleartext policy, with no debug overrides.
Full evidence index with narrated timestamps · fixture matrix · scripts: generate-certs.sh, proxy.mjs, seed-and-canary.mjs, pair-row.mjs, prepare-public-cert.sh, http-observer.mjs
Verification log
expo prebuildis idempotent. Nothing undernode_modulesdeclares its own network security config. The native fingerprint includes the plugin, so the dev client rebuilds.vp run check:changedpasses on 9e3e0af. CI: 31/31 green on 9e3e0af. CodeRabbit: no blocking issue. Macroscope: needs human approval (trust boundary).🤖 Generated with Claude Code