← Back to list

Securing Android React Native Apps: Detect USB Debugging & Developer Options using TurboModules +…

In today’s mobile landscape, particularly within finance, banking, and enterprise sectors, security is non-negotiable. Many high-security…

Jatin Bhuva · 2025-07-21 03:31 · 108 claps · 6.4 min read
#react-native #mobile-app-security #react-native-modules #android #usb-debugging
Open on Medium ↗
Wiki topics: ECO · Economy · General 💻 · Programming 🌐 · Web Development 📱 · Mobile Development

Securing Android React Native Apps: Detect USB Debugging & Developer Options using TurboModules + JSI + Codegen

In today’s mobile landscape, particularly within finance, banking, and enterprise sectors, security is non-negotiable. Many high-security Android apps display warnings — or block access entirely — when USB Debugging or Developer Options are enabled. These settings can expose devices to serious threats like reverse engineering, data leaks, or man-in-the-middle (MITM) attacks.

React Native provides immense flexibility for cross-platform development, but it doesn’t offer built-in support for detecting device-level settings. That’s where native modules come in, and with the new React Native architecture, TurboModules and JSI provide a cleaner, more efficient way to communicate between native code and JavaScript.

In this guide, we’ll:

  • Understand why detecting Developer Options and USB Debugging is critical
  • Learn why JavaScript-only detection is not possible
  • Build a secure TurboModule-based Native Module for Android
  • Integrate the native module into React Native

Why Check Developer Options and USB Debugging?

Enabling Developer Options or USB Debugging opens the door for:

  • Reverse Engineering: With USB Debugging, anyone can connect ADB and pull app data, logs, or even decompile your APK.
  • Data Theft: Sensitive user data (like banking credentials or tokens) can be exposed via ADB commands.
  • Security Loopholes: Rooted devices or ones with Dev Options enabled can bypass intended app behaviour.

Hence, security-conscious apps often block usage when Developer Options or USB Debugging is enabled.

🚫 Why Not Use Pure JS?

React Native doesn’t provide APIs to access system-level settings like USB Debugging or Developer Options. These are Android platform-specific settings, accessible only via native code.

🚀 Enter TurboModules + JSI + Codegen (New Architecture)

TurboModules and JSI (JavaScript Interface) are part of the React Native New Architecture, which removes the overhead of the bridge and allows synchronous and faster native-to-JS communication.

Benefits:

  • No need to serialise/deserialise across the bridge
  • Access native methods directly in JS
  • Faster, cleaner communication

📌 Note: *This implementation requires React Native version 0.73+ and targets Android only, as iOS does not expose APIs to detect Developer Mode status programmatically due to Apple’s privacy constraints.*

Step-by-Step: Build Android TurboModule for Detection

📁 Step 1: Declare TypeScript Specification (Codegen Input)

The first and most critical step when working with TurboModules in React Native’s New Architecture is creating a typed JavaScript (or TypeScript) specification. This file defines the contract between your JS and native code.

✅ Purpose of the Spec File

This spec acts as:

  • A schema for Codegen to generate platform-specific boilerplate
  • A bridge contract that ensures type safety between JS ↔️ native
  • A declaration of what methods are exposed to JS

⚠️ Important Constraints and Naming Rules:

There are two critical rules you must follow for Codegen to work correctly:

  1. The file name must begin with Native If you don’t prefix your spec file with NativeCodegen will skip it, and no native classes will be generated. ✅ Correct: NativeDeveloperOptionCheck.ts ❌ Incorrect: DeveloperOptionCheck.ts
  2. The default export must use TurboModuleRegistry.getEnforcing<>() This ensures that React Native expects the module to exist and throws if it does not.

🧱 Example Spec File

Create a specs folder in the root of your project, and inside that folder, create NativeDeveloperOptionCheck.ts:

import type {TurboModule} from 'react-native';
import {TurboModuleRegistry} from 'react-native';
/**
 * Declares the TurboModule methods accessible from JS.
 * These methods are implemented natively (in Kotlin/Java for Android).
 */
export interface Spec extends TurboModule {
  isDeveloperOptionsEnabled(): boolean; 
  isUsbDebuggingEnabled(): boolean;  
  openDeveloperOptions(): void;         
}
export default TurboModuleRegistry.getEnforcing<Spec>('DeveloperOptionCheck');

📁 Step 2: Configure Codegen

In your package.json add codegenConfig:

