react-native-device-risk-signals
v0.2.0
Published
React Native device risk signals for root, jailbreak, emulator, tampering, Frida, VPN, and proxy detection
Maintainers
Keywords
Readme
React Native Device Risk Signals
Open-source React Native TurboModule for collecting raw device intelligence and fraud-prevention signals on Android and iOS: root and jailbreak indicators, emulator detection, debugger and Frida traces, VPN and proxy state, hardware, locale, application, and runtime data.
The SDK returns typed raw observations with an independent outcome for every probe. It does not calculate a risk score, block users, create a persistent device identifier, or upload data. Your application owns transport, authentication, storage, scoring, and policy.
Quick start · What it detects · AI-assisted setup · Example app · Probe Catalog · Privacy · FAQ
Quick start
Install the package:
npm install react-native-device-risk-signalsCollect a conservative set of signals locally:
import {consentFor, DeviceIntel} from "react-native-device-risk-signals";
const deviceIntel = new DeviceIntel({
sessionId: "checkout-session-42",
consent: consentFor(["device_identity", "os_integrity", "network", "runtime"]),
});
const event = await deviceIntel.collect();
console.log(JSON.stringify(event, null, 2));collect() does not upload the event. Continue to installation details, use one of
the AI setup prompts, or inspect the included demo.
See it in action
The repository includes Signal Bench, a light-theme example app that runs the library against the current device and exposes the complete result as selectable JSON.
Why this library
- Raw observations, not verdicts. Your trusted backend owns scoring, policy, and appeals.
- Failure isolation. Probes run independently and return
success,skipped,timeout, orerror; one slow native call does not fail the collection. - Explicit data control. Disable probes, change timeouts, project fields, and apply a subtractive consent gate at initialization or per collection.
- Local by design. The package contains no network transport. The host application decides if, where, and how a collected event is sent.
- Android and iOS. The package uses React Native codegen and autolinking for the New Architecture.
What it detects
The SDK collects raw observations rather than a single isRisky verdict. Availability varies by
platform, OS version, hardware, host-app permissions, and the probes you enable.
| Category | Example signals |
| --- | --- |
| Root and jailbreak | Root-management apps, su, suspicious files, writable system paths, jailbreak URL schemes |
| Emulator and tampering | Emulator heuristics, debugger state, developer mode, Frida ports, injected libraries, hooking frameworks |
| Device and application | Model, OS build, app version, bundle id, installer source, permissions, split APK state |
| Hardware | Screen, CPU, memory, battery, storage, installed-font digest |
| Network | Connection type, VPN state, proxy configuration, interfaces, local IP addresses |
| Runtime | Hermes, Fabric, TurboModules, bridgeless mode, debug build, React Native version |
| Locale and context | Language, country, timezone, calendar, measurement system, location authorization |
| Optional benchmarks | GPU and audio observations; disabled by default until explicitly enabled |
See Available signal groups for probe ids and platform notes.
Common use cases
- Enrich a backend fraud-prevention or device-risk model with raw mobile observations.
- Add context to login, checkout, account recovery, payment, promotion, or high-risk actions.
- Detect emulator, root/jailbreak, debugger, hooking, VPN, proxy, or repackaging indicators.
- Investigate security telemetry without embedding a client-side risk verdict attackers can patch.
- Run privacy-controlled device research, QA, and compatibility diagnostics.
No individual observation should automatically block a user. Combine signals with authenticated account, transaction, behavioral, and server-side evidence, and provide an appeal or recovery path for consequential decisions.
What this SDK does not do
- It does not calculate a risk score or return a trusted/untrusted verdict.
- It does not replace Google Play Integrity, Apple App Attest, DeviceCheck, or server verification.
- It does not create a persistent cross-reinstall device identifier.
- It does not upload data, fetch remote configuration, or contact a vendor service by default.
- It does not guarantee detection of every rooted, jailbroken, emulated, or instrumented device.
- It is not a substitute for threat modeling, secure backend policy, privacy review, or legal review.
Compatibility
| Surface | Support |
| --- | --- |
| React Native | 0.76 and newer; CI covers 0.76.9, 0.81.6, and 0.86.0 |
| Architecture | TurboModules and codegen; New Architecture only |
| Android | API 24 and newer |
| iOS | Uses the minimum iOS version supported by the host React Native release; CocoaPods integration |
| Expo | Not available in Expo Go; requires a native prebuild or custom development build |
| TypeScript | Typed public API and generated declaration files |
| Data transport | Not included; applications use their existing API client or transport layer |
Installation
Install the latest public version from npm:
npm install react-native-device-risk-signalsTo evaluate unreleased changes from main, install the repository directly from GitHub:
npm install github:AfanasievN/react-native-device-risk-signals#mainInstall CocoaPods dependencies after adding the package to an iOS app:
npx pod-installThe package supports React Native 0.76 and newer with the New Architecture. Android defaults to API 24 or newer.
AI-assisted installation and integration
The prompts below are designed for an AI coding assistant that can inspect and edit your current project. Copy one prompt at a time into the chat attached to your React Native repository. Review the proposed changes before accepting them, especially native configuration and collected fields.
Prompt 1: install the library
Install react-native-device-risk-signals in this React Native project.
Before changing files, inspect the repository and determine:
- the package manager and lockfile;
- the React Native version and whether the New Architecture is enabled;
- whether this is a bare React Native app or an Expo project;
- which platforms are present;
- the Android minSdk and the existing iOS CocoaPods/Bundler workflow.
Confirm that the project is compatible with React Native >= 0.76, Android API >= 24, and a native
TurboModule build. Expo Go is not supported; for Expo, use the project's existing prebuild or custom
development-client workflow. Install the package with the repository's package manager, preserve
the existing lockfile strategy, and run CocoaPods using Bundler when the project already uses a
Gemfile. Do not add permissions, remote services, or unrelated dependency upgrades. Run the
project's existing typecheck/tests plus the relevant native dependency checks. Finish with a concise
summary of changed files, commands run, compatibility findings, and any manual step still required.Prompt 2: integrate signal collection into the project architecture
Integrate react-native-device-risk-signals into this existing React Native application.
First inspect the architecture instead of assuming a template. Identify app bootstrap and lifecycle,
authentication/session ownership, navigation, dependency injection or service modules, state
management, analytics/telemetry conventions, backend API clients, consent/privacy controls, and the
current test strategy. Then choose the smallest integration point that matches those conventions.
Requirements:
- create one reusable DeviceIntel instance or service instead of constructing it throughout the UI;
- use the application's existing session id when available and never derive a persistent device id;
- begin with an explicit, conservative consentFor(...) probe list and explain every enabled group;
- call collect() locally and pass the result to the application's existing API client only when a
suitable first-party backend endpoint and payload contract already exist;
- handle success, skipped, timeout, and error outcomes without treating missing data as low risk;
- keep risk scoring and blocking decisions on the backend and do not block a user from one signal;
- account for Android/iOS differences, Expo prebuild requirements, and New Architecture codegen;
- minimize retained fields with `fields.include` before handing the event to any transport;
- add focused tests using the project's existing tools and update relevant privacy documentation;
- do not introduce vendor services, secrets, new permissions, or unrelated refactors.
Show the proposed architecture and data flow before editing. After implementation, run the relevant
tests and native checks, then summarize files changed, enabled probes, collected/transmitted fields,
failure behavior, privacy implications, and remaining backend or product decisions.Collect signals locally
import {
consentFor,
DeviceIntel,
type RawSignalEvent,
} from "react-native-device-risk-signals";
const deviceIntel = new DeviceIntel({
sessionId: "checkout-session-42",
consent: consentFor([
"device_identity",
"hardware",
"os_integrity",
"locale",
"runtime",
]),
});
const event: RawSignalEvent = await deviceIntel.collect();No endpoint is configured above, so the event remains in your application.
Real response example
The response below was collected by the included Signal Bench app on an iOS Simulator. Only the session id, timestamp, and local IP addresses were replaced with safe example values.
{
"session_id": "demo-session-42",
"event_type": "device_intel_collection",
"schema_version": 1,
"collected_at": "2026-07-15T16:00:43.622Z",
"probes": {
"device_identity": {
"status": "success",
"data": {
"systemName": "iOS",
"manufacturer": "Apple",
"osBuild": "25F84",
"kernelOsType": "Darwin",
"model": "arm64",
"kernelOsRelease": "25.5.0",
"brand": "Apple",
"systemVersion": "26.4.1",
"isTablet": 0,
"kernelVersion": "Darwin Kernel Version 25.5.0: Tue Jun 9 22:28:24 PDT 2026; root:xnu-12377.121.10~1/RELEASE_ARM64_T6020"
}
},
"hardware": {
"status": "success",
"data": {
"processorCount": 12,
"screenBrightness": 0.5,
"freeMemoryBytes": 146178048,
"screenDensity": 3,
"screenHeightPx": 2622,
"totalMemoryBytes": 34359738368,
"screenPhysicalHeightPx": 2622,
"screenWidthPx": 1206,
"screenPhysicalDensity": 3,
"screenPhysicalWidthPx": 1206,
"batteryState": "unknown"
}
},
"fonts": {
"status": "success",
"data": {
"fontsDigest": "def45589933acee661159b4a13123add069a6ba797b7af69f3835fe45df9c922"
}
},
"os_integrity": {
"status": "success",
"data": {
"injectedLibraryNames": [],
"suBinaryFound": true,
"suspiciousFilePaths": ["/usr/sbin/sshd", "/usr/bin/ssh"],
"injectedLibrariesFound": 0,
"suspiciousFilePathsFound": 1,
"symbolicLinksSuspicious": false,
"writableSystemPathFound": false,
"hookFrameworkFound": 0,
"developerModeEnabled": false,
"dyldImageCount": 1010,
"canOpenJailbreakScheme": false,
"isDebuggerAttached": false,
"isEmulator": true,
"rootManagementAppFound": false
}
},
"os_integrity_frida_scan": {
"status": "skipped",
"reason": "disabled"
},
"os_integrity_fork_test": {
"status": "skipped",
"reason": "disabled"
},
"network": {
"status": "success",
"data": {
"connectionType": "wifi",
"isConnected": 1,
"localIpAddresses": ["xxx.xx.x.x2"],
"isVpnActive": false,
"interfaceNames": [
"anpi2",
"anpi1",
"anpi0",
"en4",
"en5",
"en6",
"en1",
"en2",
"en3",
"bridge0",
"en0",
"awdl0",
"llw0",
"utun0",
"utun1",
"utun2",
"utun3",
"utun4",
"utun5",
"utun100"
],
"isProxyConfigured": false
}
},
"telephony": {
"status": "success",
"data": {}
},
"locale": {
"status": "success",
"data": {
"languages": ["ru-RU", "en-GB"],
"timezoneOffsetMinutes": 180,
"calendar": "gregorian",
"uses24HourClock": 1,
"groupingSeparator": " ",
"firstDayOfWeek": 2,
"decimalSeparator": ",",
"language": "en",
"measurementSystem": "metric",
"country": "RU",
"timezoneId": "Europe/Moscow",
"currencyCode": "RUB"
}
},
"geolocation": {
"status": "success",
"data": {
"authorizationStatus": "notDetermined",
"hasCoarsePermission": false
}
},
"media_bluetooth_apps": {
"status": "success",
"data": {
"isOtherAudioPlaying": false,
"isScreenMirrored": 0,
"accessibilityFeatures": [],
"isScreenCaptured": false,
"audioOutputRoute": "speaker",
"accessibilityRunning": 0,
"openableFlaggedSchemes": []
}
},
"gpu_benchmark": {
"status": "skipped",
"reason": "disabled"
},
"audio_latency": {
"status": "skipped",
"reason": "disabled"
},
"application": {
"status": "success",
"data": {
"appVersion": "1.0",
"appBuild": "1",
"bundleId": "org.reactjs.native.example.DeviceRiskSignalsExample"
}
},
"runtime": {
"status": "success",
"data": {
"isHermes": true,
"jsEngine": "hermes",
"hermesVersion": "250829098.0.14",
"isFabric": true,
"isTurboModule": true,
"isBridgeless": true,
"isDebugBuild": true,
"reactNativeVersion": "0.86.0",
"platformOs": "ios"
}
}
}
}Probe payloads vary by platform, OS version, available hardware, and permissions. Consumers should
branch on status before reading data. Individual observations are not risk verdicts; for example,
a simulator may expose development artifacts that are expected in that environment.
Configure collection
Configuration is a plain object. It can be bundled with the app, supplied by your backend, or changed for an individual flow. The SDK does not fetch configuration itself.
const deviceIntel = new DeviceIntel({
config: {
probes: {
geolocation: { enabled: false },
telephony: { enabled: false },
device_identity: {
timeoutMs: 300,
fields: {
include: ["systemName", "manufacturer", "model", "systemVersion"],
},
},
},
},
});
const event = await deviceIntel.collect({
config: {
probes: {
network: { enabled: false },
},
},
});Per-call configuration is layered over instance configuration. A consent gate is applied last and
can only remove probes; it cannot re-enable a disabled probe. Unknown probe ids, invalid timeouts,
and unknown selected fields throw ProbeConfigValidationError instead of being silently ignored.
Validate untrusted or remotely supplied configuration before constructing the SDK:
import {validateProbeConfig} from "react-native-device-risk-signals";
const issues = validateProbeConfig(candidateConfig);
if (issues.length > 0) {
// Reject the configuration or report it through the application's own diagnostics.
}Send with your application API client
The SDK intentionally has no transport. Collect and minimize the event, then use the authenticated API layer that already owns retries, headers, telemetry, and error handling in your application:
const event = await deviceIntel.collect();
await appApi.post("/v1/device-signals", event);Do not place credentials or vendor endpoints in SDK configuration. The application decides whether an event should be sent and how delivery failures are handled.
Available signal groups
| Group | Probe ids | Notes |
| -------------- | ------------------------------------------------------------------- | ----------------------------------------------------------- |
| Device and app | device_identity, application | Device/OS identity and host application metadata |
| Hardware | hardware, gpu_benchmark, audio_latency | GPU and audio benchmarks ship disabled |
| Integrity | os_integrity, os_integrity_frida_scan, os_integrity_fork_test | The fork test is iOS-only and ships disabled |
| Runtime | runtime, fonts | JavaScript runtime and installed-font observations |
| Connectivity | network, telephony | Availability depends on platform and OS restrictions |
| Context | locale, geolocation | Geolocation uses only information available to the host app |
| Media and apps | media_bluetooth_apps | Media route, Bluetooth, and finite known-app observations |
Some values are opportunistic by design. Unsupported or unavailable information should appear as a skipped probe or an unavailable value, not be treated as evidence of low risk.
Probe Catalog
PROBE_CATALOG is a machine-readable inventory of every probe, including platforms, default state,
sensitivity, permissions, data categories, purpose, notes, and selectable fields.
import {getProbeDescriptor, PROBE_CATALOG} from "react-native-device-risk-signals";
const network = getProbeDescriptor("network");
const enabledByDefault = PROBE_CATALOG.filter((probe) => probe.enabledByDefault);See the complete Data Dictionary and transparent benchmark methodology and baseline.
Data flow
flowchart LR
A[Host application] --> B[Config and consent]
B --> C[Independent native probes]
C --> D[RawSignalEvent]
D --> E[On-device use]
D -.->|application-owned transport| F[Your backend]
F --> G[Risk policy and scoring]Privacy and responsible use
- The Android library manifest declares no permissions and does not request
QUERY_ALL_PACKAGES. - The SDK does not derive a persistent cross-reinstall identifier.
client_id, when used, is supplied by the host application; it is never inferred from the device.- Consent, retention, disclosure, and lawful-basis requirements remain the integrator's responsibility.
- Location, telephony, application visibility, and high-entropy fingerprints should be treated as sensitive data.
- Do not use the library for covert tracking or as the sole basis for a consequential decision.
Review every enabled probe, platform permission, privacy disclosure, and retention rule before a production rollout. Higher-risk probes such as GPU benchmarking, audio latency measurement, and the iOS fork test intentionally ship disabled.
Frequently asked questions
What does React Native Device Risk Signals do?
It collects raw, on-device observations that can support fraud prevention, account protection, and
security decisions. It returns evidence and probe outcomes rather than a single risk score or a
safe / unsafe verdict.
Can it detect rooted Android devices and jailbroken iPhones?
It checks multiple root and jailbreak indicators on Android and iOS. No client-side check can guarantee detection on every device, so treat these signals as part of a layered server-side risk policy rather than as definitive proof.
Can it detect emulators, debuggers, Frida, VPNs, and proxies?
Yes, the SDK exposes relevant raw indicators where the operating system and current runtime make
them available. Individual probes may report success, skipped, timeout, or error; always
branch on the outcome instead of assuming that every field is present.
Does it create a device fingerprint or persistent identifier?
No. The SDK does not create a persistent cross-install identifier or combine observations into a vendor-controlled fingerprint. Some observations can still be high entropy, so the host application must minimize, disclose, retain, and protect any data it chooses to use.
Does the SDK upload data?
No. The package contains no network transport. collect() returns a local value, and only the host
application can decide to pass that value to its own API client.
Does it replace Google Play Integrity, Apple App Attest, or DeviceCheck?
No. Platform attestation provides different, often cryptographically stronger guarantees. This library complements attestation with inspectable device and runtime context; use both when your threat model requires them.
Does it work with Expo?
It does not run inside Expo Go because it includes native Android and iOS code. Use an Expo native prebuild or a custom development build, then validate the generated native projects and supported React Native version.
What permissions does it require?
The Android library manifest declares no permissions. Signal availability can still depend on the host application's existing permissions, platform restrictions, and OS version. Audit each enabled probe against your privacy policy and store requirements.
What happens when a probe is unsupported or fails?
Probes are isolated and return explicit outcomes such as skipped, timeout, or error. One failed
probe does not invalidate the entire collection. Your integration should tolerate partial results
and avoid treating missing data as evidence of fraud.
Does it support React Native's old architecture?
The package is designed and tested as a TurboModule for React Native's New Architecture. Projects that still use the legacy architecture should migrate or validate compatibility before adoption.
Run the example
Install the example independently from the library root:
cd example
npm install
npm startIn another terminal, launch a running emulator, simulator, or connected device:
npm run androidFor iOS, install native dependencies before the first run:
bundle install
cd ios && bundle exec pod install && cd ..
npm run iosSee example/README.md for environment requirements and troubleshooting notes.
Development
npm install
npm run verifyWhen adding a probe, implement both native platforms or an explicit platform fallback, register the
probe in src/probes/index.ts, document its privacy impact, and add focused tests.
npm run build compiles the JavaScript entrypoint and TypeScript declarations into lib/.
npm pack --dry-run shows exactly which files would be published. Maintainers should follow
RELEASING.md for versioning, npm Trusted Publishing, and GitHub Release steps.
Project status
The project is in early public development. The API and collected fields may evolve before 1.0.0
and should be reviewed before adopting the library in a production application. See
CHANGELOG.md for release notes.
Maintainer
The project is maintained by AfanasievN. For React Native consulting, native integrations, architecture, performance, security, or release engineering, see the availability note at the top of this README.
Security
This project uses CI, CodeQL analysis for JavaScript/TypeScript, Java/Kotlin, and Objective-C/C++, native Android and iOS builds, dependency review, Dependabot security updates, and npm provenance through Trusted Publishing. These checks reduce risk but cannot guarantee that the software is vulnerability-free. Report suspected vulnerabilities privately as described in SECURITY.md.
Contributing
Contributions are welcome. Read CONTRIBUTING.md before opening a pull request. Participation in this project is governed by the Code of Conduct.
License
MIT © React Native Device Risk Signals contributors.
