{"id":80631,"date":"2026-07-28T11:52:56","date_gmt":"2026-07-28T06:22:56","guid":{"rendered":"https:\/\/www.tothenew.com\/blog\/?p=80631"},"modified":"2026-07-30T15:56:08","modified_gmt":"2026-07-30T10:26:08","slug":"react-native-app-security-part-3-runtime-protection-from-hooking-and-tampering-with-freerasp","status":"publish","type":"post","link":"https:\/\/www.tothenew.com\/blog\/react-native-app-security-part-3-runtime-protection-from-hooking-and-tampering-with-freerasp\/","title":{"rendered":"React Native App Security (Part 3): Runtime Protection from Hooking and Tampering with freeRASP"},"content":{"rendered":"<h2>Why a Startup Check Isn\u2019t Enough<\/h2>\n<p>In Part 2, we covered JailMonkey \u2014 a library that checks whether a device is rooted or jailbroken when your app launches.<\/p>\n<p>That check matters, but it has a gap.<\/p>\n<p>JailMonkey runs once, typically at startup. It tells you what the device looked like at that moment. It doesn\u2019t tell you what happens five minutes into the session, after the user has logged in, after a Frida script attaches to your running process, or after someone hooks a function call mid-transaction.<\/p>\n<p>An attacker doesn\u2019t need to compromise your app before launch. They can attach instrumentation tools\u00a0while the app is running\u00a0\u2014 long after your one-time check has already passed.<\/p>\n<p>This is the gap that\u00a0<strong>Runtime Application Self-Protection (RASP)<\/strong>\u00a0is built to close.<\/p>\n<p>We\u2019ll look at:<\/p>\n<ul>\n<li>What RASP means, and how it differs from a startup-only check<\/li>\n<li>What freeRASP adds on top of device integrity checks<\/li>\n<li>How to configure it and react to threats as they happen<\/li>\n<li>How to actively block screen capture on sensitive screens<\/li>\n<li>What freeRASP can\u2019t do, and where it fits in a layered strategy<\/li>\n<\/ul>\n<h2>What RASP Actually Means<\/h2>\n<p>A startup check is a snapshot. RASP is a process that keeps watching.<\/p>\n<p>Instead of asking \u201cwas this device compromised when the app opened,\u201d a RASP library continuously monitors the running app for signs of tampering, hooking, debugging, and instrumentation \u2014 for the entire session, not just the first few hundred milliseconds.<\/p>\n<p>When it detects something, it doesn\u2019t just set a flag you can check later. It fires a callback immediately, so your app can react in real time \u2014 block a screen, end the session, or in the most aggressive configurations, terminate the process outright.<\/p>\n<p>The difference in coverage is significant. JailMonkey fires once and stops caring. freeRASP keeps watching for the entire session:<\/p>\n<p><img decoding=\"async\" loading=\"lazy\" class=\"wp-image-80628 size-large\" src=\"https:\/\/www.tothenew.com\/blog\/wp-ttn-blog\/uploads\/2026\/07\/freeRASP-vs-JailMonkey-938x1024.png\" alt=\"freeRASP vs JailMonkey\" width=\"625\" height=\"682\" srcset=\"\/blog\/wp-ttn-blog\/uploads\/2026\/07\/freeRASP-vs-JailMonkey-938x1024.png 938w, \/blog\/wp-ttn-blog\/uploads\/2026\/07\/freeRASP-vs-JailMonkey-275x300.png 275w, \/blog\/wp-ttn-blog\/uploads\/2026\/07\/freeRASP-vs-JailMonkey-768x838.png 768w, \/blog\/wp-ttn-blog\/uploads\/2026\/07\/freeRASP-vs-JailMonkey-1407x1536.png 1407w, \/blog\/wp-ttn-blog\/uploads\/2026\/07\/freeRASP-vs-JailMonkey-1877x2048.png 1877w, \/blog\/wp-ttn-blog\/uploads\/2026\/07\/freeRASP-vs-JailMonkey-624x681.png 624w\" sizes=\"(max-width: 625px) 100vw, 625px\" \/><\/p>\n<h2>freeRASP and JailMonkey, Briefly<\/h2>\n<p>If you\u2019ve already implemented JailMonkey from Part 2, you don\u2019t need to throw it away to understand this section \u2014 but it\u2019s worth knowing that freeRASP covers everything JailMonkey does (root\/jailbreak detection, emulator detection, hook detection, debug mode) and extends well past it. Root and jailbreak detection isn\u2019t the differentiator here; continuous monitoring and reaction is.<\/p>\n<p>The capabilities below are the ones JailMonkey simply doesn\u2019t have.<\/p>\n<h2>What freeRASP Adds<\/h2>\n<p>Beyond device state, freeRASP watches for things that only matter while the app is running:<\/p>\n<ul>\n<li><strong>App integrity verification<\/strong> \u2014 detecting whether the APK\/IPA itself has been repackaged, resigned, or tampered with before it reached the user<\/li>\n<li><strong>Unofficial store detection<\/strong> \u2014 flagging installs that didn\u2019t come through the Play Store or App Store<\/li>\n<li><strong>Obfuscation issue detection<\/strong> \u2014 warning you if an Android release build shipped without ProGuard\/R8<\/li>\n<li><strong>Screen capture and screen recording detection<\/strong> \u2014 and the ability to actively block it on sensitive screens<\/li>\n<li><strong>System VPN, time spoofing, and location spoofing detection<\/strong><\/li>\n<li><strong>Kill-on-bypass enforcement<\/strong>\u00a0\u2014 terminating the app if an attacker tries to hook or manipulate freeRASP\u2019s own detection mechanism<\/li>\n<\/ul>\n<p>That last point is worth sitting with. A library that only detects threats is useful. A library that detects an attacker trying to\u00a0disable the detector itself, and kills the process in response, is a meaningfully different level of protection.<\/p>\n<h2>Installing freeRASP<\/h2>\n<pre>npm install freerasp-react-native<\/pre>\n<p>or<\/p>\n<pre>yarn add freerasp-react-native<\/pre>\n<p>For iOS:<\/p>\n<pre>cd ios &amp;&amp; pod install<\/pre>\n<p>Android requires\u00a0<strong>minSdkVersion 23<\/strong>\u00a0or higher and Kotlin 2.0+. If your project is older, you&#8217;ll need to bump these in\u00a0<strong>android\/build.gradle<\/strong> before freeRASP will build.<\/p>\n<h2>Configuring freeRASP<\/h2>\n<p>Unlike JailMonkey, freeRASP needs a small amount of setup specific to your app \u2014 your package name or bundle ID, and your release signing certificate hash, so it can verify the app hasn\u2019t been resigned by someone else.<\/p>\n<pre>import { useFreeRasp } from 'freerasp-react-native';\r\n\r\nconst config = { \r\n    androidConfig: { \r\n        packageName: 'com.yourapp', \r\n        certificateHashes: [ \r\n            'YOUR_BASE64_SIGNING_CERT_HASH=', \r\n        ], \r\n    }, \r\n    iosConfig: { \r\n        appBundleId: 'com.yourapp', \r\n        appTeamId: 'YOUR_APPLE_TEAM_ID', \r\n    }, \r\n    watcherMail: 'security@yourcompany.com', \r\n    isProd: !__DEV__, \r\n    killOnBypass: true, \r\n};\r\n\r\nuseFreeRasp(config);\r\n\r\n<\/pre>\n<h2>A couple of things worth calling out:<\/h2>\n<ul>\n<li><strong>isProd: !__DEV__<\/strong> keeps you from getting blocked on a simulator during development, the same pattern we used with JailMonkey.<\/li>\n<li><strong>killOnBypass: true<\/strong>\u00a0is what enables the anti-tamper enforcement described above. Without it, freeRASP only detects \u2014 it won&#8217;t terminate the app if its own callback mechanism is hooked.<\/li>\n<\/ul>\n<h2>Reacting to Threats<\/h2>\n<p>Where JailMonkey gives you a single boolean, freeRASP gives you a callback per threat type, so you can respond differently depending on severity.<\/p>\n<pre>const actions = {\r\n    privilegedAccess: () =&gt; {\r\n        \/\/ Rooted or jailbroken\r\n        flagSession('privileged_access');\r\n    },\r\n    hooks: () =&gt; {\r\n        \/\/ Frida, Xposed, or similar attached at runtime\r\n        flagSession('hook_detected');\r\n    },\r\n    appIntegrity: () =&gt; {\r\n        \/\/ App was repackaged or resigned\r\n        flagSession('app_integrity_fail');\r\n    },\r\n    screenshot: () =&gt; {\r\n        console.warn('Screenshot taken on a sensitive screen');\r\n    },\r\n    screenRecording: () =&gt; {\r\n        console.warn('Screen recording active on a sensitive screen');\r\n    },\r\n};\r\n\r\nuseFreeRasp(config, actions);<\/pre>\n<p>One important detail:\u00a0<strong>useFreeRasp<\/strong>\u00a0is called at the top level of your component, not inside a\u00a0<strong>useEffect<\/strong>. That&#8217;s intentional \u2014 freeRASP runs checks continuously through the session, so it needs to stay mounted and active the whole time your app is open, not just fire once and unmount.<\/p>\n<h2>Actively Blocking Screen Capture<\/h2>\n<p>Detecting a screenshot after it\u2019s already been taken is useful for logging, but for something like a card number or a KYC document, you often want to prevent the capture in the first place. freeRASP can do that directly on screens where it matters:<\/p>\n<pre>import { blockScreenCapture } from 'freerasp-react-native';\r\n\r\nuseEffect(() =&gt; {\r\n    blockScreenCapture(true);\r\n    return () =&gt; blockScreenCapture(false);\r\n}, []);<\/pre>\n<p>Mount this on an account details screen or a payment confirmation screen, and it blocks capture while the component is visible, then restores normal behavior the moment the user navigates away.<\/p>\n<h2>How Should Apps Respond?<\/h2>\n<p>The same response spectrum from Part 2 applies here, just triggered continuously instead of once at startup:<\/p>\n<ul>\n<li><strong>Log and flag<\/strong> \u2014 send the threat type to your backend so it\u2019s part of the user\u2019s risk profile, without interrupting them<\/li>\n<li><strong>Restrict sensitive actions<\/strong> \u2014 block payments, password changes, or account recovery for the rest of the session<\/li>\n<li><strong>End the session<\/strong> \u2014 force a logout and require re-authentication<\/li>\n<li><strong>Terminate the app<\/strong> \u2014 what\u00a0killOnBypass\u00a0does automatically when the detection mechanism itself is attacked<\/li>\n<\/ul>\n<p>Which response fits depends on the threat. A VPN connection might just be worth logging. A confirmed hook on your authentication function is a different conversation entirely.<\/p>\n<p>Here\u2019s how to think about threat severity and the right response at each level:<\/p>\n<p><img decoding=\"async\" loading=\"lazy\" class=\"size-large wp-image-80629\" src=\"https:\/\/www.tothenew.com\/blog\/wp-ttn-blog\/uploads\/2026\/07\/freeRASP-Threat-Callback-Fired-1024x589.png\" alt=\"freeRASP Threat Callback Fired\" width=\"625\" height=\"359\" srcset=\"\/blog\/wp-ttn-blog\/uploads\/2026\/07\/freeRASP-Threat-Callback-Fired-1024x589.png 1024w, \/blog\/wp-ttn-blog\/uploads\/2026\/07\/freeRASP-Threat-Callback-Fired-300x172.png 300w, \/blog\/wp-ttn-blog\/uploads\/2026\/07\/freeRASP-Threat-Callback-Fired-768x442.png 768w, \/blog\/wp-ttn-blog\/uploads\/2026\/07\/freeRASP-Threat-Callback-Fired-1536x883.png 1536w, \/blog\/wp-ttn-blog\/uploads\/2026\/07\/freeRASP-Threat-Callback-Fired-2048x1178.png 2048w, \/blog\/wp-ttn-blog\/uploads\/2026\/07\/freeRASP-Threat-Callback-Fired-624x359.png 624w\" sizes=\"(max-width: 625px) 100vw, 625px\" \/><\/p>\n<h2>A Pricing Caveat Worth Knowing Upfront<\/h2>\n<p>freeRASP is free up to 100,000 app downloads under Talsec\u2019s Fair Usage Policy. Past that, you\u2019re expected to move to the paid RASP+ tier, which also swaps the shared free-tier binary for an app-specific hardened one \u2014 making it harder for a generic bypass script to work against your app specifically.<\/p>\n<p>If you\u2019re early-stage or still validating the approach, the free tier is genuinely fine to build and ship with. Just budget for RASP+ if you\u2019re a banking or payments app expecting real scale \u2014 it\u2019s worth knowing about before you\u2019re 90,000 downloads deep into a free integration.<\/p>\n<h2>Limitations<\/h2>\n<p>freeRASP raises the bar considerably over a startup-only check, but it\u2019s still running on a device the attacker physically controls. A sufficiently determined attacker with enough time can still attempt to patch around it \u2014\u00a0killOnBypass\u00a0makes that meaningfully harder, but &#8220;harder&#8221; isn&#8217;t &#8220;impossible.&#8221;<\/p>\n<p>The same principle from\u00a0Part 2\u00a0still holds: treat freeRASP\u2019s signals as strong client-side risk indicators, and pair them with server-side enforcement. A device claiming to be clean is not the same as a backend verifying the request actually came from your real app \u2014 which is exactly where\u00a0Part 4\u00a0picks up.<\/p>\n<h2>What\u2019s Next<\/h2>\n<p>In\u00a0Part 4, we\u2019ll look at\u00a0Firebase App Check\u00a0\u2014 how to make sure your backend only accepts requests that actually came from your real app, not from a bot script calling your API directly.<\/p>\n<h2>Final Thoughts<\/h2>\n<p>Device checks at startup catch the casual cases. Continuous runtime protection catches the attacker who waits until your app is already running before they attach their tools.<\/p>\n<p>Neither one replaces the other, and neither one is the whole story by itself. They\u2019re both client-side signals feeding into a backend that makes the real decision \u2014 which is the thread running through this entire series.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Why a Startup Check Isn\u2019t Enough In Part 2, we covered JailMonkey \u2014 a library that checks whether a device is rooted or jailbroken when your app launches. That check matters, but it has a gap. JailMonkey runs once, typically at startup. It tells you what the device looked like at that moment. It doesn\u2019t [&hellip;]<\/p>\n","protected":false},"author":1769,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"iawp_total_views":0},"categories":[5881],"tags":[8716,5853],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/posts\/80631"}],"collection":[{"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/users\/1769"}],"replies":[{"embeddable":true,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/comments?post=80631"}],"version-history":[{"count":3,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/posts\/80631\/revisions"}],"predecessor-version":[{"id":80968,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/posts\/80631\/revisions\/80968"}],"wp:attachment":[{"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/media?parent=80631"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/categories?post=80631"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/tags?post=80631"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}