{
  "name": "TurbomoduleExample",
  "version": "0.0.1",
  "private": true,
  "codegenConfig": { 
    "name": "DeveloperOptionCheckSpec",
    "type": "modules",
    "jsSrcsDir": "specs",
    "android": {
      "javaPackageName": "com.developeroption"
    }
  },
  "scripts": {
    "android": "react-native run-android",
    "ios": "react-native run-ios",
    "lint": "eslint .",
    "start": "react-native start",
    "test": "jest"
  },
  "dependencies": {
    "react": "19.1.0",
    "react-native": "0.80.1",
    "@react-native/new-app-screen": "0.80.1"
  },
  "devDependencies": {
     ...
    "typescript": "5.0.4"
     ...
  },
  "engines": {
    "node": ">=18"
  }
}

Then run:

cd android
./gradlew generateCodegenArtifactsFromSchema

It will generate native code interfaces from a spec file for Turbo Native Modules and Fabric Native Components

  • NativeYourModuleNameSpec.java

You can verify whether this file was created or not at android/app/build/generated/source/codegen/java/com/developeroption/NativeDeveloperOptionCheckSpec.java

It will be something similar to the file below. Note: You can’t change anything in this file


/**
 * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
 *
 * Do not edit this file as changes may cause incorrect behavior and will be lost
 * once the code is regenerated.
 *
 * @generated by codegen project: GenerateModuleJavaSpec.js
 *
 * @nolint
 */

package com.developeroption;

import com.facebook.proguard.annotations.DoNotStrip;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.turbomodule.core.interfaces.TurboModule;
import javax.annotation.Nonnull;

public abstract class NativeDeveloperOptionCheckSpec extends ReactContextBaseJavaModule implements TurboModule {
  public static final String NAME = "DeveloperOptionCheck";

  public NativeDeveloperOptionCheckSpec(ReactApplicationContext reactContext) {
    super(reactContext);
  }

  @Override
  public @Nonnull String getName() {
    return NAME;
  }

  @ReactMethod(isBlockingSynchronousMethod = true)
  @DoNotStrip
  public abstract boolean isDeveloperOptionsEnabled();

  @ReactMethod(isBlockingSynchronousMethod = true)
  @DoNotStrip
  public abstract boolean isUsbDebuggingEnabled();

  @ReactMethod
  @DoNotStrip
  public abstract void openDeveloperOptions();
}

📁 Step 3: Native Implementation in Android (Kotlin)

Now it’s time to write some Android platform code to detect the developer and USB debugging options and ask the user to disable them.

The first step is to implement the generated NativeDeveloperOptionCheckSpec interface:

Create DeveloperOptionCheckModule.kt file at android/app/src/main/java/com/yourprojectpackage

package com.turbomoduleexample // this line should be as per your project package

import android.content.Intent
import android.provider.Settings
import com.developeroption.NativeDeveloperOptionCheckSpec
import com.facebook.react.turbomodule.core.interfaces.TurboModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.module.annotations.ReactModule

@ReactModule(name = NativeDeveloperOptionCheckModule.NAME)
class NativeDeveloperOptionCheckModule(
    private val reactContext: ReactApplicationContext
) : NativeDeveloperOptionCheckSpec(reactContext), TurboModule {

    companion object {
        const val NAME = NativeDeveloperOptionCheckSpec.NAME
    }

    // This method will check if developer option is enabled or not
    override fun isDeveloperOptionsEnabled(): Boolean {
        return  Settings.Global.getInt(
            reactContext.contentResolver,
            Settings.Global.DEVELOPMENT_SETTINGS_ENABLED,
            0
        ) != 0

    }

    // This method will check if USB Debugging is enabled or not
    override fun isUsbDebuggingEnabled(): Boolean {
        return Settings.Secure.getInt(
            reactContext.contentResolver,
            Settings.Global.ADB_ENABLED,
            0
        ) == 1
    }

    // This method will redirect user to developer settings page in android device
    override  fun openDeveloperOptions() {
        val intent = Intent(Settings.ACTION_APPLICATION_DEVELOPMENT_SETTINGS)
        reactContext.currentActivity?.startActivity(intent)
    }

}

Next, we have to create NativeDeveloperOptionCheckPackage. It provides an object to register our Module in the React Native runtime, by wrapping it as a Base Native Package:

package com.turbomoduleexample

import com.facebook.react.BaseReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.module.model.ReactModuleInfo
import com.facebook.react.module.model.ReactModuleInfoProvider

class NativeDeveloperOptionCheckPackage: BaseReactPackage() {

    override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? =
        if (name == NativeDeveloperOptionCheckModule.NAME) {
            NativeDeveloperOptionCheckModule(reactContext)
        } else {
            null
        }

    override fun getReactModuleInfoProvider() : ReactModuleInfoProvider {
        return ReactModuleInfoProvider {

            mapOf(
                NativeDeveloperOptionCheckModule.NAME to ReactModuleInfo(
                    name = NativeDeveloperOptionCheckModule.NAME,
                    className = NativeDeveloperOptionCheckModule.NAME,
                    canOverrideExistingModule = false,
                    needsEagerInit = false,
                    isCxxModule = false,
                    isTurboModule = true,
                    hasConstants = false
                )
            )
        }
    }
}
}

