[Bug]: Android app does not trust user-installed CA certificates
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 23k
- Forks
- 5.9k
- Avg merge
- 11h 14m
- Merged PRs (30d)
- 357
Description
Before submitting
- I searched existing issues and did not find a duplicate.
- I included enough detail to reproduce or investigate the problem.
Area
apps/mobile
Steps to reproduce
- Stand up a T3 Code server behind an internal CA (in my case stepCA), so it serves HTTPS with a cert chaining to a private root.
- Install that root cert on the Android device under Settings → Security → Encryption & credentials → Install a certificate → CA certificate. It lands in User credentials, not System.
- Confirm the cert is trusted by the system by opening
https://private.ca.com/.well-known/t3/environmentin Chrome on the same device. It loads without a warning. - Open the T3 Code Android app → Add environment.
- Enter Host:
https://private.ca.comand the pairing code. - Tap + ADD ENVIRONMENT.
Expected behavior
The app should reach the environment endpoint and pair, the same way Chrome on the same device does, since the CA is installed and trusted at the OS level.
Actual behavior
Pairing fails immediately with:
Failed to fetch remote environment endpoint https://private.ca.com/.well-known/t3/environment (HttpClientError: Transport error (GET https://private.ca.com/.well-known/t3/environment)).
The error is generic, so it does not read as a TLS problem at first. From the app side there is nothing distinguishing a rejected chain from the host being unreachable.
Digging through the repo, my guess would be this is the stock Android trust policy rather than anything specific to the pairing flow:
- There is no
network_security_config.xmlanywhere inapps/mobile, and noandroid:networkSecurityConfigattribute on the manifest<application>element. - With no config, API 24+ defaults to system CAs only. User-installed CAs are ignored for app traffic. RN/Expo min and target SDK are both in that range.
- The only Android networking change in the config plugins is cleartext, which is a separate switch and does not affect trust anchors:
// apps/mobile/plugins/withAndroidCleartextTraffic.cjs
const { withAndroidManifest } = require("expo/config-plugins");
module.exports = function withAndroidCleartextTraffic(config) {
return withAndroidManifest(config, (nextConfig) => {
// ...
application.$["android:usesCleartextTraffic"] = "true";
return nextConfig;
});
};
- There is no custom TrustManager, OkHttp cert pinning, or custom SSL stack, so HTTPS/WSS goes React Native → OkHttp → platform TLS with the default anchors.
iOS does not have the same problem because NSAllowsArbitraryLoads: true is set there. Android has no equivalent flag for user CAs, it has to be done through a network security config.
So in practice: system or public CA works, http:// and ws:// work because cleartext is permitted, and a user-installed CA fails the handshake.
Impact
Major degradation or frequent failure
Version or commit
T3 Code Android 1.0.1, installed from Google Play
Environment
Samsung Android 15, stepCA-issued server cert, root installed under Settings → Security → User credentials
Logs or stack traces
Failed to fetch remote environment endpoint https://private.ca.com/.well-known/t3/environment (HttpClientError: Transport error (GET https://private.ca.com/.well-known/t3/environment)).
The same URL returns 200 in Chrome on the same device with the same cert installed.
Screenshots, recordings, or supporting files
No response
Workaround
Add a network security config that includes the user trust anchors, and wire it up through an Expo config plugin alongside the existing cleartext one:
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<certificates src="system" />
<certificates src="user" />
</trust-anchors>
</base-config>
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with apps/mobile/plugins/withAndroidCleartextTraffic.cjs and the Android manifest generated by the Expo config plugins. Add the Android network security configuration alongside the existing cleartext setting so user-installed and system CAs are trusted. Verify pairing against the private CA endpoint on Android while preserving public-CA and cleartext behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- android, react-native, typescript
- Domain
- mobile, networking, security
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 76/100