Mobile security react native
Skill agents-inc/skills/dist/plugins/mobile-security-react-native/skills/mobile-security-react-native
The official skills marketplace for Agents Inc, an agent composition framework that builds stacks and compiles specialized subagents for Claude Code
npx -y skills add agents-inc/skills --skill mobile-security-react-nativeAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 18 stars18 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Secure storage, certificate pinning, biometric auth, jailbreak detection, code obfuscation, network security, screenshot prevention for React Native
SKILL.md
18.8 KB, ~4.2k tokens by cl100k_base, as published. Nobody here has run it
React Native Security Patterns
Quick Guide: Defense-in-depth: layer secure storage (expo-secure-store or react-native-keychain), certificate pinning, biometric authentication, jailbreak/root detection, and code obfuscation. Never store secrets in AsyncStorage or JS bundles. Use Hermes bytecode as your first obfuscation layer. iOS Keychain persists across reinstalls; Android Keystore does not. Certificate pins require at least two hashes (primary + backup) on iOS.
<critical_requirements>
CRITICAL: Before Using This Skill
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
import type, named constants)
(You MUST NEVER store tokens, passwords, API keys, or PII in AsyncStorage or plain-text files -- use hardware-backed secure storage)
(You MUST use at least two public key hashes for certificate pinning on iOS -- TrustKit/iOS enforces this and will throw if only one is provided)
(You MUST treat jailbreak/root detection as one layer in defense-in-depth -- client-side checks can be bypassed, always validate server-side too)
(You MUST configure both iOS ATS and Android Network Security Config to enforce HTTPS -- never ship with NSAllowArbitraryLoads: true in production)
(You MUST add NSFaceIDUsageDescription to Info.plist when using Face ID -- the OS silently falls back to passcode without it)
</critical_requirements>
Auto-detection: secure storage, SecureStore, expo-secure-store, react-native-keychain, Keychain, Keystore, certificate pinning, SSL pinning, react-native-ssl-public-key-pinning, TrustKit, jailbreak detection, root detection, jail-monkey, biometric authentication, expo-local-authentication, Face ID, Touch ID, fingerprint, code obfuscation, Hermes bytecode, ProGuard, R8, screen capture prevention, App Transport Security, Network Security Config, MITM
When to use:
- Storing credentials, tokens, or sensitive data on device
- Implementing certificate pinning to prevent MITM attacks
- Adding biometric authentication (Face ID, Touch ID, fingerprint)
- Detecting jailbroken/rooted devices
- Hardening builds with code obfuscation (Hermes, ProGuard/R8)
- Preventing screenshot/screen recording of sensitive screens
- Configuring network security (ATS on iOS, Network Security Config on Android)
When NOT to use:
- General React Native component architecture (not a security concern)
- Server-side API security (use your backend security approach)
- Web-only applications (web security patterns differ fundamentally)
Key patterns covered:
- Secure storage with expo-secure-store and react-native-keychain
- Certificate pinning with react-native-ssl-public-key-pinning
- Biometric authentication with expo-local-authentication and react-native-keychain
- Jailbreak/root detection with jail-monkey
- Code obfuscation: Hermes bytecode, Metro transformer, ProGuard/R8
- Network security: iOS ATS and Android Network Security Config
- Screenshot and screen recording prevention
- Defense-in-depth strategy and security layering
Detailed Resources:
- examples/core.md - Secure storage, certificate pinning, biometric auth
- examples/hardening.md - Code obfuscation, jailbreak detection, screenshot prevention, network config
- reference.md - Security checklist, library API reference, pin hash commands
<philosophy>
Philosophy
Mobile security is defense-in-depth -- no single measure is sufficient. Attackers can bypass any individual protection, so layer multiple defenses: secure storage protects data at rest, certificate pinning protects data in transit, biometric authentication protects access, jailbreak detection identifies compromised environments, and code obfuscation raises the cost of reverse engineering.
Core principles:
- Never trust the client -- all sensitive operations need server-side validation. Client-side checks are speed bumps, not walls.
- Hardware-backed storage -- iOS Keychain and Android Keystore provide hardware-level encryption. AsyncStorage is a plain-text file.
- HTTPS everywhere -- enforce TLS for all network communication. Certificate pinning adds a second layer against compromised CAs.
- Minimal data exposure -- store the least sensitive data possible on device. Prefer short-lived tokens over long-lived credentials.
- Fail secure -- when security checks fail (biometric, jailbreak), deny access by default rather than falling back to insecure paths.
Mental model:
Think of mobile security as concentric rings. Each ring (secure storage, pinning, biometrics, obfuscation, jailbreak detection) independently slows attackers. The combination creates a security posture that makes exploitation impractical for most threat models.
Platform differences that matter:
| Concern | iOS | Android |
|---|---|---|
| Secure storage | Keychain (persists across reinstalls) | Keystore + SharedPreferences (cleared on uninstall) |
| Biometrics | Face ID / Touch ID | Fingerprint / Face Unlock (weak vs strong) |
| Network security | ATS (default HTTPS since iOS 9) | Network Security Config (clear text blocked API 28+) |
| Code protection | Hermes bytecode (no ProGuard for JS) | Hermes bytecode + ProGuard/R8 for native/Java |
| Screenshot prevention | Effective (screen recording + screenshots) | FLAG_SECURE (effective for screenshots, partial for recording) |
<patterns>
Core Patterns
Pattern 1: Secure Storage
Two main libraries: expo-secure-store (Expo-managed, simpler API, 2KB value limit) and react-native-keychain (bare RN, biometric-protected credentials, no size limit).
expo-secure-store uses iOS Keychain and Android Keystore-encrypted SharedPreferences. Values are strings with a ~2KB limit. Supports biometric gating via requireAuthentication.
import * as SecureStore from "expo-secure-store";
const AUTH_TOKEN_KEY = "auth-token";
// Store securely with biometric protection
await SecureStore.setItemAsync(AUTH_TOKEN_KEY, token, {
requireAuthentication: true,
authenticationPrompt: "Authenticate to save credentials",
});
// Retrieve (prompts biometric if requireAuthentication was set)
const stored = await SecureStore.getItemAsync(AUTH_TOKEN_KEY);
react-native-keychain provides credential storage with granular access control and biometric gating via ACCESS_CONTROL options.
import * as Keychain from "react-native-keychain";
await Keychain.setGenericPassword("[email protected]", token, {
accessControl: Keychain.ACCESS_CONTROL.BIOMETRY_ANY_OR_DEVICE_PASSCODE,
accessible: Keychain.ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY,
});
Why good: hardware-backed encryption, biometric gating prevents unauthorized reads, WHEN_UNLOCKED_THIS_DEVICE_ONLY prevents extraction from backups
See examples/core.md for full secure storage patterns with error handling and library comparison.
Pattern 2: Certificate Pinning
Pin your server's public key hashes to prevent MITM attacks even when a device's CA store is compromised. Use react-native-ssl-public-key-pinning for a JS-level approach that works with all HTTP clients.
import { initializeSslPinning } from "react-native-ssl-public-key-pinning";
const PIN_EXPIRATION_DATE = "2026-12-31";
await initializeSslPinning({
"api.example.com": {
includeSubdomains: true,
publicKeyHashes: [
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", // Primary
"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=", // Backup (REQUIRED on iOS)
],
expirationDate: PIN_EXPIRATION_DATE,
},
});
Why good: intercepts all fetch/XMLHttpRequest calls globally, no native code changes required, expiration date prevents bricking when certificates rotate
Gotcha: iOS requires at least two hashes per domain. Providing only one causes initializeSslPinning to throw.
See examples/core.md for rotation strategy and reference.md for pin hash generation commands.
Pattern 3: Biometric Authentication
Two approaches: expo-local-authentication (standalone biometric prompt, no credential storage) and react-native-keychain (biometric-gated credential retrieval).
import * as LocalAuthentication from "expo-local-authentication";
async function authenticateUser(): Promise<boolean> {
const hasHardware = await LocalAuthentication.hasHardwareAsync();
const isEnrolled = await LocalAuthentication.isEnrolledAsync();
if (!hasHardware || !isEnrolled) return false;
const result = await LocalAuthentication.authenticateAsync({
promptMessage: "Verify your identity",
disableDeviceFallback: false,
cancelLabel: "Cancel",
});
return result.success;
}
Why good: checks hardware + enrollment before prompting, disableDeviceFallback: false allows passcode as backup, clean boolean result
Gotcha: Face ID requires NSFaceIDUsageDescription in Info.plist. Without it, iOS silently falls back to passcode (no error, no Face ID prompt). Face ID is not supported in Expo Go -- use a development build.
See examples/core.md for biometric-gated credential flow combining both libraries.
Pattern 4: Jailbreak/Root Detection
Detect compromised devices where security controls (sandboxing, code signing) are disabled. Use jail-monkey for detection checks.
import JailMonkey from "jail-monkey";
function getDeviceSecurityStatus() {
return {
isJailbroken: JailMonkey.isJailBroken(),
canMockLocation: JailMonkey.canMockLocation(),
isDebugMode: JailMonkey.isDebuggedMode(),
isOnExternalStorage: JailMonkey.isOnExternalStorage(), // Android only
};
}
Why good: checks multiple indicators (not just one file path), includes location mocking and debug detection
Important: Jailbreak detection is a speed bump, not a wall. Determined attackers bypass client-side checks with hooking frameworks (Frida, Objection). Always pair with server-side device attestation for high-security apps.
See examples/hardening.md for response strategies and server-side validation.
Pattern 5: Code Obfuscation
Layer 1: Hermes bytecode (enabled by default since RN 0.70) compiles JS to bytecode, making casual reverse engineering difficult. Layer 2: Metro obfuscation transformer for additional string encryption and control flow flattening. Layer 3: ProGuard/R8 for Android native/Java code.
// metro.config.js -- adding obfuscation transformer
const { getDefaultConfig } = require("@react-native/metro-config");
const config = getDefaultConfig(__dirname);
config.transformer = {
...config.transformer,
babelTransformerPath: require.resolve(
"react-native-obfuscating-transformer"
),
};
module.exports = config;
Why good: layered approach -- Hermes handles baseline, transformer adds string/flow obfuscation, ProGuard/R8 covers native code
Gotcha: Not all obfuscation options work. The stringArray option in react-native-obfuscating-transformer is known to break builds. Test thoroughly.
See examples/hardening.md for ProGuard/R8 configuration and obfuscation options.
Pattern 6: Network Security Configuration
Enforce HTTPS at the OS level. iOS uses App Transport Security (ATS), Android uses Network Security Config.
iOS (Info.plist): ATS enforces HTTPS by default since iOS 9. Never ship with NSAllowArbitraryLoads: true.
Android (network_security_config.xml): Clear text blocked by default since API 28. Pin certificates natively for defense-in-depth alongside JS-level pinning.
<!-- android/app/src/main/res/xml/network_security_config.xml -->
<network-security-config>
<base-config cleartextTrafficPermitted="false">
<trust-anchors>
<certificates src="system" />
</trust-anchors>
</base-config>
<domain-config>
<domain includeSubdomains="true">api.example.com</domain>
<pin-set expiration="2026-12-31">
<pin digest="SHA-256">AAAAAAAAAA...=</pin>
<pin digest="SHA-256">BBBBBBBBBB...=</pin>
</pin-set>
</domain-config>
</network-security-config>
Why good: OS-level enforcement, clear text blocked globally, native pinning adds second layer beyond JS-level pinning
See examples/hardening.md for iOS ATS configuration and debug vs release network policies.
Pattern 7: Screenshot and Screen Recording Prevention
Prevent screen capture on sensitive screens (banking, credentials, personal data).
import { useIsFocused } from "@react-navigation/native";
import * as ScreenCapture from "expo-screen-capture";
import { useEffect } from "react";
function usePreventCapture() {
const isFocused = useIsFocused();
useEffect(() => {
if (isFocused) {
ScreenCapture.preventScreenCaptureAsync();
} else {
ScreenCapture.allowScreenCaptureAsync();
}
return () => {
ScreenCapture.allowScreenCaptureAsync();
};
}, [isFocused]);
}
Why good: per-screen control (not global), cleanup on unfocus/unmount, works for both screenshots and screen recording on iOS
Gotcha: On Android, FLAG_SECURE reliably blocks screenshots but screen recording prevention varies by Android version. On iOS, screenshot content is replaced with blank but the user can still trigger the screenshot action.
See examples/hardening.md for non-Expo alternatives and listener-based approaches.
</patterns><decision_framework>
Decision Framework
Secure Storage Choice
Need to store credentials/tokens securely?
+-- Using Expo managed workflow?
| +-- YES -> expo-secure-store (simpler API, Expo-native)
| +-- NO -> react-native-keychain (more control, biometric options)
|
+-- Need biometric-gated credential retrieval?
| +-- YES -> react-native-keychain with ACCESS_CONTROL.BIOMETRY_ANY
| +-- OR -> expo-secure-store with requireAuthentication: true
|
+-- Value larger than 2KB?
| +-- YES -> react-native-keychain (no size limit)
| +-- NO -> Either library works
|
+-- Need credential persistence across app reinstalls?
+-- iOS -> Both persist (Keychain behavior)
+-- Android -> Neither persists (cleared on uninstall)
Biometric Authentication Choice
Need biometric prompt (no credential storage)?
+-- YES -> expo-local-authentication
|
Need biometric-gated credential storage/retrieval?
+-- YES -> react-native-keychain with accessControl
|
Need to distinguish biometric security level (weak vs strong)?
+-- YES -> expo-local-authentication (provides SecurityLevel enum)
Certificate Pinning Approach
Need SSL pinning?
+-- JS-level (works with all HTTP clients)?
| +-- YES -> react-native-ssl-public-key-pinning
|
+-- Native-level (defense-in-depth)?
| +-- iOS -> TrustKit (via CocoaPods)
| +-- Android -> Network Security Config XML
|
+-- Best practice -> Both JS-level AND native-level
Security Layering
Minimum viable security:
1. Secure storage (expo-secure-store or react-native-keychain)
2. HTTPS enforcement (ATS + Network Security Config)
3. Hermes bytecode (default since RN 0.70)
Standard security (most apps):
+ Certificate pinning
+ Biometric authentication
+ ProGuard/R8 on Android
High security (banking, healthcare, fintech):
+ Jailbreak/root detection
+ JS code obfuscation transformer
+ Screenshot prevention
+ Server-side device attestation
+ Runtime integrity checks
</decision_framework>
<red_flags>
RED FLAGS
High Priority Issues:
- Storing tokens or credentials in AsyncStorage -- it is a plain-text file, trivially readable on jailbroken/rooted devices
- Storing API keys or secrets in the JS bundle -- the bundle is extractable from any published app
- Shipping with
NSAllowArbitraryLoads: truein production Info.plist -- disables ATS entirely, allows HTTP - Using only one public key hash for certificate pinning on iOS -- TrustKit throws, pinning silently fails
- Relying solely on jailbreak detection for security -- client-side checks are bypassable with Frida/Objection
Medium Priority Issues:
- Not checking
hasHardwareAsyncandisEnrolledAsyncbefore callingauthenticateAsync-- crashes or confusing UX on devices without biometrics - Missing
NSFaceIDUsageDescriptionin Info.plist -- Face ID silently falls back to passcode without any error - Using
ACCESSIBLE.ALWAYSfor Keychain items -- allows access even when device is locked (deprecated on iOS) - Skipping ProGuard/R8 on Android release builds -- native code is trivially decompilable without it
- Not setting
expirationDateon certificate pins -- expired certificates brick the app until users update
Gotchas & Edge Cases:
- iOS Keychain data persists across app reinstalls (same bundle ID); Android Keystore data does not -- plan token refresh accordingly
- expo-secure-store has a ~2KB value size limit -- large tokens or data blobs will silently fail or throw
z.coerce.boolean()treats string"false"as truthy -- when parsing security config from strings, use explicit comparison- Android
FLAG_SECUREblocks screenshots reliably but screen recording prevention varies by OS version and manufacturer - Hermes bytecode can be decompiled with tools like
hbctool-- it raises the bar but is not true encryption - react-native-obfuscating-transformer's
stringArrayoption breaks React Native builds -- avoid it - Android biometrics have "weak" (2D face) vs "strong" (fingerprint, 3D face) security levels -- financial apps should require strong
- Certificate pins must be rotated before expiry -- set calendar reminders and use the
expirationDatefield as a safety net Keychain.ACCESS_CONTROL.BIOMETRY_CURRENT_SETinvalidates credentials when biometrics are re-enrolled -- useBIOMETRY_ANYfor persistence across biometric changes
</red_flags>
<critical_reminders>
CRITICAL REMINDERS
All code must follow project conventions in CLAUDE.md
(You MUST NEVER store tokens, passwords, API keys, or PII in AsyncStorage or plain-text files -- use hardware-backed secure storage)
(You MUST use at least two public key hashes for certificate pinning on iOS -- TrustKit/iOS enforces this and will throw if only one is provided)
(You MUST treat jailbreak/root detection as one layer in defense-in-depth -- client-side checks can be bypassed, always validate server-side too)
(You MUST configure both iOS ATS and Android Network Security Config to enforce HTTPS -- never ship with NSAllowArbitraryLoads: true in production)
(You MUST add NSFaceIDUsageDescription to Info.plist when using Face ID -- the OS silently falls back to passcode without it)
Failure to follow these rules will expose user credentials, enable MITM attacks, and create false security assumptions.
</critical_reminders>
Gives 0 of the 12 instructions most security skills give in ~4.2k tokens
Counted across 648 of the 828 authors here whose files we hold, read 2026-08-07
- parameterize all database queriesin 68 of 648, across 51 files
- hash passwords using bcrypt scrypt or argon2in 49 of 648, across 36 files
- apply rate limiting to authentication endpointsin 48 of 648, across 24 files
- Configure security headersin 35 of 648, across 19 files
- validate all inputsin 32 of 648, across 24 files
- validate all external input at the system boundaryin 29 of 648, across 19 files
- run containers as a non-root userin 28 of 648, across 15 files
- use httponly secure samesite cookies for sessionsin 26 of 648, across 15 files
- run dependency audits before every releasein 21 of 648, across 10 files
- encode output to prevent cross-site scriptingin 21 of 648, across 11 files
- copy dependencies before source codein 20 of 648, across 9 files
- store secrets in environment variablesin 20 of 648, across 18 files
Said here and by no other author read
- layer multiple security defenses
- prefer short-lived tokens over long-lived credentials
- add an NSFaceIDUsageDescription to the Info.plist
- use hardware-backed secure storage
- validate sensitive operations server-side
- use at least two public key hashes for certificate pinning
Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once. Length counted with cl100k_base; the agent that loads this file may tokenize it differently.