Then we have to register our package in MainApplication.kt

package com.turbomoduleexample

import android.app.Application
import com.facebook.react.PackageList
import com.facebook.react.ReactApplication
import com.facebook.react.ReactHost
import com.facebook.react.ReactNativeHost
import com.facebook.react.ReactPackage
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load
import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost
import com.facebook.react.defaults.DefaultReactNativeHost
import com.facebook.soloader.SoLoader
 Above line not mandatory but if android studio gives error in package list while adding our package then add import statement

class MainApplication : Application(), ReactApplication {

  override val reactNativeHost: ReactNativeHost =
      object : DefaultReactNativeHost(this) {
        override fun getPackages(): List<ReactPackage> =
            PackageList(this).packages.apply {
              add(NativeDeveloperOptionCheckPackage()) // Add this line (Our package)
            }

        override fun getJSMainModuleName(): String = "index"

        override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG

        override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED
        override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED
      }

  override val reactHost: ReactHost
    get() = getDefaultReactHost(applicationContext, reactNativeHost)

  override fun onCreate() {
    super.onCreate()
    SoLoader.init(this, false)
    if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
      // If you opted-in for the New Architecture, we load the native entry point for this app.
      load()
    }
  }
}

That’s it, we have done all the things now, just use our native module in React Native.

📁 Step 4: Use it in React Native

import React, {useEffect, useRef} from 'react';
import {Text, View, Alert, AppState, AppStateStatus} from 'react-native';
import DeveloperOptionCheck from './specs/NativeDeveloperOptionCheck';

const App = () => {
  const appState = useRef(AppState.currentState);

  const checkDeveloperOptions = () => {
    const isDeveloperOptionsEnabled = DeveloperOptionCheck.isDeveloperOptionsEnabled();
    const isUsbDebuggingEnabled = DeveloperOptionCheck.isUsbDebuggingEnabled();
    if (isDeveloperOptionsEnabled||isUsbDebuggingEnabled) // Both not required just for presentation 
     { 
      Alert.alert(
        'Security Warning',
        'Developer Options or USB Debugging is enabled. Please disable them for secure access.',
        [{text: 'Go to Settings', onPress: () => DeveloperOptionCheck.openDeveloperOptions()}],
      );
    }
  };

  useEffect(() => {
    checkDeveloperOptions();

    const subscription = AppState.addEventListener('change', (nextAppState: AppStateStatus) => {
      if (appState.current.match(/inactive|background/) && nextAppState === 'active') {
        checkDeveloperOptions();
      }
      appState.current = nextAppState;
    });

    return () => subscription.remove();
  }, []);

  return (
    <View style={{flex: 1, justifyContent: 'center', alignItems: 'center'}}>
      <Text>Welcome to Secure React Native</Text>
    </View>
  );
};

export default App;

✅ Test Your Implementation

  1. Enable USB Debugging or Developer Options on Android
  2. Run your app from Android Studio
  3. Launch your app
  4. You should see a security alert if enabled
  5. Clicking the button will open Android’s Developer Options screen

🧪 Pro Tip: How to Block an App Completely

You can conditionally block access to your entire app using this check:

if (DeveloperOptionCheck.isDeveloperOptionsEnabled()) {
  return <Text>Access Denied: USB Debugging is enabled</Text>;
}
return (
 <YourAppNavigator/>
)

🎁 Conclusion

With TurboModules + JSI+Codegen, you can access low-level Android APIs securely and efficiently, without relying on clunky NativeModules or third-party libraries.

By detecting USB Debugging and Developer Options:

  • You enhance app integrity
  • You prevent reverse engineering and data leaks
  • You align with best practices used by banks, wallets, and secure apps

Thanks for reading! I hope this guide helped you securely detect Developer Options and USB Debugging on Android using React Native’s New Architecture (TurboModules + JSI + Codegen). If you have any questions, feedback, or suggestions, feel free to ask in the comments!


메타데이터
post_id
1d0a2cb3cd45
slug
securing-android-react-native-apps-detect-usb-debugging-developer-options-using-turbomodules-1d0a2cb3cd45
url
https://medium.com/@jatinbhuva/securing-android-react-native-apps-detect-usb-debugging-developer-options-using-turbomodules-1d0a2cb3cd45
canonical_url
https://medium.com/@jatinbhuva/securing-android-react-native-apps-detect-usb-debugging-developer-options-using-turbomodules-1d0a2cb3cd45
author_url
https://medium.com/@jatinbhuva
status
ok
fetched_at
2026-08-23 01:57:10