react-native-shield-guard
v0.1.2
Published
A robust React Native library for detecting root, jailbreak, and emulator environments.
Maintainers
Readme
react-native-shield-guard
A React Native TurboModule that detects Android emulators at the native C++ layer. On Android it performs 7 independent native checks; on iOS and web the functions are safe no-ops that always return false / "".
Platform scope — Active detection runs on Android only. The library ships a compiled-to-spec iOS stub so your project builds on every platform without
#ifdefguards in JavaScript.
Table of Contents
- How it works
- What emulators are detected
- Requirements
- Installation
- Android Setup
- Expo (managed & bare workflow)
- Usage
- API Reference
- Understanding getResult()
- Platform Behaviour Summary
- Troubleshooting
- Contributing
- License
How it works
When you call isDetected() for the first time, the native C++ engine runs seven sequential checks. Each check appends a human-readable line to an internal list; if that list is non-empty after all checks, isDetected() returns true. You can retrieve every finding as a newline-separated string via getResult().
The seven checks (from EmulatorDetection.cpp):
| # | Check | What it examines |
|---|-------|-----------------|
| 1 | Hardware properties | ro.build.product, ro.product.manufacturer, ro.bootloader, ro.product.device, ro.hardware — compared against known emulator values |
| 2 | Mount points | /proc/mounts — looks for vboxsf (VirtualBox shared-folder driver used by many emulators) |
| 3 | CPU info | /proc/cpuinfo — searches for hypervisor, amd, and intel, which appear on x86 host machines running an Android VM |
| 4 | Emulator files | Checks for the presence of 70+ known emulator-specific files and kernel modules across /system, /data, /dev, /sys, and /boot |
| 5 | CPU architecture | ro.product.cpu.abilist — flags x86 architecture, which almost always means an emulator |
| 6 | ARM-translation layer | Checks for libhoudini.so in loaded libraries and on disk, plus ro.dalvik.vm.native.bridge and ro.dalvik.vm.isa.arm* properties — all indicators of an x86 emulator running ARM apps through a translation shim |
| 7 | Bluestacks memory scan | Reads /proc/self/maps and scans the memory regions of libc.so, libandroid_runtime.so, and libart.so for the string "bluestacks" |
All checks are implemented in native C++ (EmulatorDetection.cpp) and exposed to the React Native JS layer via a TurboModule (NativeShieldGuard.ts). The native code is compiled into libemulatordetector.so via CMake and loaded by the Java class com.reveny.emulatordetector.plugin.EmulatorDetection through System.loadLibrary("emulatordetector").
What emulators are detected
The detection database lives in EmulatorData.hpp. The library actively targets:
By hardware/product/device system properties:
sdk, google_sdk, sdk_x86, andy, nox, droid4x, vbox86p, emu64x
By hardware name (ro.hardware):
goldfish (Android Studio AVD), vbox86 (VirtualBox-based), nox, VM_x86, intel, amd, x86
By emulator-specific file presence:
| Emulator | Example fingerprint files |
|----------|--------------------------|
| Android Studio AVD | /dev/socket/qemud, /dev/qemu_pipe, hardware goldfish |
| NoxPlayer | /system/bin/nox, /system/bin/noxd, /system/lib/libnoxd.so |
| BlueStacks | /data/.bluestacks.prop, /dev/bst_gps, /sys/module/bstpgaipc |
| LDPlayer | /system/lib/hw/gps.ld.so, /system/bin/ldinit, /system/app/LDAppStore/LDAppStore.apk |
| Memu | /dev/memufp, /dev/memuguest, /system/lib/memuguest.ko |
| PhoenixOS | /system/xbin/phoenix_compat, /system/phoenixos |
| Genymotion | /system/bin/genybaseband, VirtualBox kernel modules |
| KOPlayer | /system/bin/KOPLAYER.ini |
| Droid4x | /system/bin/droid4x, /system/bin/droid4x-vbox-sf |
| VirtualBox (generic) | /lib/vboxguest.ko, /lib/vboxsf.ko, /sys/module/vboxsf |
Requirements
| Requirement | Version |
|-------------|---------|
| React Native | ≥ 0.73 (New Architecture / TurboModules required) |
| Android API | 24 (Android 7.0) minimum |
| NDK | 27.1 or newer (auto-inherited from the host app) |
| CMake | ≥ 3.13 (bundled with the Android SDK) |
| iOS | Any (library compiles; always returns false / "") |
New Architecture is required. This is a TurboModule. It will not work with the legacy bridge (
newArchEnabled=false).
Installation
# yarn (recommended)
yarn add react-native-shield-guard
# npm
npm install react-native-shield-guardThe library uses React Native autolinking — no react-native link step is needed on RN 0.73+.
Android Setup
For most projects no manual changes are needed after installation. Autolinking wires everything up automatically.
Verify autolinking resolved correctly
npx react-native configUnder "dependencies" you should see "react-native-shield-guard" with an "android" block containing a "cmakeListsPath" key.
Pinning the NDK version (optional)
The library inherits the ndkVersion from your host app's Gradle configuration. To pin an explicit version, add to android/gradle.properties in your app:
ndkVersion=27.1.12297006First-time clean build
After adding the library for the first time, a clean build clears any cached CMake state:
cd android && ./gradlew clean && cd ..
npx react-native run-androidConfirming the native library is bundled
After a successful build you can verify libemulatordetector.so was produced:
android/app/build/intermediates/merged_native_libs/debug/out/lib/arm64-v8a/libemulatordetector.soThe library is built for all four ABIs: arm64-v8a, armeabi-v7a, x86, x86_64.
Expo (managed & bare workflow)
The library ships an Expo Config Plugin (app.plugin.js). The plugin is a passthrough — it registers the library with Expo's plugin system so it is correctly resolved during expo prebuild. No additional Gradle configuration is injected because React Native autolinking handles everything automatically.
Step 1 — Register the plugin
Add the plugin to your app.json or app.config.js:
{
"expo": {
"plugins": [
"react-native-shield-guard"
]
}
}Step 2 — Run prebuild
npx expo prebuildThis generates (or regenerates) the native android/ and ios/ directories with the library already linked.
Step 3 — Build
npx expo run:androidNew Architecture — Make sure
newArchEnabled=trueis present in the generatedandroid/gradle.properties(default from Expo SDK 51+).
Usage
Basic — block the app on emulators
import React, { useEffect } from 'react';
import { Alert } from 'react-native';
import { isDetected } from 'react-native-shield-guard';
export default function App() {
useEffect(() => {
if (isDetected()) {
Alert.alert(
'Unsupported Environment',
'This application cannot run on an Android emulator.',
);
}
}, []);
// ... rest of app
}Display detection details
import React, { useEffect, useState } from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { isDetected, getResult } from 'react-native-shield-guard';
export default function App() {
const [detected, setDetected] = useState<boolean | null>(null);
const [log, setLog] = useState('');
useEffect(() => {
// Both calls are synchronous
const found = isDetected();
setDetected(found);
if (found) {
setLog(getResult()); // only meaningful after isDetected()
}
}, []);
return (
<View style={styles.container}>
<Text>Running on emulator: {detected === null ? '...' : detected ? 'YES' : 'NO'}</Text>
{log ? <Text style={styles.log}>{log}</Text> : null}
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1, justifyContent: 'center', padding: 24 },
log: { fontFamily: 'monospace', fontSize: 12, backgroundColor: '#eee', padding: 10, marginTop: 12 },
});Blocking pattern — prevent rendering entirely
The cleanest way to block an emulator is at the app entry point, before any React tree mounts:
// index.js
import { AppRegistry } from 'react-native';
import { isDetected } from 'react-native-shield-guard';
import App from './App';
import BlockedScreen from './BlockedScreen';
import { name as appName } from './app.json';
// isDetected() is synchronous — safe to call here
const Root = isDetected() ? BlockedScreen : App;
AppRegistry.registerComponent(appName, () => Root);API Reference
Import everything from 'react-native-shield-guard'.
isDetected(): boolean
Runs all 7 native emulator checks on Android. Returns true if any check finds emulator-specific evidence.
- Synchronous — no Promise, no callback.
- Stateful — results are cached in the native layer. Calling it multiple times is safe and cheap after the first call.
- On iOS / web always returns
false(no native code runs).
import { isDetected } from 'react-native-shield-guard';
const onEmulator: boolean = isDetected();| Return | Meaning |
|--------|---------|
| true | One or more emulator indicators were found on this Android device. |
| false | No emulator indicators found, or the platform is iOS / web. |
getResult(): string
Returns a newline-separated string listing every emulator indicator that was found during the last isDetected() call.
- Returns
""if nothing was detected, or ifisDetected()has not been called yet. - Must be called after
isDetected()— the internal detection list is only populated duringisDetected(). - On iOS / web always returns
"".
import { isDetected, getResult } from 'react-native-shield-guard';
if (isDetected()) {
const details = getResult();
console.log(details);
// Example output on an Android Studio AVD:
// - Detected Product: sdk
// - Detected Hardware: goldfish
// - Detected x86 Architecture
// - Detected Hypervisor in cpuinfo
}Understanding getResult()
Each line corresponds to one positive finding from the 7 native checks:
| Output line | Source check |
|---|---|
| - Detected Product: <value> | Hardware props — matched ro.build.product |
| - Detected Manufacturer: <value> | Hardware props — matched ro.product.manufacturer |
| - Detected Bootloader: <value> | Hardware props — ro.bootloader == "nox" |
| - Detected Device: <value> | Hardware props — matched ro.product.device |
| - Detected Hardware: <value> | Hardware props — matched ro.hardware |
| - Detected VM Module in Mounts | Mount check — vboxsf in /proc/mounts |
| - Detected Hypervisor in cpuinfo | CPU info — "hypervisor" in /proc/cpuinfo |
| - Detected Host CPU in cpuinfo | CPU info — "amd" or "intel" in /proc/cpuinfo |
| - Detected Emulator File: <path> | File check (primary list) |
| - Detected Emulator File (2): <path> | File check (extended list for newer emulator versions) |
| - Detected Nox Property File | File check — "nox" string in /data/property/ listing |
| - Detected x86 Architecture | ABI check — x86 in ro.product.cpu.abilist |
| - Detected ARM Translation | ARM translation — libhoudini.so in loaded process libraries |
| - Detected ARM Translation Property | ARM translation — ro.dalvik.vm.native.bridge is non-empty |
| - Detected ARM Translation Library | ARM translation — libhoudini.so exists on disk |
| - Detected ARM Translation Property (2) | ARM translation — ro.dalvik.vm.isa.arm mapped to x86 |
| - Detected Bluestacks Memory | Memory scan — "bluestacks" string found in process memory maps |
Platform Behaviour Summary
| Platform | isDetected() | getResult() | C++ code runs |
|----------|---------------|--------------|---------------|
| Android | Runs all 7 checks | Returns detection lines | ✅ Yes |
| iOS | Always false | Always "" | ❌ Stub only |
| Web | Always false | Always "" | ❌ JS fallback |
The iOS stub is intentional. A TurboModule must have a native implementation on every registered platform for the project to compile, so the library ships a valid but inert Obj-C++ implementation that satisfies the codegen-generated protocol (NativeShieldGuardSpec).
Troubleshooting
TurboModuleRegistry.getEnforcing … 'ShieldGuard' could not be found
The New Architecture is required. Check android/gradle.properties:
newArchEnabled=trueBuild error: react_codegen_ShieldGuardSpec already exists
This is a monorepo-specific issue. It occurs when the app's react.root is set to the repository root, causing React Native to run codegen twice (once for the library, once for the app) inside the same Gradle build.
In android/app/build.gradle of the host/example app, ensure root points to the app directory, not the monorepo root:
react {
// Correct: points to the directory that contains the app's package.json
root = file("../..")
// Also correct the paths of related directories accordingly:
reactNativeDir = file("../../../node_modules/react-native")
codegenDir = file("../../../node_modules/@react-native/codegen")
}After fixing, run ./gradlew clean before rebuilding.
Build error: APP_BUILD_SCRIPT points to an unknown file
This error comes from the legacy ndk-build system failing when the project path contains spaces. The library has already been migrated to CMake (android/CMakeLists.txt). If you see this error, ensure you are on a version of the library that uses CMake (the externalNativeBuild block in android/build.gradle should reference cmake, not ndkBuild).
isDetected() returns false on a known emulator
The detection database is maintained in EmulatorData.hpp. Some newer emulator versions (e.g. BlueStacks 5+, modern cloud-based Android runners) have improved at disguising themselves. If a particular emulator is not being caught:
- Run the app on it and print
getResult()to see which checks did fire. - Inspect the emulator's system properties (
adb shell getprop) and look for values not yet inEmulatorData.hpp. - Add the missing signatures following the Contributing guide.
Warning: CXX5304 — This version only understands SDK XML version 4
This is a non-fatal warning produced when the bundled CMake version is slightly behind the installed Android SDK tools. The build succeeds normally. To eliminate it, update your Android SDK Build Tools via Android Studio → SDK Manager → SDK Tools → Android SDK Build Tools.
Contributing
Adding a new emulator to the detection database
All detection data lives in:
android/src/main/jni/Include/EmulatorData.hppAdd the emulator's identifying values to the appropriate vectors:
// products vector — value of ro.build.product
inline std::vector<std::string> products {
// ... existing entries ...
"your_emulator_product",
};
// manufacturers vector — value of ro.product.manufacturer
// hardwares vector — value of ro.hardware
// deviceInfo vector — value of ro.product.device
// emulatorFiles vector — emulator-specific file pathsAdding a new detection technique
- Write a
void checkYourTechnique()function inEmulatorDetection.cppthat callsdetections.push_back(...)for each finding. - Declare the function in
EmulatorDetection.hpp. - Call it from
isDetected()alongside the existing checks.
Running the example app locally
# From the library root
yarn install
yarn example androidLicense
MIT — see LICENSE for details.
