Skip to content

fix(security): add SSL public key pinning for iOS and Android#717

Merged
RUKAYAT-CODER merged 1 commit into
rinafcode:mainfrom
Vox-d-glitch:security/ssl-certificate-pinning
Jun 28, 2026
Merged

fix(security): add SSL public key pinning for iOS and Android#717
RUKAYAT-CODER merged 1 commit into
rinafcode:mainfrom
Vox-d-glitch:security/ssl-certificate-pinning

Conversation

@Vox-d-glitch

Copy link
Copy Markdown
Contributor

Closes #578

All API traffic relied on OS-level TLS verification alone, leaving bearer tokens, user PII, and payment receipts exposed to MITM attacks via corporate MDM proxies, intercepting proxies (Burp Suite, Charles), and rogue Wi-Fi access points that present forged certificates the OS will accept. This adds SHA-256 public key pinning at the native TLS layer for iOS and Android, with a JS-layer failure handler that forces logout and reports to Sentry without leaking sensitive data.

Summary

  • Added SSL_PINNING constants to src/config/security.ts — domain, primary SPKI SHA-256 hash, backup hash (for zero-downtime rotation), and bypassEnabled flag driven by EXPO_PUBLIC_APP_ENV; pin hashes are never hardcoded inline
  • Created plugins/withSSLPinning.js Expo config plugin following the existing withProguard.js pattern: injects NSPinnedDomains into Info.plist (iOS 14+) and writes res/xml/network_security_config.xml with a <pin-set> and <debug-overrides> trusting user CAs in debug builds (Android 7+); sets android:networkSecurityConfig on <application> in AndroidManifest.xml
  • Registered the plugin in app.json with domain and both pin hash slots; pinning is skipped entirely for non-production build profiles
  • Added isCertPinFailure() to src/services/api/axios.config.ts that detects SSL/TLS/certificate keywords in error.message and error.cause, short-circuits in bypass mode, and intercepts before the ERR_NETWORK retry path; on failure: reports to Sentry with endpoint and method only (no token, headers, or body), forces logout(), and rejects with SSL_PIN_FAILURE
  • Created docs/security/pin-rotation.md zero-downtime rotation runbook covering: keypair generation, fingerprint commands, backup-pin build → 80% adoption gate → server cert rotation → primary-pin promotion, emergency rollback, and Sentry alert thresholds
  • Unit tests cover SSL message classification, logout side-effect, Sentry payload shape (asserts absence of token/body keys), ordinary network errors excluded from pin-failure path, and bypass mode disabling detection; manual device test steps for native pinning are documented in the test file

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Chore / Refactor (no functional changes)

Testing Done

  • Unit Tests
  • Integration Tests
  • Manual Verification (e.g., iOS/Android UI checks)

Security Considerations

  • Does this store user data securely (e.g., avoiding plain AsyncStorage for sensitive data)? — Pin hashes (public, non-secret) live in src/config/security.ts and app.json; private keys are never committed — see rotation runbook
  • Is token handling secure (no token exposure in logs or UI)? — Sentry pin failure events capture only endpoint and method; no token, Authorization header, request body, or response data is included
  • Are all user inputs validated? — N/A to this change
  • Is deep link handling safe from malicious payloads? — N/A to this change

Performance Considerations

  • Are React hooks (useCallback, useMemo) used appropriately to prevent unnecessary renders? — N/A to this change
  • Is FlatList optimized (e.g., using getItemLayout, keyExtractor)? — N/A to this change
  • Are asynchronous patterns handled correctly (e.g., useEffect cleanup to avoid memory leaks)? — Pin failure handler is synchronous; no timers or subscriptions introduced
  • Have bundle size impacts been considered? — No new npm dependencies; pinning is implemented via native platform config and a zero-dependency config plugin

Checklist

  • I have read the CONTRIBUTING guide.
  • My code follows the style guidelines of this project.
  • I have updated the documentation accordingly. — docs/security/pin-rotation.md created with full rotation runbook
  • Are there architectural changes? If so, is there an Architectural Decision Record (ADR)? — No architectural changes; pinning is additive and scoped to the native config plugin and axios interceptor

All API traffic relied on OS-level TLS verification alone, leaving bearer
tokens, PII, and payment receipts exposed to MITM attacks via corporate
MDM proxies or rogue access points that present forged certificates the
OS will accept.

- config/security.ts: add SSL_PINNING constants (domain, primary/backup
  SPKI SHA-256 hashes, bypassEnabled flag tied to EXPO_PUBLIC_APP_ENV)
- plugins/withSSLPinning.js: Expo config plugin that injects
  NSPinnedDomains into Info.plist (iOS 14+) and writes
  network_security_config.xml with <pin-set> + <debug-overrides>
  (Android 7+); registers android:networkSecurityConfig on <application>
- app.json: register withSSLPinning plugin with domain and pin hash slots
- axios.config.ts: isCertPinFailure() detects SSL handshake errors by
  message/cause keywords, forces full logout, reports to Sentry with
  endpoint + method only (no token, headers, or body), rejects with
  SSL_PIN_FAILURE before the ERR_NETWORK retry path
- docs/security/pin-rotation.md: zero-downtime rotation runbook
  (generate next keypair → deploy backup pin build → rotate server cert
  → promote backup to primary); includes emergency rollback and Sentry
  monitoring guidance
- tests: unit tests for pin failure detection, logout side-effect,
  Sentry payload shape, ordinary-network-error exclusion, bypass in dev

Closes rinafcode#577
@drips-wave

drips-wave Bot commented Jun 27, 2026

Copy link
Copy Markdown

@Vox-d-glitch Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@RUKAYAT-CODER

Copy link
Copy Markdown
Contributor

Thank you for contributing to the project.

@RUKAYAT-CODER RUKAYAT-CODER merged commit 500ce6b into rinafcode:main Jun 28, 2026
1 of 14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Security] No certificate pinning — app is vulnerable to MITM on intercepting proxies

2 participants