skip to content

In a React Native Android app, how do android:usesCleartextTraffic and network_security_config.xml relate, and what else can the config file express?

level: middleimportance: nice to knowfreq 28%

answer

  1. one flag vs a per-domain policy file
  2. android:networkSecurityConfig on application
  3. the config file wins over the flag
  4. domain-config, base-config, debug-overrides
  5. pin-set lives here too

basics

~20 s

usesCleartextTraffic is one app-wide flag; network_security_config.xml is a per-domain policy file referenced from the manifest. When the file is present it takes precedence, and it can also set trust anchors, debug-only overrides and certificate pins.

solid answer

~40 s

`android:usesCleartextTraffic` is a single yes or no for the whole app; since React Native 0.82 the template fills it from a Gradle placeholder (`true` in debug, `false` in release). A **network security config**, an XML file in `res/xml/` referenced by `android:networkSecurityConfig` on `<application>`, is a declarative policy: a `base-config` for defaults, `domain-config` blocks for specific hosts with their own `cleartextTrafficPermitted`, `trust-anchors`, `debug-overrides` that apply only to debuggable builds, and a `pin-set` for public-key pins with an optional `expiration`. When the file exists, Android uses it instead of the manifest flag for cleartext decisions. Because React Native's networking runs on OkHttp over the platform TLS stack, the policy covers `fetch` without any JavaScript changes.

code

xml · 20 lines
xml
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
  <base-config cleartextTrafficPermitted="false">
    <trust-anchors>
      <certificates src="system" />
    </trust-anchors>
  </base-config>
  <domain-config>
    <domain includeSubdomains="true">api.payments.example</domain>
    <pin-set expiration="2027-06-30">
      <pin digest="SHA-256">CURRENT_SPKI_HASH_BASE64=</pin>
      <pin digest="SHA-256">BACKUP_SPKI_HASH_BASE64=</pin>
    </pin-set>
  </domain-config>
  <debug-overrides>
    <trust-anchors>
      <certificates src="user" />
    </trust-anchors>
  </debug-overrides>
</network-security-config>

go deeper

for a junior

Know that the manifest flag is app-wide and that a network security config file can set rules per domain.

for a middle

Describe the config elements, how the file relates to the manifest flag, and how React Native 0.82 sets cleartext per build type.

for a senior

Write a production config: cleartext off, pins scoped to your API, proxy trust only in debug-overrides, and generated files handled via plugins in Expo.

for a principal

Decide which transport rules belong in declarative platform config versus code, so security reviews read one file per platform.

## Two levels of control Android gives an app two ways to say how it may use the network: - **`android:usesCleartextTraffic`**, an attribute on the manifest's `<application>` element. It is one boolean for the whole app. - A **network security config**, an XML file (conventionally `res/xml/network_security_config.xml`) that the manifest points at with `android:networkSecurityConfig="@xml/network_security_config"`. When a network security config is present, Android takes cleartext decisions from it rather than from the manifest flag, so the two should not be used to say different things. The file is the more precise tool; the flag is a blunt default. ## What React Native sets up for you Since **React Native 0.82**, the template's single `AndroidManifest.xml` contains `android:usesCleartextTraffic="${usesCleartextTraffic}"`, and the React Native Gradle plugin fills the placeholder per build type: `true` for `debug` and `debugOptimized`, `false` for `release`. That is what lets a debug build reach Metro over plain HTTP. The template does not ship a network security config; you add one when you need per-domain rules or pins. ## What the config file can express | Element | Purpose | |---|---| | `<base-config>` | defaults for every connection, such as `cleartextTrafficPermitted="false"` | | `<domain-config>` with `<domain>` children | rules for specific hosts; `includeSubdomains` widens a domain to its subdomains | | `<trust-anchors>` with `<certificates>` | which certificate authorities are trusted, for example system only | | `<debug-overrides>` | extra trust that applies only when the app is debuggable, handy for a local proxy | | `<pin-set>` with `<pin digest="SHA-256">` | public-key pins for a domain, with an optional `expiration` date | Because the policy is enforced by the platform TLS stack, and React Native's networking module uses OkHttp on top of it, a rule in this file applies to `fetch` calls from JavaScript with no code changes. ## How Android evaluates the file for one connection 1. It looks for the **most specific** `domain-config` whose `domain` matches the host (taking `includeSubdomains` into account); nested `domain-config` blocks inherit unset values from their parent. 2. If none matches, the `base-config` applies; if there is no `base-config`, the platform defaults for the app's target SDK apply. 3. The trust anchors of the chosen block validate the certificate chain in the usual way. 4. If the block has a `pin-set` that has not expired, at least one certificate in the validated chain must match a pin. 5. If the app is debuggable, `debug-overrides` adds its trust anchors on top. ## A payments app's config 1. `base-config` with cleartext off, so nothing can quietly fall back to HTTP. 2. A `domain-config` for `api.payments.example` with a `pin-set` holding the current key and a backup key, and an `expiration` date. 3. `debug-overrides` trusting a user-installed CA, so QA can inspect traffic with a proxy in debug builds only. ## In an Expo project Expo generates the `android` directory during prebuild, so hand edits to the manifest or to `res/xml` are overwritten. The app-wide flag is exposed as the `android.usesCleartextTraffic` option of `expo-build-properties`; a full network security config file is added with a config plugin that writes the XML and sets the manifest attribute. ## Pitfalls - Setting `usesCleartextTraffic="true"` in the manifest and expecting it to override a config file that says otherwise. - Trying to put a `pin-set` in `base-config`: pins are declared inside a `domain-config`, for the specific domains you control. - Forgetting `includeSubdomains` and wondering why `eu.api.payments.example` is not covered. - Shipping `debug-overrides` trust into production by making the release build debuggable.

  • Why put a proxy CA under `debug-overrides` instead of `base-config`?
    `debug-overrides` applies only when the app is debuggable, so QA can inspect traffic in debug builds while release builds trust only the system CAs. Putting the user CA in `base-config` would let anyone who installs a CA on their phone intercept the release app too.
  • Do you need to change JavaScript for these rules to apply to `fetch`?
    No. React Native's networking module on Android uses OkHttp over the platform TLS stack, and the network security config is enforced there, so cleartext rules and pins apply to `fetch` and XHR from JavaScript automatically.

saying these in an interview costs you the question

  • usesCleartextTraffic overrides whatever the config file says.
  • The React Native template ships a network security config.
  • Network security config rules require changes to JavaScript fetch calls.
  • debug-overrides also apply to release builds.
  • A domain entry automatically covers all its subdomains.