{"id":51523345,"url":"https://github.com/huytdps13400/react-native-ssl-manager","last_synced_at":"2026-07-08T18:00:54.060Z","repository":{"id":252359800,"uuid":"840181563","full_name":"huytdps13400/react-native-ssl-manager","owner":"huytdps13400","description":"React Native SSL Pinning provides seamless SSL certificate pinning integration for enhanced network security in React Native apps. This module enables developers to easily implement and manage certificate pinning, protecting applications against man-in-the-middle (MITM) attacks. With dynamic configuration options and the ability to toggle SSL ","archived":false,"fork":false,"pushed_at":"2026-07-03T05:04:37.000Z","size":38156,"stargazers_count":34,"open_issues_count":1,"forks_count":3,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-07-03T05:05:53.497Z","etag":null,"topics":["react-native","react-native-app","react-native-ssl-manager","ssl-certificates","ssl-manager","ssl-pinning","ssl-proxy"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/react-native-ssl-manager","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/huytdps13400.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2024-08-09T06:26:11.000Z","updated_at":"2026-07-03T03:14:16.000Z","dependencies_parsed_at":"2024-08-09T09:05:44.776Z","dependency_job_id":"12ba8d7a-344d-44fc-acdb-e96b2c1ade0e","html_url":"https://github.com/huytdps13400/react-native-ssl-manager","commit_stats":null,"previous_names":["huytdps13400/react-native-use-ssl-pinning"],"tags_count":7,"template":false,"template_full_name":null,"purl":"pkg:github/huytdps13400/react-native-ssl-manager","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/huytdps13400%2Freact-native-ssl-manager","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/huytdps13400%2Freact-native-ssl-manager/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/huytdps13400%2Freact-native-ssl-manager/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/huytdps13400%2Freact-native-ssl-manager/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/huytdps13400","download_url":"https://codeload.github.com/huytdps13400/react-native-ssl-manager/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/huytdps13400%2Freact-native-ssl-manager/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35273524,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-08T02:00:06.796Z","response_time":61,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["react-native","react-native-app","react-native-ssl-manager","ssl-certificates","ssl-manager","ssl-pinning","ssl-proxy"],"created_at":"2026-07-08T18:00:48.283Z","updated_at":"2026-07-08T18:00:54.052Z","avatar_url":"https://github.com/huytdps13400.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# react-native-ssl-manager\n\n[![npm version](https://img.shields.io/npm/v/react-native-ssl-manager.svg)](https://www.npmjs.com/package/react-native-ssl-manager)\n[![npm downloads](https://img.shields.io/npm/dm/react-native-ssl-manager.svg)](https://www.npmjs.com/package/react-native-ssl-manager)\n[![license](https://img.shields.io/npm/l/react-native-ssl-manager.svg)](./LICENSE)\n\nProduction-ready SSL certificate pinning for React Native and Expo. Protects against MITM attacks with platform-native enforcement on both iOS and Android.\n\n## Features\n\n- **Platform-native pinning** — TrustKit (iOS) + Network Security Config (Android)\n- **Single config** — one `ssl_config.json` drives both platforms\n- **Runtime toggle** — enable/disable pinning without rebuilding\n- **Expo + RN CLI** — built-in Expo plugin, auto-setup for bare projects\n- **Nitro Module** — native code generated by `nitrogen`; requires the New Architecture (RN 0.75+) and the `react-native-nitro-modules` peer dependency\n- **Android NSC generation** — auto-generates `network_security_config.xml` at build time, covering OkHttp, WebView, Coil, Glide, and HttpURLConnection\n\n## Installation\n\n\u003e **v2 is a [Nitro Module](https://nitro.margelo.com).** It requires\n\u003e **React Native 0.75+** (New Architecture) and the `react-native-nitro-modules`\n\u003e peer dependency. Upgrading from v1? See [`MIGRATION.md`](./MIGRATION.md).\n\n```bash\nnpm install react-native-ssl-manager react-native-nitro-modules\n# or\nyarn add react-native-ssl-manager react-native-nitro-modules\n```\n\niOS:\n```bash\ncd ios \u0026\u0026 pod install\n```\n\n### Expo\n\n```bash\nnpx expo install react-native-ssl-manager\n```\n\nAdd to `app.json`:\n```json\n{\n  \"expo\": {\n    \"plugins\": [\n      [\"react-native-ssl-manager\", { \"sslConfigPath\": \"./ssl_config.json\" }]\n    ]\n  }\n}\n```\n\nExpo plugin options:\n\n| Option | Default | Description |\n|--------|---------|-------------|\n| `sslConfigPath` | `\"ssl_config.json\"` | Path to config relative to project root |\n| `enableAndroid` | `true` | Enable Android NSC generation + manifest patching |\n| `enableIOS` | `true` | Enable iOS asset bundling |\n\n## Quick Start\n\n### 1. Create `ssl_config.json` in your project root\n\n```json\n{\n  \"sha256Keys\": {\n    \"api.example.com\": [\n      \"sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\",\n      \"sha256/BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=\"\n    ]\n  }\n}\n```\n\n\u003e Always include at least 2 pins per domain (primary + backup) to avoid lockout during certificate rotation.\n\n### 2. That's it — pinning is active at launch\n\nOnce `ssl_config.json` is bundled (via the Expo plugin or the CLI build\nscripts), **SSL pinning is enforced automatically at app launch — no JavaScript\ncall is required.** On iOS this is wired up through an Objective-C `+load`\nbootstrap; on Android through an `androidx.startup` initializer that installs the\npinned `OkHttpClientFactory` before React Native's networking stack initializes.\n\n\u003e Earlier versions only initialized pinning inside the native module\n\u003e constructor, which React Native instantiates lazily. That meant pinning did\n\u003e not take effect until JS first touched the module (e.g. calling\n\u003e `getUseSSLPinning()`). This is no longer necessary.\n\n### 3. (Optional) Control pinning from JavaScript\n\n```typescript\nimport {\n  setUseSSLPinning,\n  getUseSSLPinning,\n  setSSLConfig,\n  getPinnedDomains,\n  isSSLManagerAvailable,\n} from 'react-native-ssl-manager';\n\n// Confirm the native module is linked (false ⇒ pinning is NOT active)\nisSSLManagerAvailable();\n\n// Toggle pinning (see note below about iOS)\nawait setUseSSLPinning(false);\nconst isEnabled = await getUseSSLPinning();\n\n// Update pins at runtime\nawait setSSLConfig({\n  sha256Keys: {\n    'api.example.com': ['sha256/AAAA...=', 'sha256/BBBB...='],\n  },\n});\n\n// Inspect the active configuration\nconst domains = await getPinnedDomains();\n```\n\n**Important:** Disabling/changing pinning at runtime requires an app restart to\nfully take effect on **iOS**, because TrustKit can only be initialized once per\nprocess. On **Android** changes apply to subsequent requests immediately. See\n[Runtime Toggle](#runtime-toggle) below.\n\n## How It Works\n\n### iOS — TrustKit Swizzling\n\nTrustKit is initialized with `kTSKSwizzleNetworkDelegates: true`, which swizzles `URLSession` delegates at the OS level. Most libraries using `URLSession` under the hood are automatically covered — no per-library configuration needed.\n\nEach domain is configured with:\n```swift\npinnedDomains[domain] = [\n    kTSKIncludeSubdomains: true,\n    kTSKEnforcePinning: true,\n    kTSKPublicKeyHashes: pins  // SHA-256 base64\n]\n```\n\n### Android — Network Security Config + OkHttp CertificatePinner\n\nTwo layers of enforcement:\n\n1. **OkHttpClientFactory** — Intercepts React Native's HTTP client creation, applies `CertificatePinner` from `ssl_config.json`. Covers `fetch`/`axios` calls from JS.\n\n2. **Network Security Config (NSC)** — Auto-generated `network_security_config.xml` at build time from `ssl_config.json`. Enforced at OS level for all stacks using the platform default `TrustManager`.\n\nGenerated XML format:\n```xml\n\u003cnetwork-security-config\u003e\n  \u003cdomain-config cleartextTrafficPermitted=\"true\"\u003e\n    \u003cdomain includeSubdomains=\"false\"\u003elocalhost\u003c/domain\u003e\n    \u003cdomain includeSubdomains=\"false\"\u003e10.0.2.2\u003c/domain\u003e\n    \u003cdomain includeSubdomains=\"false\"\u003e10.0.3.2\u003c/domain\u003e\n  \u003c/domain-config\u003e\n  \u003cdomain-config cleartextTrafficPermitted=\"false\"\u003e\n    \u003cdomain includeSubdomains=\"true\"\u003eapi.example.com\u003c/domain\u003e\n    \u003cpin-set\u003e\n      \u003cpin digest=\"SHA-256\"\u003eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\u003c/pin\u003e\n    \u003c/pin-set\u003e\n  \u003c/domain-config\u003e\n\u003c/network-security-config\u003e\n```\n\nThe first `domain-config` keeps local dev hosts (`localhost`, and the emulator\nloopbacks `10.0.2.2` / `10.0.3.2`) reachable over cleartext so the Metro bundler\nstill connects in debug builds — without it, referencing this config from the\nmanifest would override React Native's default debug config and break the JS\nbundle connection. Pins carry no expiration (an expired `pin-set` would silently\nstop enforcing). If your app already has a `network_security_config.xml`, the\nlibrary **merges** pin-set entries — existing configs (debug-overrides,\nbase-config, an existing localhost cleartext block) are preserved.\n\n## Supported Networking Stacks\n\n| Stack | Platform | Covered | Mechanism |\n|-------|----------|---------|-----------|\n| `fetch` / `axios` | iOS | Yes | TrustKit swizzling |\n| `URLSession` | iOS | Yes | TrustKit swizzling |\n| `SDWebImage` | iOS | Yes | TrustKit swizzling (uses URLSession) |\n| `Alamofire` | iOS | Yes | TrustKit swizzling (uses URLSession) |\n| Other URLSession-based libs | iOS | Yes* | TrustKit swizzling |\n| `fetch` / `axios` | Android | Yes | OkHttpClientFactory + NSC |\n| OkHttp (direct) | Android | Yes | NSC + CertificatePinner |\n| Android WebView | Android | Yes | NSC |\n| Coil / Ktor OkHttp engine | Android | Yes | NSC |\n| Glide / OkHttp3 | Android | Yes | NSC |\n| `HttpURLConnection` | Android | Yes | NSC |\n| Cronet | Android | Best-effort* | NSC (if using platform TrustManager) |\n\n### Known limitations\n\n- **iOS URLSession**: Apps with complex custom `URLSessionDelegate` implementations or other method-swizzling libraries may conflict with TrustKit. TrustKit docs note swizzling is designed for simple delegate setups.\n- **Android Cronet**: No authoritative docs confirm Cronet always respects NSC `\u003cpin-set\u003e`. Cronet may use its own TLS stack / custom TrustManager that bypasses NSC, so coverage is best-effort. For guaranteed enforcement use Cronet's own pinning API — `CronetEngine.Builder.addPublicKeyPins()`.\n- **Custom TrustManager**: Any library (OkHttp, Cronet, etc.) that builds its own `TrustManager` bypassing the system default will not be covered by NSC.\n- **Custom TLS stacks**: iOS libraries not using `URLSession` (e.g., OpenSSL bindings) and Android Ktor CIO engine are not covered. See [Ktor CIO](#ktor-cio-engine) below.\n\n## PinnedOkHttpClient (Android)\n\nFor native module authors who need a pinned OkHttp client outside React Native's networking layer:\n\n```kotlin\nimport com.usesslpinning.PinnedOkHttpClient\n\nval client = PinnedOkHttpClient.getInstance(context)\n```\n\n- Singleton with double-checked locking\n- Reads `ssl_config.json` and configures `CertificatePinner` when pinning is enabled\n- Returns plain `OkHttpClient` when pinning is disabled\n- Auto-invalidates when pinning state changes via `setUseSSLPinning`\n\n### Glide Integration\n\n```kotlin\n@GlideModule\nclass MyAppGlideModule : AppGlideModule() {\n    override fun registerComponents(context: Context, glide: Glide, registry: Registry) {\n        val client = PinnedOkHttpClient.getInstance(context)\n        registry.replace(\n            GlideUrl::class.java,\n            InputStream::class.java,\n            OkHttpUrlLoader.Factory(client)\n        )\n    }\n}\n```\n\n### Coil Integration\n\n```kotlin\nval imageLoader = ImageLoader.Builder(context)\n    .okHttpClient { PinnedOkHttpClient.getInstance(context) }\n    .build()\n```\n\n### Ktor OkHttp Engine\n\n```kotlin\nval httpClient = HttpClient(OkHttp) {\n    engine {\n        preconfigured = PinnedOkHttpClient.getInstance(context)\n    }\n}\n```\n\n### Ktor CIO Engine\n\nCIO uses its own TLS stack — **not covered** by NSC or `PinnedOkHttpClient`. Manual `TrustManager` required:\n\n```kotlin\nimport io.ktor.client.*\nimport io.ktor.client.engine.cio.*\nimport java.security.MessageDigest\nimport java.security.cert.X509Certificate\nimport javax.net.ssl.X509TrustManager\n\nval httpClient = HttpClient(CIO) {\n    engine {\n        https {\n            trustManager = object : X509TrustManager {\n                override fun checkClientTrusted(chain: Array\u003cX509Certificate\u003e, authType: String) {}\n                override fun checkServerTrusted(chain: Array\u003cX509Certificate\u003e, authType: String) {\n                    val leafCert = chain[0]\n                    val publicKeyHash = MessageDigest.getInstance(\"SHA-256\")\n                        .digest(leafCert.publicKey.encoded)\n                    val pin = android.util.Base64.encodeToString(publicKeyHash, android.util.Base64.NO_WRAP)\n                    val expectedPins = listOf(\"YOUR_PIN_HERE\") // from ssl_config.json\n                    if (pin !in expectedPins) {\n                        throw javax.net.ssl.SSLPeerUnverifiedException(\"Certificate pin mismatch\")\n                    }\n                }\n                override fun getAcceptedIssuers(): Array\u003cX509Certificate\u003e = arrayOf()\n            }\n        }\n    }\n}\n```\n\n## Runtime Toggle\n\n- **Android:** `setUseSSLPinning(...)` and `setSSLConfig(...)` rebuild the\n  `OkHttpClientFactory` and take effect on subsequent requests immediately.\n- **iOS:** TrustKit can only be initialized once per process, so disabling or\n  changing pinning is persisted and applied on the **next app launch**. Restart\n  the app to apply.\n\n```typescript\nimport { setUseSSLPinning } from 'react-native-ssl-manager';\nimport RNRestart from 'react-native-restart'; // optional\n\nawait setUseSSLPinning(false);\nRNRestart.Restart(); // apply change (required on iOS)\n```\n\nDefault state is **enabled** (`true`). State is persisted in:\n- iOS: `UserDefaults`\n- Android: `SharedPreferences` (context: `AppSettings`, key: `useSSLPinning`)\n\n## Disabling for e2e tests (Detox, mocked backends)\n\nOn iOS, TrustKit is installed at app launch via an Objective-C `+load` hook —\n**before any JavaScript runs** — and swizzles `NSURLSession` process-wide.\nBecause it cannot be deinitialized within a running process, calling\n`setUseSSLPinning(false)` from JS is **too late** for an e2e run: the swizzle is\nalready in place, so a mocked backend whose certificate doesn't match your pins\ngets its connection blocked (requests appear to hang / never respond).\n\nUse one of these **before-launch** off-switches to skip TrustKit entirely for a\ntest build or a single test launch. Each also prevents the swizzling, so mocked\ntraffic flows normally. They have **no effect on your production build** unless\nyou set them there.\n\n**Detox — per-launch (no separate build):**\n\n```js\nawait device.launchApp({\n  newInstance: true,\n  launchArgs: { RNSSLManagerDisabled: true },\n});\n```\n\n**Build-time exclude (a dedicated test/e2e configuration):** set a boolean\n`RNSSLManagerDisabled = YES` in that configuration's `Info.plist`.\n\n**Other channels** (all equivalent):\n- Launch argument `--disable-ssl-pinning` (e.g. `xcodebuild` test args)\n- Environment variable `RN_SSL_MANAGER_DISABLED=1` (Xcode scheme / CI)\n\nThe Info.plist / `NSUserDefaults` flag accepts a real boolean (`\u003ctrue/\u003e`) or a\ntruthy string (`YES` / `true` / `1`).\n\n**Verify it took effect (iOS):** the native layer logs to the device console at\nlaunch (filter for `RNSSLManager`, e.g. in Console.app or\n`xcrun simctl spawn booted log stream --predicate 'eventMessage CONTAINS \"RNSSLManager\"'`):\n- `SSL pinning DISABLED for this launch via \u003cchannel\u003e` — pinning was skipped\n- `SSL pinning ACTIVE — TrustKit installed … for domains: …` — pinning is on\n- `BLOCKED connection to \u003chost\u003e …` — a request failed pin validation\n\n\u003e Android does not have this problem: pinning applies per-request, so\n\u003e `setUseSSLPinning(false)` takes effect immediately without relaunching. For\n\u003e connecting to a local mock/dev server over cleartext, the generated Network\n\u003e Security Config already permits `localhost`, `10.0.2.2` and `10.0.3.2`.\n\n## API Reference\n\n### `setUseSSLPinning(usePinning: boolean): Promise\u003cvoid\u003e`\n\nEnable or disable SSL pinning. On iOS, disabling takes effect on the next app\nlaunch (TrustKit cannot be un-initialized within a running process).\n\n### `getUseSSLPinning(): Promise\u003cboolean\u003e`\n\nReturns current pinning state. Defaults to `true` if never explicitly set.\n\n### `setSSLConfig(config: SslPinningConfig | string): Promise\u003cvoid\u003e`\n\nUpdate the pinning configuration at runtime. Accepts a config object or a\npre-serialized JSON string. Rejects with an error code on malformed input.\nAndroid applies changes to subsequent requests immediately; iOS applies them on\nthe next app launch.\n\n### `getPinnedDomains(): Promise\u003cstring[]\u003e`\n\nResolves with the domains in the active configuration (runtime config if set,\notherwise the bundled `ssl_config.json`).\n\n### `isSSLManagerAvailable(): boolean`\n\nReturns whether the native module is linked. When `false`, all functions are\nno-ops and pinning is **not** enforced — rebuild the app so the native module is\nlinked.\n\n### Types\n\n```typescript\ninterface SslPinningConfig {\n  sha256Keys: {\n    [domain: string]: string[];\n  };\n}\n\ninterface SslPinningError extends Error {\n  code?: string;\n  message: string;\n}\n```\n\n## Configuration Details\n\n### `ssl_config.json`\n\nMust be named exactly `ssl_config.json` and placed in the project root.\n\n```json\n{\n  \"sha256Keys\": {\n    \"api.example.com\": [\n      \"sha256/primary-cert-hash=\",\n      \"sha256/backup-cert-hash=\"\n    ]\n  }\n}\n```\n\nPin format: `sha256/` prefix + base64-encoded SHA-256 hash of the certificate's Subject Public Key Info (SPKI). The `sha256/` prefix is stripped automatically when generating NSC XML.\n\n### How the config reaches each platform\n\n| Platform | RN CLI | Expo |\n|----------|--------|------|\n| **iOS** | Podspec script phase copies to app bundle | Plugin copies to `ios/` + adds to Xcode project |\n| **Android (OkHttp)** | Postinstall copies to `assets/` | Plugin copies to `app/src/main/assets/` |\n| **Android (NSC)** | Gradle task generates XML in `res/xml/` | Plugin generates XML in `res/xml/` |\n| **Android (Manifest)** | Gradle task patches manifest | Plugin patches manifest |\n\n### Verifying your setup\n\n**Android** (RN CLI): After building, run:\n```bash\n./gradlew checkSslConfig\n```\n\n**Testing with Proxyman/Charles**: Enable pinning, then attempt to intercept traffic. Requests should fail with SSL handshake errors. Disable pinning to inspect traffic during development.\n\n## Platform Requirements\n\n| | Minimum |\n|---|---------|\n| iOS | 13.0 |\n| Android | API 21 (5.0) |\n| React Native | 0.60+ (AutoLinking), 0.68+ (New Architecture) |\n| Expo | SDK 47+ |\n| Node | 16+ |\n\n## Roadmap\n\n- Certificate rotation support and expiry notifications\n- `react-native-ssl-manager-glide` — optional artifact with pre-configured `AppGlideModule`\n- React Native Web support\n\n## Demo\n\n| iOS | Android |\n|-----|---------|\n| [![iOS Demo](https://vumbnail.com/1109299210.jpg)](https://vimeo.com/1109299210) | [![Android Demo](https://vumbnail.com/1109299632.jpg)](https://vimeo.com/1109299632) |\n\n## Contributing\n\n```bash\ngit clone https://github.com/huytdps13400/react-native-ssl-manager.git\ncd react-native-ssl-manager\nyarn install\nnpm run build\n\n# Example apps\nnpm run example:ios\nnpm run example:android\nnpm run example-expo:ios\nnpm run example-expo:android\n```\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md) for details.\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhuytdps13400%2Freact-native-ssl-manager","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhuytdps13400%2Freact-native-ssl-manager","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhuytdps13400%2Freact-native-ssl-manager/lists"}