react-native-rootbeer-checkroot 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,799 @@
1
+ # react-native-root-beer
2
+
3
+ A **React Native New Architecture (TurboModule)** wrapper around the battle-tested Android root detection library [`com.scottyab:rootbeer-lib`](https://github.com/scottyab/rootbeer). It exposes 12 granular detection methods over JSI (JavaScript Interface) — no bridge overhead, no threading surprises.
4
+
5
+ > **⚠️ Android Only.** This library is strictly an Android module. Its TurboModule is registered with `TurboModuleRegistry.getEnforcing`, which means calling any method on a non-Android platform (iOS, web) will throw a runtime error. All calls must be guarded with `Platform.OS === 'android'`.
6
+
7
+ ---
8
+
9
+ ## Table of Contents
10
+
11
+ - [How It Works](#how-it-works)
12
+ - [Requirements](#requirements)
13
+ - [Installation](#installation)
14
+ - [Bare React Native](#bare-react-native)
15
+ - [Expo (Managed & Bare Workflow)](#expo-managed--bare-workflow)
16
+ - [Native Android Configuration](#native-android-configuration)
17
+ - [Usage Guide](#usage-guide)
18
+ - [Quick Start — Single Root Check](#quick-start--single-root-check)
19
+ - [Granular Detection Scan](#granular-detection-scan)
20
+ - [Reusable React Hook](#reusable-react-hook)
21
+ - [Navigation Guard Pattern](#navigation-guard-pattern)
22
+ - [API Reference](#api-reference)
23
+ - [Detection Method Deep Dive](#detection-method-deep-dive)
24
+ - [Platform Guard Reference](#platform-guard-reference)
25
+ - [Architecture](#architecture)
26
+ - [Troubleshooting](#troubleshooting)
27
+
28
+ ---
29
+
30
+ ## How It Works
31
+
32
+ `react-native-root-beer` is built as a **TurboModule** using React Native's New Architecture (Codegen). Instead of going through the old asynchronous JS bridge, every method call crosses the native boundary via JSI — synchronously and with zero serialization overhead.
33
+
34
+ The library ships its own `android/build.gradle` which declares `com.scottyab:rootbeer-lib:0.1.2` as a Gradle `implementation` dependency. When any consumer app installs this package, Gradle resolves and downloads the RootBeer SDK automatically as a **transitive dependency**. You never need to touch your app's `android/` folder.
35
+
36
+ ---
37
+
38
+ ## Requirements
39
+
40
+ | Requirement | Version |
41
+ |---|---|
42
+ | React Native | ≥ 0.73 (New Architecture required) |
43
+ | Android `minSdkVersion` | **24** (Android 7.0 Nougat) |
44
+ | Android `compileSdkVersion` | 36 |
45
+ | Java source/target compatibility | **17** |
46
+ | Kotlin | 2.0.21 (bundled in the library) |
47
+ | `react-native-root-beer` | 0.1.0 |
48
+
49
+ > **New Architecture is mandatory.** This library is a TurboModule — it does not have an Old Architecture (bridge) fallback. Ensure `newArchEnabled=true` is set in your project's `android/gradle.properties`.
50
+
51
+ ---
52
+
53
+ ## Installation
54
+
55
+ ### Bare React Native
56
+
57
+ **Step 1 — Install the package**
58
+
59
+ ```bash
60
+ # npm
61
+ npm install react-native-root-beer
62
+
63
+ # yarn
64
+ yarn add react-native-root-beer
65
+ ```
66
+
67
+ **Step 2 — Enable New Architecture**
68
+
69
+ Open `android/gradle.properties` and confirm the following line is present and not commented out:
70
+
71
+ ```properties
72
+ # android/gradle.properties
73
+ newArchEnabled=true
74
+ ```
75
+
76
+ **Step 3 — Rebuild the native layer**
77
+
78
+ ```bash
79
+ cd android && ./gradlew clean
80
+ cd ..
81
+ npx react-native run-android
82
+ ```
83
+
84
+ Gradle will automatically resolve `com.scottyab:rootbeer-lib:0.1.2` from Maven Central during this build. No manual `build.gradle` edits are required.
85
+
86
+ ---
87
+
88
+ ### Expo (Managed & Bare Workflow)
89
+
90
+ Because this library contains native Android code, it **cannot** run in the Expo Go client. You must use a [development build](https://docs.expo.dev/develop/development-builds/introduction/).
91
+
92
+ **Step 1 — Install the package**
93
+
94
+ ```bash
95
+ npx expo install react-native-root-beer
96
+ ```
97
+
98
+ **Step 2 — Enable New Architecture in your Expo config**
99
+
100
+ In `app.json` or `app.config.js`, ensure the new architecture flag is set:
101
+
102
+ ```json
103
+ {
104
+ "expo": {
105
+ "android": {
106
+ "newArchEnabled": true
107
+ }
108
+ }
109
+ }
110
+ ```
111
+
112
+ **Step 3 — Run prebuild**
113
+
114
+ ```bash
115
+ npx expo prebuild --platform android
116
+ ```
117
+
118
+ > **Why this is safe with Expo:** The library declares its own Gradle dependency inside its own `android/build.gradle`. When `expo prebuild` (re)generates the native `android/` folder, it does **not** wipe library-level Gradle files — only the app-level files are regenerated. Your native configuration is therefore **upgrade-safe** and survives every future `expo prebuild` run.
119
+
120
+ **Step 4 — Build a development client or production build**
121
+
122
+ ```bash
123
+ # Development build (EAS)
124
+ eas build --profile development --platform android
125
+
126
+ # Or run locally
127
+ npx expo run:android
128
+ ```
129
+
130
+ ---
131
+
132
+ ## Native Android Configuration
133
+
134
+ **None required.**
135
+
136
+ This is the primary design goal of `react-native-root-beer`. The `com.scottyab:rootbeer-lib:0.1.2` dependency is declared inside the library's own `android/build.gradle`:
137
+
138
+ ```groovy
139
+ // This lives inside the library — you do not write this yourself.
140
+ dependencies {
141
+ implementation "com.facebook.react:react-android"
142
+ implementation "com.scottyab:rootbeer-lib:0.1.2"
143
+ }
144
+ ```
145
+
146
+ Gradle's dependency resolution propagates this to your app's build graph automatically.
147
+
148
+ **No `AndroidManifest.xml` permissions are needed.** The library's own manifest is empty — root detection is performed entirely through file-system introspection, system property reads, and process execution, none of which require declared permissions on Android.
149
+
150
+ ---
151
+
152
+ ## Usage Guide
153
+
154
+ ### Quick Start — Single Root Check
155
+
156
+ The simplest possible integration. Use `RootBeer.isRooted()` to run all available checks in one call.
157
+
158
+ ```typescript
159
+ import React, { useEffect, useState } from 'react';
160
+ import { Platform, Text, View, StyleSheet } from 'react-native';
161
+ import { RootBeer } from 'react-native-root-beer';
162
+
163
+ export default function App() {
164
+ const [isRooted, setIsRooted] = useState<boolean | null>(null);
165
+
166
+ useEffect(() => {
167
+ // Always guard with Platform.OS — this library is Android-only.
168
+ if (Platform.OS === 'android') {
169
+ const result = RootBeer.isRooted();
170
+ setIsRooted(result);
171
+ }
172
+ }, []);
173
+
174
+ if (Platform.OS !== 'android') {
175
+ return (
176
+ <View style={styles.container}>
177
+ <Text>Root detection is only available on Android.</Text>
178
+ </View>
179
+ );
180
+ }
181
+
182
+ return (
183
+ <View style={styles.container}>
184
+ {isRooted === null ? (
185
+ <Text>Checking device integrity...</Text>
186
+ ) : (
187
+ <Text style={isRooted ? styles.danger : styles.safe}>
188
+ {isRooted ? '⚠️ Device is rooted' : '✅ Device is not rooted'}
189
+ </Text>
190
+ )}
191
+ </View>
192
+ );
193
+ }
194
+
195
+ const styles = StyleSheet.create({
196
+ container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
197
+ danger: { color: '#e53e3e', fontSize: 18, fontWeight: 'bold' },
198
+ safe: { color: '#38a169', fontSize: 18, fontWeight: 'bold' },
199
+ });
200
+ ```
201
+
202
+ > **Note:** All `RootBeer` methods are **synchronous**. They return `boolean` directly — no `await`, no `.then()`.
203
+
204
+ ---
205
+
206
+ ### Granular Detection Scan
207
+
208
+ For security-sensitive applications, run each check individually to understand *why* a device is flagged and log the results for your backend.
209
+
210
+ ```typescript
211
+ import { Platform } from 'react-native';
212
+ import { RootBeer } from 'react-native-root-beer';
213
+
214
+ interface RootScanResult {
215
+ isRooted: boolean;
216
+ isRootedWithoutBusyBoxCheck: boolean;
217
+ hasRootManagementApps: boolean;
218
+ hasDangerousApps: boolean;
219
+ hasTestKeys: boolean;
220
+ hasSuBinary: boolean;
221
+ hasMagiskBinary: boolean;
222
+ suExecutes: boolean;
223
+ hasDangerousProps: boolean;
224
+ hasRWPaths: boolean;
225
+ hasRootCloakingApps: boolean;
226
+ hasBusyBox: boolean;
227
+ }
228
+
229
+ /**
230
+ * Runs every available RootBeer check and returns a structured result.
231
+ * Call this on Android only.
232
+ */
233
+ export function performFullRootScan(): RootScanResult {
234
+ if (Platform.OS !== 'android') {
235
+ throw new Error('performFullRootScan() is only supported on Android.');
236
+ }
237
+
238
+ return {
239
+ // High-level composite checks
240
+ isRooted: RootBeer.isRooted(),
241
+ isRootedWithoutBusyBoxCheck: RootBeer.isRootedWithoutBusyBoxCheck(),
242
+
243
+ // Installed app checks
244
+ hasRootManagementApps: RootBeer.detectRootManagementApps(),
245
+ hasDangerousApps: RootBeer.detectPotentiallyDangerousApps(),
246
+ hasRootCloakingApps: RootBeer.detectRootCloakingApps(),
247
+
248
+ // Build / ROM checks
249
+ hasTestKeys: RootBeer.detectTestKeys(),
250
+
251
+ // Binary presence checks (static — does not execute anything)
252
+ hasSuBinary: RootBeer.checkForSuBinary(),
253
+ hasMagiskBinary: RootBeer.checkForMagiskBinary(),
254
+ hasBusyBox: RootBeer.checkForBinary('busybox'),
255
+
256
+ // Runtime probe (actually attempts to run `su`)
257
+ suExecutes: RootBeer.checkSuExists(),
258
+
259
+ // System property & filesystem checks
260
+ hasDangerousProps: RootBeer.checkForDangerousProps(),
261
+ hasRWPaths: RootBeer.checkForRWPaths(),
262
+ };
263
+ }
264
+
265
+ // --- Usage ---
266
+ if (Platform.OS === 'android') {
267
+ const scan = performFullRootScan();
268
+
269
+ if (scan.isRooted) {
270
+ console.warn('Root indicators detected:', scan);
271
+ // Send `scan` to your analytics / security backend for forensics
272
+ }
273
+ }
274
+ ```
275
+
276
+ ---
277
+
278
+ ### Reusable React Hook
279
+
280
+ Encapsulate all detection logic in a custom hook for clean, idiomatic React consumption across your app.
281
+
282
+ ```typescript
283
+ // hooks/useRootDetection.ts
284
+ import { useEffect, useState } from 'react';
285
+ import { Platform } from 'react-native';
286
+ import { RootBeer } from 'react-native-root-beer';
287
+
288
+ interface RootDetectionState {
289
+ /** True if any root indicator was found. */
290
+ isRooted: boolean;
291
+ /** True while the detection is running (first render only). */
292
+ isChecking: boolean;
293
+ /** True if the check completed without errors. */
294
+ isReady: boolean;
295
+ /** Populated if an unexpected error occurred during detection. */
296
+ error: Error | null;
297
+ }
298
+
299
+ /**
300
+ * Runs `RootBeer.isRooted()` once on mount.
301
+ * On non-Android platforms, `isRooted` is always `false` and `isReady`
302
+ * is `false` to prevent false security guarantees.
303
+ */
304
+ export function useRootDetection(): RootDetectionState {
305
+ const [state, setState] = useState<RootDetectionState>({
306
+ isRooted: false,
307
+ isChecking: Platform.OS === 'android',
308
+ isReady: false,
309
+ error: null,
310
+ });
311
+
312
+ useEffect(() => {
313
+ if (Platform.OS !== 'android') {
314
+ return;
315
+ }
316
+
317
+ try {
318
+ const isRooted = RootBeer.isRooted();
319
+ setState({ isRooted, isChecking: false, isReady: true, error: null });
320
+ } catch (err) {
321
+ setState({
322
+ isRooted: false,
323
+ isChecking: false,
324
+ isReady: false,
325
+ error: err instanceof Error ? err : new Error(String(err)),
326
+ });
327
+ }
328
+ }, []);
329
+
330
+ return state;
331
+ }
332
+ ```
333
+
334
+ ```typescript
335
+ // components/SecureScreen.tsx
336
+ import React from 'react';
337
+ import { ActivityIndicator, Text, View } from 'react-native';
338
+ import { useRootDetection } from '../hooks/useRootDetection';
339
+
340
+ export function SecureScreen() {
341
+ const { isRooted, isChecking, error } = useRootDetection();
342
+
343
+ if (isChecking) {
344
+ return <ActivityIndicator size="large" />;
345
+ }
346
+
347
+ if (error) {
348
+ // Treat detection failure conservatively — block access
349
+ return <Text>Security check failed. Access denied.</Text>;
350
+ }
351
+
352
+ if (isRooted) {
353
+ return (
354
+ <View>
355
+ <Text>⚠️ This app cannot run on a rooted device.</Text>
356
+ </View>
357
+ );
358
+ }
359
+
360
+ return <Text>✅ Device integrity verified. Welcome!</Text>;
361
+ }
362
+ ```
363
+
364
+ ---
365
+
366
+ ### Navigation Guard Pattern
367
+
368
+ Block access to sensitive screens at the navigation level. This example uses React Navigation, but the pattern applies to any router.
369
+
370
+ ```typescript
371
+ // navigation/RootGuard.tsx
372
+ import React, { useEffect } from 'react';
373
+ import { Platform, Alert } from 'react-native';
374
+ import { useNavigation } from '@react-navigation/native';
375
+ import { RootBeer } from 'react-native-root-beer';
376
+
377
+ interface RootGuardProps {
378
+ children: React.ReactNode;
379
+ /** Screen name to redirect to when root is detected. */
380
+ fallbackScreen?: string;
381
+ }
382
+
383
+ /**
384
+ * Wraps a screen and redirects away if the device is rooted.
385
+ * Must be rendered inside a React Navigation navigator.
386
+ */
387
+ export function RootGuard({ children, fallbackScreen = 'Home' }: RootGuardProps) {
388
+ const navigation = useNavigation();
389
+
390
+ useEffect(() => {
391
+ if (Platform.OS !== 'android') {
392
+ return;
393
+ }
394
+
395
+ const isRooted = RootBeer.isRooted();
396
+
397
+ if (isRooted) {
398
+ Alert.alert(
399
+ 'Security Warning',
400
+ 'This feature is not available on rooted devices.',
401
+ [{ text: 'OK', onPress: () => navigation.navigate(fallbackScreen as never) }],
402
+ { cancelable: false }
403
+ );
404
+ }
405
+ }, [navigation, fallbackScreen]);
406
+
407
+ return <>{children}</>;
408
+ }
409
+ ```
410
+
411
+ ```typescript
412
+ // Inside your screen component
413
+ function PaymentScreen() {
414
+ return (
415
+ <RootGuard fallbackScreen="Home">
416
+ {/* Sensitive payment UI renders here */}
417
+ </RootGuard>
418
+ );
419
+ }
420
+ ```
421
+
422
+ ---
423
+
424
+ ## API Reference
425
+
426
+ All methods are synchronous and return `boolean`. They are exposed on the `RootBeer` object (`as const`) imported from `react-native-root-beer`.
427
+
428
+ ```typescript
429
+ import { RootBeer } from 'react-native-root-beer';
430
+ ```
431
+
432
+ ### Method Summary
433
+
434
+ | Method | Parameters | Returns | Description |
435
+ |---|---|---|---|
436
+ | `isRooted()` | — | `boolean` | Runs **all** detection checks, including BusyBox. The primary, recommended check. |
437
+ | `isRootedWithoutBusyBoxCheck()` | — | `boolean` | Runs all checks **except** BusyBox. Use when BusyBox causes false positives. |
438
+ | `detectRootManagementApps()` | — | `boolean` | Scans installed apps for known root managers (SuperSU, Magisk Manager, KingRoot, etc.). |
439
+ | `detectPotentiallyDangerousApps()` | — | `boolean` | Scans installed apps for apps commonly associated with root exploitation. |
440
+ | `detectRootCloakingApps()` | — | `boolean` | Scans installed apps for apps that conceal root from other apps (RootCloak, Hide My Root). |
441
+ | `detectTestKeys()` | — | `boolean` | Checks `android.os.Build.TAGS` for `"test-keys"` — a signature of unofficial/custom ROMs. |
442
+ | `checkForSuBinary()` | — | `boolean` | Scans a list of known filesystem paths for the `su` executable. **Static — does not execute anything.** |
443
+ | `checkForMagiskBinary()` | — | `boolean` | Scans filesystem paths for the Magisk binary. Effective even when Magisk is in hide mode. |
444
+ | `checkForBinary(binaryName)` | `binaryName: string` | `boolean` | Scans filesystem paths for **any named binary**. Pass `"busybox"`, `"su"`, or any other name. |
445
+ | `checkSuExists()` | — | `boolean` | **Attempts to execute `su`**. Returns `true` if the call succeeds. Runtime probe — more invasive than `checkForSuBinary()`. |
446
+ | `checkForDangerousProps()` | — | `boolean` | Reads system properties and returns `true` if dangerous values are set (e.g. `ro.debuggable=1`, `ro.secure=0`). |
447
+ | `checkForRWPaths()` | — | `boolean` | Inspects `/proc/mounts` for filesystem paths that should be read-only but are mounted read-write. |
448
+
449
+ ---
450
+
451
+ ### Method Detail
452
+
453
+ #### `RootBeer.isRooted(): boolean`
454
+
455
+ The top-level composite check. Internally calls every other detection method (including BusyBox) and returns `true` if **any single indicator** is found.
456
+
457
+ **Use this** for the vast majority of use cases. Only fall back to individual methods when you need to understand which specific indicator triggered.
458
+
459
+ ```typescript
460
+ const safe = !RootBeer.isRooted();
461
+ ```
462
+
463
+ ---
464
+
465
+ #### `RootBeer.isRootedWithoutBusyBoxCheck(): boolean`
466
+
467
+ Identical to `isRooted()`, but skips the BusyBox binary check. BusyBox is a legitimate developer tool that may be present on non-malicious devices (e.g. some manufacturer ROMs include it). Use this variant if you are seeing false positives from `isRooted()` in your production user base.
468
+
469
+ ```typescript
470
+ // Prefer this if you're seeing false positives from isRooted()
471
+ const safe = !RootBeer.isRootedWithoutBusyBoxCheck();
472
+ ```
473
+
474
+ ---
475
+
476
+ #### `RootBeer.detectRootManagementApps(): boolean`
477
+
478
+ Queries the Android `PackageManager` for the package names of well-known root management applications. Returns `true` if any are installed.
479
+
480
+ **Examples of detected apps:** SuperSU, Magisk Manager, KingRoot, Towelroot, BaiduEasyRoot.
481
+
482
+ ```typescript
483
+ if (RootBeer.detectRootManagementApps()) {
484
+ console.warn('Root management app found on device.');
485
+ }
486
+ ```
487
+
488
+ ---
489
+
490
+ #### `RootBeer.detectPotentiallyDangerousApps(): boolean`
491
+
492
+ Queries the `PackageManager` for apps that are commonly used to exploit, abuse, or probe rooted environments — even if they are not root managers themselves.
493
+
494
+ ```typescript
495
+ if (RootBeer.detectPotentiallyDangerousApps()) {
496
+ console.warn('Potentially dangerous app detected.');
497
+ }
498
+ ```
499
+
500
+ ---
501
+
502
+ #### `RootBeer.detectRootCloakingApps(): boolean`
503
+
504
+ Scans for root-hiding apps (e.g. RootCloak, MagiskHide-related helpers) whose presence implies a deliberate attempt to conceal root access from the system. A positive result here is a particularly strong signal of malicious intent.
505
+
506
+ ```typescript
507
+ if (RootBeer.detectRootCloakingApps()) {
508
+ // High-confidence indicator — user is actively trying to hide root
509
+ console.warn('Root cloaking app detected.');
510
+ }
511
+ ```
512
+
513
+ ---
514
+
515
+ #### `RootBeer.detectTestKeys(): boolean`
516
+
517
+ Reads `android.os.Build.TAGS` and returns `true` if it contains `"test-keys"`. Production devices from manufacturers are always signed with `"release-keys"`. Test-key builds indicate either a custom ROM or developer engineering builds — both of which typically have root access.
518
+
519
+ ```typescript
520
+ if (RootBeer.detectTestKeys()) {
521
+ console.warn('Device is running a custom or test-key ROM.');
522
+ }
523
+ ```
524
+
525
+ ---
526
+
527
+ #### `RootBeer.checkForSuBinary(): boolean`
528
+
529
+ Scans a curated list of common binary paths (e.g. `/system/bin/su`, `/system/xbin/su`, `/sbin/su`, `/data/local/xbin/su`, etc.) for the presence of the `su` executable. This is a **static file check** — it does not execute `su`.
530
+
531
+ ```typescript
532
+ if (RootBeer.checkForSuBinary()) {
533
+ console.warn('su binary found on filesystem.');
534
+ }
535
+ ```
536
+
537
+ ---
538
+
539
+ #### `RootBeer.checkForMagiskBinary(): boolean`
540
+
541
+ Scans common paths for the Magisk binary. Useful as a secondary check because Magisk's systemless root approach can evade package-manager-based detection (`detectRootManagementApps`) if the Magisk Manager APK is uninstalled.
542
+
543
+ ```typescript
544
+ if (RootBeer.checkForMagiskBinary()) {
545
+ console.warn('Magisk binary present — device likely uses Magisk root.');
546
+ }
547
+ ```
548
+
549
+ ---
550
+
551
+ #### `RootBeer.checkForBinary(binaryName: string): boolean`
552
+
553
+ | Parameter | Type | Required | Description |
554
+ |---|---|---|---|
555
+ | `binaryName` | `string` | ✅ | The binary filename to scan for (e.g. `"su"`, `"busybox"`, `"magisk"`). |
556
+
557
+ Scans the same set of filesystem paths as `checkForSuBinary()` and `checkForMagiskBinary()`, but for an **arbitrary binary name**. Use this for custom checks beyond the preset methods.
558
+
559
+ ```typescript
560
+ // Check for busybox specifically
561
+ const hasBusyBox = RootBeer.checkForBinary('busybox');
562
+
563
+ // Check for any custom binary
564
+ const hasFridaServer = RootBeer.checkForBinary('frida-server');
565
+ ```
566
+
567
+ ---
568
+
569
+ #### `RootBeer.checkSuExists(): boolean`
570
+
571
+ Attempts to **execute** `su` using `Runtime.exec()` and returns `true` if the process starts successfully. This is a runtime probe — the most definitive `su` check, but also the most invasive. On a hardened device, this call might be logged by security software.
572
+
573
+ ```typescript
574
+ // Definitive but invasive — su is actually invoked
575
+ if (RootBeer.checkSuExists()) {
576
+ console.warn('su is executable on this device.');
577
+ }
578
+ ```
579
+
580
+ ---
581
+
582
+ #### `RootBeer.checkForDangerousProps(): boolean`
583
+
584
+ Reads Android system properties via `getprop` and returns `true` if any of the following dangerous values are set:
585
+
586
+ | Property | Dangerous Value |
587
+ |---|---|
588
+ | `ro.debuggable` | `1` |
589
+ | `ro.secure` | `0` |
590
+ | `ro.build.type` | `eng` |
591
+ | `ro.build.tags` | `test-keys` |
592
+
593
+ ```typescript
594
+ if (RootBeer.checkForDangerousProps()) {
595
+ console.warn('Dangerous system properties detected.');
596
+ }
597
+ ```
598
+
599
+ ---
600
+
601
+ #### `RootBeer.checkForRWPaths(): boolean`
602
+
603
+ Parses `/proc/mounts` and checks whether any paths that should always be read-only on a stock Android device (e.g. `/system`, `/data`) are mounted with write permissions. Read-write system mounts are a strong indicator of root-level filesystem modifications.
604
+
605
+ ```typescript
606
+ if (RootBeer.checkForRWPaths()) {
607
+ console.warn('System paths are mounted read-write.');
608
+ }
609
+ ```
610
+
611
+ ---
612
+
613
+ ## Detection Method Deep Dive
614
+
615
+ Different checks have different trade-offs. Use this table to choose the right combination for your threat model.
616
+
617
+ | Method | Technique | Invasiveness | False Positive Risk | Bypass Difficulty |
618
+ |---|---|---|---|---|
619
+ | `isRooted()` | All checks combined | Medium | Low–Medium | Hard |
620
+ | `isRootedWithoutBusyBoxCheck()` | All except BusyBox | Medium | **Lower** | Hard |
621
+ | `detectRootManagementApps()` | Package query | Low | Very Low | Easy (uninstall app) |
622
+ | `detectPotentiallyDangerousApps()` | Package query | Low | Low | Easy (uninstall app) |
623
+ | `detectRootCloakingApps()` | Package query | Low | Very Low | Very Easy (the app hides it) |
624
+ | `detectTestKeys()` | Build property | Very Low | Low | Hard (requires ROM resign) |
625
+ | `checkForSuBinary()` | File path scan | Very Low | Low | Medium (bind mount) |
626
+ | `checkForMagiskBinary()` | File path scan | Very Low | Very Low | Medium (Magisk hide) |
627
+ | `checkForBinary(name)` | File path scan | Very Low | Depends on binary | Medium |
628
+ | `checkSuExists()` | Process execution | **High** | Low | Medium |
629
+ | `checkForDangerousProps()` | `getprop` read | Low | Low | Medium (prop reset) |
630
+ | `checkForRWPaths()` | `/proc/mounts` parse | Very Low | Very Low | Hard |
631
+
632
+ **Recommended strategy for production apps:**
633
+
634
+ 1. Start with `isRooted()` as your primary gate.
635
+ 2. If you need lower false positives, switch to `isRootedWithoutBusyBoxCheck()`.
636
+ 3. For high-security applications (banking, DRM), run all 12 checks individually and send the full result object to your backend for server-side evaluation.
637
+
638
+ ---
639
+
640
+ ## Platform Guard Reference
641
+
642
+ Because the module is registered with `TurboModuleRegistry.getEnforcing`, it **throws** on any platform where it is not registered (iOS, Android Old Architecture, web). Always guard calls:
643
+
644
+ ```typescript
645
+ import { Platform } from 'react-native';
646
+ import { RootBeer } from 'react-native-root-beer';
647
+
648
+ // ✅ Correct — guarded
649
+ if (Platform.OS === 'android') {
650
+ const rooted = RootBeer.isRooted();
651
+ }
652
+
653
+ // ✅ Correct — early return pattern
654
+ function checkDevice(): boolean {
655
+ if (Platform.OS !== 'android') {
656
+ return false; // or handle as you see fit
657
+ }
658
+ return RootBeer.isRooted();
659
+ }
660
+
661
+ // ❌ Incorrect — will throw on iOS / web
662
+ const rooted = RootBeer.isRooted();
663
+ ```
664
+
665
+ For TypeScript projects you can create a typed utility:
666
+
667
+ ```typescript
668
+ // utils/security.ts
669
+ import { Platform } from 'react-native';
670
+ import { RootBeer } from 'react-native-root-beer';
671
+
672
+ /**
673
+ * Returns `null` on non-Android platforms so callers can
674
+ * distinguish "not applicable" from "false".
675
+ */
676
+ export function isDeviceRooted(): boolean | null {
677
+ if (Platform.OS !== 'android') return null;
678
+ return RootBeer.isRooted();
679
+ }
680
+ ```
681
+
682
+ ---
683
+
684
+ ## Architecture
685
+
686
+ ```
687
+ Consumer App (TypeScript)
688
+
689
+ │ import { RootBeer } from 'react-native-root-beer'
690
+
691
+ ┌─────────────────────┐
692
+ │ src/RootBeer.ts │ Public API object (as const, fully typed)
693
+ └──────────┬──────────┘
694
+
695
+
696
+ ┌──────────────────────────┐
697
+ │ src/NativeRootBeer.ts │ TurboModule Spec (Codegen source of truth)
698
+ └──────────┬───────────────┘
699
+ │ React Native Codegen auto-generates ↓
700
+
701
+ ┌────────────────────────────────┐
702
+ │ NativeRootBeerSpec.kt │ Abstract Kotlin class (generated)
703
+ │ (com.rootbeer package) │
704
+ └──────────┬─────────────────────┘
705
+ │ extends
706
+
707
+ ┌────────────────────────────────┐
708
+ │ RootBeerModule.kt │ Concrete implementation
709
+ │ • Registered as TurboModule │ • lazy-initialises RootBeerLib
710
+ │ • isTurboModule = true │ • delegates each method to SDK
711
+ └──────────┬─────────────────────┘
712
+
713
+
714
+ ┌──────────────────────────────────────────┐
715
+ │ com.scottyab:rootbeer-lib:0.1.2 │ Android SDK (Maven Central)
716
+ │ Declared in library's build.gradle │ Resolved as transitive dep
717
+ └──────────────────────────────────────────┘
718
+ ```
719
+
720
+ **Key architectural decisions:**
721
+
722
+ - **`BaseReactPackage`** (not the older `ReactPackage`): The `RootBeerPackage` class extends `BaseReactPackage` — the correct base for New Architecture modules that expose TurboModules.
723
+ - **`isTurboModule = true`** in `ReactModuleInfo`: This opts the module into the TurboModule JSI pathway, bypassing the legacy bridge entirely.
724
+ - **`needsEagerInit = false`**: The native module is only instantiated on first use, not at app startup.
725
+ - **Lazy SDK initialization** (`by lazy`): The `com.scottyab.rootbeer.RootBeer` instance is created once, on first method call, using the `ReactApplicationContext`. This avoids paying the initialization cost if the module is never called.
726
+ - **Codegen spec name** (`codegenConfig.name = "RootBeerSpec"`): The generated spec class is `NativeRootBeerSpec`, and the Java package is `com.rootbeer`, controlled by `codegenConfig.android.javaPackageName`.
727
+
728
+ ---
729
+
730
+ ## Troubleshooting
731
+
732
+ ### Build error: `Cannot find NativeRootBeerSpec`
733
+
734
+ Codegen has not run yet. This happens when you install the package and try to build without cleaning first.
735
+
736
+ ```bash
737
+ cd android && ./gradlew clean
738
+ cd .. && npx react-native run-android
739
+ ```
740
+
741
+ For Expo:
742
+
743
+ ```bash
744
+ npx expo prebuild --clean --platform android
745
+ npx expo run:android
746
+ ```
747
+
748
+ ---
749
+
750
+ ### Build error: `Could not resolve com.scottyab:rootbeer-lib:0.1.2`
751
+
752
+ Your Gradle build cannot reach Maven Central. Check that `mavenCentral()` is in your root `android/build.gradle` repositories block:
753
+
754
+ ```groovy
755
+ // android/build.gradle
756
+ allprojects {
757
+ repositories {
758
+ google()
759
+ mavenCentral() // ← must be present
760
+ }
761
+ }
762
+ ```
763
+
764
+ ---
765
+
766
+ ### `TurboModuleRegistry.getEnforcing` error at runtime
767
+
768
+ You called a `RootBeer` method on a non-Android platform, or your app is running in Old Architecture mode.
769
+
770
+ - **Platform fix:** Wrap all calls in `if (Platform.OS === 'android') { ... }`.
771
+ - **Architecture fix:** Ensure `newArchEnabled=true` in `android/gradle.properties`.
772
+
773
+ ---
774
+
775
+ ### `isRooted()` returns `true` on a production device (false positive)
776
+
777
+ Some manufacturer ROMs (particularly older Xiaomi, Samsung Knox variants) ship with BusyBox or test-keys. Switch to:
778
+
779
+ ```typescript
780
+ // Exclude BusyBox from the composite check
781
+ const isRooted = RootBeer.isRootedWithoutBusyBoxCheck();
782
+ ```
783
+
784
+ Then run the granular scan to identify the exact triggering check and evaluate whether it represents a real risk for your threat model.
785
+
786
+ ---
787
+
788
+ ### Methods return `false` on a known-rooted emulator
789
+
790
+ Some AVD (Android Virtual Device) emulators and certain Genymotion configurations don't expose root indicators in the paths RootBeer scans. For reliable testing:
791
+
792
+ - Use a **physical device** with Magisk or SuperSU installed.
793
+ - Or try `RootBeer.detectTestKeys()` — emulators are almost always signed with test keys.
794
+
795
+ ---
796
+
797
+ ## License
798
+
799
+ MIT © mr:X