AfanasievN/react-native-device-risk-signals

GitHub: AfanasievN/react-native-device-risk-signals

一个开源的 React Native TurboModule,用于在 Android 和 iOS 上本地采集设备风险信号与防欺诈情报,将原始观测数据交由后端自行评分和决策。

Stars: 3 | Forks: 0

提供 React Native 咨询及移动产品开发服务。
专注于架构、原生 SDK 集成、性能、安全、CI/CD 和发布工程等领域。可通过我的 GitHub 主页探讨项目或合作事宜。

# React Native 设备风险信号 开源的 React Native TurboModule,用于在 Android 和 iOS 上收集原始设备情报与防欺诈信号:Root 和越狱指示器、模拟器检测、调试器和 Frida 痕迹、VPN 和代理状态,以及硬件、区域设置、应用程序和运行时数据。 该 SDK 返回具有类型的原始观测结果,并为每个探测提供独立的结果。它不计算风险评分、不阻断用户、不创建持久性设备标识符,也不上传数据。您的应用程序将自行负责传输、身份验证、存储、评分和策略。

npm version CI status CodeQL status React Native TurboModule Android and iOS MIT license

[快速开始](#quick-start) · [检测能力](#what-it-detects) · [AI 辅助设置](#ai-assisted-installation-and-integration) · [示例应用](#run-the-example) · [探测目录](#probe-catalog) · [隐私说明](#privacy-and-responsible-use) · [常见问题](#frequently-asked-questions) ## 快速开始 安装该包: ``` npm install react-native-device-risk-signals ``` 在本地收集一组保守的信号: ``` 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()` 不会上传事件。继续阅读[安装详情](#installation),使用其中一种 [AI 设置提示词](#ai-assisted-installation-and-integration),或查看内置的演示。 ## 实际效果 代码库包含 **Signal Bench**,这是一个浅色主题的示例应用,可在当前设备上运行该库,并将完整结果展示为可选的 JSON 格式。

Signal Bench before device signal collection    Signal Bench showing probe counts and the raw JSON event

以下为内置 iOS 示例的真实截图。该演示不会上传收集到的任何数据。

## 为什么选择此库 - **提供原始观测结果,而非最终裁决。** 您的受信任后端负责评分、策略和申诉。 - **故障隔离。** 探测独立运行并返回 `success`、`skipped`、`timeout` 或 `error`;单个缓慢的原生调用不会导致整个收集过程失败。 - **明确的数据控制。** 可在初始化时或每次收集时禁用探测、更改超时时间、过滤字段,并应用减量式的授权关卡。 - **设计上的本地化。** 该包不包含任何网络传输层。由宿主应用程序决定是否发送收集到的事件,以及发送的目标和方式。 - **同时支持 Android 和 iOS。** 该包采用 React Native codegen 和自动链接,并专为 New Architecture 设计。 ## 检测能力 SDK 收集原始观测数据,而不是单一的 `isRisky` 判定。其可用性因平台、操作系统版本、硬件、宿主应用权限及您启用的探测而异。 | 类别 | 示例信号 | | --- | --- | | Root 和越狱 | Root 管理应用、`su`、可疑文件、可写系统路径、越狱 URL schemes | | 模拟器与篡改 | 模拟器启发式分析、调试器状态、开发者模式、Frida 端口、注入的库、hooking 框架 | | 设备与应用 | 型号、OS 构建、应用版本、bundle id、安装来源、权限、split APK 状态 | | 硬件 | 屏幕、CPU、内存、电池、存储、已安装字体的摘要信息 | | 网络 | 连接类型、VPN 状态、代理配置、网络接口、本地 IP 地址 | | 运行时 | Hermes、Fabric、TurboModules、bridgeless 模式、debug 构建、React Native 版本 | | 区域设置与上下文 | 语言、国家/地区、时区、日历、度量衡系统、定位授权 | | 可选基准测试 | GPU 和音频观测结果;在显式启用前默认禁用 | 查看[可用信号组](#available-signal-groups)以获取探测 ID 和平台相关说明。 ## 常见用例 - 利用原始移动观测数据丰富后端的防欺诈或设备风险模型。 - 为登录、结账、账户恢复、支付、促销或高风险操作添加上下文。 - 检测模拟器、Root/越狱、调试器、hooking、VPN、代理或重打包指示器。 - 调查安全遥测数据,避免嵌入会被攻击者篡改的客户端风险裁决。 - 执行受隐私控制的设备研究、QA 和兼容性诊断。 任何单一观测结果都不应直接用于阻断用户。应将信号与经过身份验证的账户、交易、行为及服务器端证据相结合,并为重大决策提供申诉或恢复途径。 ## 本 SDK 不具备的功能 - 不计算风险评分,也不返回受信任/不受信任的判定。 - 不替代 Google Play Integrity、Apple App Attest、DeviceCheck 或服务器端验证。 - 不创建跨重装的持久性设备标识符。 - 默认不上传数据、不获取远程配置,也不连接任何供应商服务。 - 不保证能检测到每一台已 Root、已越狱、被模拟或被插桩的设备。 - 不替代威胁建模、安全后端策略、隐私审查或法律审查。 ## 兼容性 | 环境与平台 | 支持情况 | | --- | --- | | React Native | `0.76` 及以上版本;CI 覆盖 `0.76.9`、`0.81.6` 和 `0.86.0` | | 架构 | TurboModules 和 codegen;仅支持 New Architecture | | Android | API 24 及以上版本 | | iOS | 使用宿主 React Native 发行版支持的最低 iOS 版本;CocoaPods 集成 | | Expo | 在 Expo Go 中不可用;需要原生预构建或自定义开发构建 | | TypeScript | 类型化的公共 API 和生成的声明文件 | | 数据传输 | 不包含在内;应用程序使用其现有的 API 客户端或传输层 | ## 安装 从 npm 安装最新的公开发布版本: ``` npm install react-native-device-risk-signals ``` 若要从 `main` 分支评估未发布的更改,请直接从 GitHub 安装该代码库: ``` npm install github:AfanasievN/react-native-device-risk-signals#main ``` 将该包添加到 iOS 应用后,请安装 CocoaPods 依赖: ``` npx pod-install ``` 本包支持 React Native 0.76 及以上版本且使用 New Architecture。Android 默认要求 API 24 及以上版本。 ## AI 辅助安装与集成 以下提示词专为能够检查并编辑您当前项目的 AI 编程助手设计。请将提示词逐一复制到附加于 React Native 代码库的聊天中。在接受建议的更改之前,请务必进行审查,尤其是原生配置和收集的字段。 ### 提示词 1:安装库 ``` 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. ``` ### 提示词 2:将信号收集集成到项目架构中 ``` 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. ``` ## 在本地收集信号 ``` 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(); ``` 上述代码中未配置任何 endpoint,因此事件会保留在您的应用程序中。 ### 真实响应示例 以下响应由内置的 Signal Bench 应用在 iOS 模拟器上收集。仅 session id、timestamp 和本地 IP 地址被替换为了安全的示例值。
查看完整的 JSON 响应(11 个探测成功,4 个被跳过) ``` { "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" } } } } ```
探测的 payload 会因平台、操作系统版本、可用硬件及权限的不同而有所差异。使用者在读取 `data` 前应根据 `status` 进行逻辑分支处理。单一的观测结果并非风险裁决;例如,模拟器可能会暴露在该环境中符合预期的开发残留信息。 ## 配置收集 配置是一个普通对象。它可以随应用一起打包,由您的后端提供,也可以针对特定流程进行更改。SDK 本身不会获取配置。 ``` 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 }, }, }, }); ``` 按次调用的配置会叠加在实例配置之上。授权关卡最后应用,并且只能移除探测;它无法重新启用已禁用的探测。未知的探测 ID、无效的超时时间以及未知的字段选择会抛出 `ProbeConfigValidationError`,而不会被静默忽略。在构建 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. } ``` ### 使用您的应用程序 API 客户端发送 SDK 有意不包含传输层。请在收集并精简事件后,使用您的应用程序中已经负责重试、Headers、遥测和错误处理的身份验证 API 层: ``` const event = await deviceIntel.collect(); await appApi.post("/v1/device-signals", event); ``` 请勿在 SDK 配置中放置凭据或供应商 endpoint。应用程序应自行决定是否发送事件以及如何处理传输失败。 ## 可用信号组 | 组别 | 探测 ID | 备注 | | -------------- | ------------------------------------------------------------------- | ----------------------------------------------------------- | | 设备与应用 | `device_identity`, `application` | 设备/OS 标识及宿主应用元数据 | | 硬件 | `hardware`, `gpu_benchmark`, `audio_latency` | GPU 和音频基准测试默认处于禁用状态 | | 完整性 | `os_integrity`, `os_integrity_frida_scan`, `os_integrity_fork_test` | fork test 仅限 iOS 且默认禁用 | | 运行时 | `runtime`, `fonts` | JavaScript 运行时和已安装字体的观测结果 | | 连接性 | `network`, `telephony` | 可用性取决于平台和 OS 限制 | | 上下文 | `locale`, `geolocation` | 地理定位仅使用宿主应用可获取的信息 | | 媒体与应用 | `media_bluetooth_apps` | 媒体路由、蓝牙及有限的已知应用观测结果 | 某些值在设计上具有机会性(依条件获取)。不支持或不可用的信息应显示为跳过的探测或不可用的值,而不应被视为低风险的证据。 ## 探测目录 `PROBE_CATALOG` 是一个机器可读的清单,包含每个探测的详细信息,如支持平台、默认状态、敏感度、权限、数据类别、用途、备注以及可选字段。 ``` import {getProbeDescriptor, PROBE_CATALOG} from "react-native-device-risk-signals"; const network = getProbeDescriptor("network"); const enabledByDefault = PROBE_CATALOG.filter((probe) => probe.enabledByDefault); ``` 查看完整的[数据字典](docs/DATA_DICTIONARY.md)以及透明的[基准测试方法与基线](docs/BENCHMARKS.md)。 ## 数据流 ``` 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] ``` ## 隐私与负责任的使用 - Android 库的 manifest 不声明任何权限,且不请求 `QUERY_ALL_PACKAGES`。 - SDK 不会派生跨重装的持久性标识符。 - `client_id`(如果使用)由宿主应用程序提供;绝不会从设备推断得出。 - 同意、保留、披露和合法性基础的要求仍由集成方负责。 - 位置、电话、应用可见性和高熵指纹应被视为敏感数据。 - 请勿将本库用于隐蔽跟踪,或将其作为做出重大决策的唯一依据。 在投入生产环境之前,请审查每一个已启用的探测、平台权限、隐私披露和保留规则。像 GPU 基准测试、音频延迟测量和 iOS fork test 等高风险探测在默认情况下均处于故意禁用状态。 ## 常见问题 ### React Native Device Risk Signals 的作用是什么? 它收集设备端原始观测数据,以支持防欺诈、账户保护和安全决策。它返回证据和探测结果,而不是单一的风险评分或 `safe` / `unsafe` 的判定。 ### 它能检测到已 Root 的 Android 设备和已越狱的 iPhone 吗? 它会在 Android 和 iOS 上检查多种 Root 和越狱指示器。没有任何客户端检查能保证检测到每一台设备,因此应将这些信号视为分层服务器端风险策略的一部分,而不是确凿的证据。 ### 它能检测模拟器、调试器、Frida、VPN 和代理吗? 是的,只要操作系统和当前运行时提供相关数据,SDK 就会暴露这些原始指示器。个别探测可能会报告 `success`、`skipped`、`timeout` 或 `error`;请始终根据结果进行分支处理,而不是假定每个字段都必然存在。 ### 它会创建设备指纹或持久标识符吗? 不会。SDK 不会创建跨安装的持久标识符,也不会将观测结果组合成由供应商控制的指纹。不过,某些观测结果仍可能具有高熵,因此宿主应用程序必须对其选择使用的任何数据进行最小化处理、披露、保留和保护。 ### SDK 会上传数据吗? 不会。该包不包含任何网络传输层。`collect()` 返回的是本地值,只有宿主应用程序才能决定是否将该值传递给其自身的 API 客户端。 ### 它会替代 Google Play Integrity、Apple App Attest 或 DeviceCheck 吗? 不会。平台证明提供了不同且通常在密码学上更强的保证。本库通过可检查的设备和运行时上下文来补充证明功能;当您的威胁模型有需要时,请同时结合使用两者。 ### 它能在 Expo 中使用吗? 它无法在 Expo Go 内部运行,因为它包含了原生的 Android 和 iOS 代码。请使用 Expo 原生预构建或自定义开发构建,然后验证生成的原生项目及其支持的 React Native 版本。 ### 它需要什么权限? Android 库的 manifest 不声明任何权限。但信号的可用性可能仍取决于宿主应用程序现有的权限、平台限制操作系统版本。请根据您的隐私政策和应用商店要求,对每个启用的探测进行审查。 ### 如果探测不受支持或失败会怎样? 探测是相互隔离的,会返回明确的结果,例如 `skipped`、`timeout` 或 `error`。单个探测失败不会使整个收集过程无效。您的集成方案应能容忍部分结果,避免将缺失的数据视为存在欺诈的证据。 ### 它支持 React Native 的旧架构吗? 本包是作为 React Native New Architecture 的 TurboModule 进行设计和测试的。仍使用旧架构的项目应在采用前进行迁移或验证兼容性。 ## 运行示例 请在库的根目录之外独立安装示例: ``` cd example npm install npm start ``` 在另一个终端中,启动运行中的模拟器或连接的设备: ``` npm run android ``` 对于 iOS,首次运行前请安装原生依赖: ``` bundle install cd ios && bundle exec pod install && cd .. npm run ios ``` 请参阅 [example/README.md](example/README.md) 了解环境要求和故障排除说明。 ## 开发说明 ``` npm install npm run verify ``` 添加探测时,请实现双原生平台支持或显式的平台回退,在 `src/probes/index.ts` 中注册该探测,记录其隐私影响,并添加针对性的测试。 `npm run build` 会将 JavaScript 入口和 TypeScript 声明编译到 `lib/` 目录中。 `npm pack --dry-run` 会准确显示将要发布哪些文件。维护者应遵循 [RELEASING.md](RELEASING.md) 中的说明进行版本控制、npm Trusted Publishing 和 GitHub Release 步骤。 ## 项目状态 本项目正处于早期公开开发阶段。API 和收集的字段在 `1.0.0` 版本之前可能会发生演变,因此在将本库用于生产应用之前应进行审查。请参阅 [CHANGELOG.md](CHANGELOG.md) 获取发布说明。 ## 维护者 本项目由 [AfanasievN](https://github.com/AfanasievN) 维护。如需 React Native 咨询、原生集成、架构、性能、安全或发布工程方面的帮助,请参阅本 README 顶部的可服务说明。 ## 安全性 本项目采用了 CI、针对 JavaScript/TypeScript、Java/Kotlin 以及 Objective-C/C++ 的 CodeQL 分析、原生 Android 和 iOS 构建、依赖审查、Dependabot 安全更新,以及通过 Trusted Publishing 实现的 npm 溯源。这些检查可以降低风险,但无法保证软件绝对没有漏洞。请按照 [SECURITY.md](SECURITY.md) 中的说明私下报告疑似漏洞。 ## 贡献 欢迎您的贡献。在提交 pull request 之前,请阅读 [CONTRIBUTING.md](CONTRIBUTING.md)。 参与本项目受[行为准则](CODE_OF_CONDUCT.md)的约束。 ## 许可证 [MIT](LICENSE) © React Native Device Risk Signals 贡献者。
标签:Android, DSL, iOS, React Native, 欺诈防护, 移动开发, 自动化攻击, 设备指纹