English
This release lands the 2026-09 security and stability audit (#635, #637) across the JS, Android, iOS, C++ and HarmonyOS layers. Upgrading is recommended for every app: the shared C++ cores gained hard input limits, the three native downloaders now enforce archive size and free-disk bounds, and several state races were closed.
Security hardening
- Path validation and archive limits (all platforms). Relative paths from manifests and archives reject NUL/control bytes and nesting deeper than 64 levels (this closed a HarmonyOS staging-directory escape via an embedded NUL). Archives are capped at 512 MiB compressed / 2 GiB uncompressed / 20 000 entries, per-entry compression ratio at 100:1, symlink entries and reserved
.pushy-*names are rejected at any depth, and the LZMA dictionary a patch may declare is capped at 128 MiB. The JSON parser that consumes check responses caps input at 1 MiB and 65 536 nodes. - Disk-space protection (all platforms). Downloads verify free space (plus a 64 MiB margin) before the first byte and, for unknown-length bodies, re-probe every 8 MiB; extraction verifies the uncompressed size first. HarmonyOS rejects an archive whose expansion size zlib cannot report instead of decompressing blind.
- HTTPS only for remotely discovered endpoints and APK installs; the iOS check request refuses
https -> httpredirects; non-2xx check responses are truncated before they can reach an alert or a log. - JNI boundary. C++ exceptions are caught at every JNI entry point and rethrown as Java exceptions instead of unwinding through the VM.
- Content-Range validation. A resumed download rejects a
206whose range contradicts its total (bytes 100-199/0used to read as "unknown total" and skip the final size check).
Stability
- Android: every state snapshot → JNI → commit sequence runs under one lock (
switchVersion,markSuccess,rollBack, launch resolution, native-check commit, reset);restartAppdoes the version switch off the UI thread and rejectsRESTART_FAILEDinstead of falling back toActivity.recreate()with the old bundle; the installer fsyncs the versions root after the staging rename; cleanup never evicts the version the process booted from. - iOS:
packageVersion/buildTimeare persisted underREACTNATIVECN_PUSHY_*keys (one-time migration from the unprefixed keys a host app could overwrite);buildTimeresolves throughbundleForClass:underuse_frameworks!; reload no longer pre-consumes first-load state before listeners run;restartApp/markSuccess/ reset reject withRESTART_FAILED/MARK_SUCCESS_FAILED/RESET_FAILEDinstead of resolving on failure; a stale partial is freed before the free-space check so a full restart cannot fail forever on its own bytes. - HarmonyOS: every Promise-returning method is registered async, so rejections reach JS instead of being lost; persistence awaits a real preferences flush; SHA-256 of archives and bundles runs on a NAPI worker instead of the UI thread; download timeouts are bounded by the remaining deadline; every error carries a stable code (
ErrorCodes.tsmirrors the C++ header). - JS:
setOptionsno longer re-triggers a check, re-arms the auto-mark timer or re-subscribes AppState;setOptions({ locale })takes effect at runtime; strategies that failed with a non-retryable code are not re-applied on retry;switchVersionreturns a boolean and a watchdog clearsapplyingUpdatewhen the reload does not restart; web builds no longer fire a fake rollback report on every load;getCurrentVersionInfo()returns{}instead of rejecting when no record exists for the running version.
Behaviour changes to be aware of
- iOS privacy manifest.
ios/PrivacyInfo.xcprivacynow ships with the pod. It declares the required-reason APIs the SDK uses (disk space, file timestamps, system boot time, user defaults) and the data release builds send to/reportby default: crash data (JS exception reports, off withdisableErrorReporting) and other diagnostic data (lifecycle events, off withdisableTelemetry). Neither is linked to the user or used for tracking. Align your App Privacy answers with it. - Android old-architecture bridge:
setNeedUpdate,markSuccess,setUuidandsetLocalHashInfonow take a Promise like the new architecture, so persistence failures reject instead of being swallowed. - Android ProGuard rules were narrowed from blanket
-keepnamesoncom.facebook.react.**to the exact JNI result classes, native methods and reflection targets, and verified against an R8-minified RN 0.85 new-architecture build (every kept class, field, native method and reflection target present, includingReactHost.reload). If your app added its own keep rules for this module they are no longer needed. - Android
build.gradle: defaultcompileSdkVersion/targetSdkVersionwhen the root project sets none moved from 31/27 to 34; the Expo (SDK 50+) module is only compiled whenexpo-modules-coreis part of the build. - HarmonyOS HAR now carries the npm version in
oh-package.json5, and its native core is rebuilt whenevercpp/orandroid/jni/change (previously a stale HAR could ship an older core than Android/iOS). - Dropped: unused locale strings (
downloading_update,installing_update,update_available,update_downloaded,update_installed,no_update_available,network_error,download_failed,install_failed,download_speed,file_size,time_remaining), thereact-native-url-polyfilldependency, and dead event types / error codes that were never emitted.
Types, docs and tooling
ClientOptionsand related option types are exported from the package root; the context type now matches the provider value (progress included).- README (EN + CN) gained an options reference table covering every
ClientOptionsfield. CODE_AUDIT.mddocuments all findings, severities and their status.- CI: HarmonyOS HAR compiles and type-checks on every PR;
patch_coreruns under ASan/UBSan plus a bounded libFuzzer pass; a native spec parity check guards the four bindings; C++ constants are asserted against their Java/ArkTS mirrors.
中文
本版本落地 2026-09 的安全与稳定性审计(#635、#637),覆盖 JS、Android、iOS、C++ 与鸿蒙五层。建议所有应用升级:共享的 C++ 内核加上了硬性输入上限,三端原生下载器都开始强制归档大小与磁盘余量约束,并关闭了多处状态竞态。
安全加固
- 路径校验与归档上限(全平台)。 manifest 与归档中的相对路径拒绝 NUL/控制字节和超过 64 层的嵌套(此前鸿蒙存在通过内嵌 NUL 逃逸 staging 目录的问题)。归档上限:压缩 512 MiB / 解压 2 GiB / 20 000 条目、单条压缩比 100:1;任意深度的符号链接与保留的
.pushy-*名字一律拒绝;patch 声明的 LZMA 字典上限 128 MiB。解析 check 响应的 JSON 解析器限制输入 1 MiB、65 536 个节点。 - 磁盘空间保护(全平台)。 下载前校验可用空间(另加 64 MiB 余量),未知长度的响应体每 8 MiB 重新探测;解压前先校验解压后总量。鸿蒙在 zlib 报不出展开量时直接拒绝归档,而不是盲目解压。
- 远程发现的端点与 APK 安装只接受 HTTPS;iOS 的 check 请求拒绝
https -> http重定向;非 2xx 的 check 响应会先截断再进入弹窗或日志。 - JNI 边界。 每个 JNI 入口捕获 C++ 异常并转为 Java 异常,不再穿越虚拟机栈展开。
- Content-Range 校验。 续传时拒绝 range 与 total 自相矛盾的
206(bytes 100-199/0此前会被当作"总长未知"而跳过最终尺寸检查)。
稳定性
- Android: 所有"状态快照 → JNI → 提交"序列在同一把锁下执行(
switchVersion、markSuccess、rollBack、启动解析、原生检查提交、reset);restartApp的版本切换移出 UI 线程,失败时 rejectRESTART_FAILED,不再回退到会加载旧 bundle 的Activity.recreate();安装器在 staging rename 之后对版本根目录 fsync;清理永远不会删掉本进程启动所用的版本。 - iOS:
packageVersion/buildTime改存到REACTNATIVECN_PUSHY_*键下(从可能被宿主覆盖的无前缀键一次性迁移);use_frameworks!下buildTime通过bundleForClass:解析;reload 不再在监听器运行前预先消费首启状态;restartApp/markSuccess/ reset 失败时分别 rejectRESTART_FAILED/MARK_SUCCESS_FAILED/RESET_FAILED,而不是照样 resolve;空间检查前先释放已失效的 partial,全量重下不会永远卡在自己占用的空间上。 - 鸿蒙: 所有返回 Promise 的方法都按异步注册,拒绝能真正到达 JS;持久化会等待真实的 preferences flush;归档与 bundle 的 SHA-256 在 NAPI 工作线程计算,不再阻塞 UI;下载超时受剩余 deadline 约束;所有错误携带稳定错误码(
ErrorCodes.ts镜像 C++ 头文件)。 - JS:
setOptions不再重复触发检查、重置自动 markSuccess 计时器或重复订阅 AppState;setOptions({ locale })运行时即时生效;以不可重试错误码失败的策略不会在重试时再次执行;switchVersion返回布尔值,reload 未真正重启时看门狗会清掉applyingUpdate;web 构建不再每次加载都上报一次假的 rollback;运行中版本没有记录时getCurrentVersionInfo()返回{}而不是 reject。
需要留意的行为变化
- iOS 隐私清单。
ios/PrivacyInfo.xcprivacy随 pod 一起发布,声明了 SDK 使用的 required-reason API(磁盘空间、文件时间戳、系统启动时间、UserDefaults)以及 release 构建默认发往/report的数据:崩溃数据(JS 异常上报,disableErrorReporting可关)与其他诊断数据(生命周期事件,disableTelemetry可关)。两者均不关联用户身份、不用于追踪。请据此核对 App Store 的隐私问卷。 - Android 旧架构桥:
setNeedUpdate、markSuccess、setUuid、setLocalHashInfo现与新架构一样接收 Promise,持久化失败会 reject 而不是被吞掉。 - Android ProGuard 规则由对
com.facebook.react.**的整体-keepnames收窄为精确的 JNI 结果类、native 方法与反射目标,并已在 RN 0.85 新架构、开启 R8 混淆的 release 构建上验证(所有 keep 的类、字段、native 方法与反射目标均在,含ReactHost.reload)。若你的应用曾为本模块额外添加过 keep 规则,现在不再需要。 - Android
build.gradle: 根工程未设置时的默认compileSdkVersion/targetSdkVersion由 31/27 提升到 34;Expo(SDK 50+)模块仅在expo-modules-core参与构建时才编译。 - 鸿蒙 HAR 的
oh-package.json5现与 npm 版本一致,且cpp/或android/jni/变更时一定会重建原生核心(此前可能发出比 Android/iOS 更旧的内核)。 - 移除: 从未展示的 locale 字符串(
downloading_update、installing_update、update_available、update_downloaded、update_installed、no_update_available、network_error、download_failed、install_failed、download_speed、file_size、time_remaining)、react-native-url-polyfill依赖,以及从未触发的事件类型 / 错误码。
类型、文档与工具
ClientOptions及相关选项类型从包根导出;context 类型与 provider 实际提供的值一致(含 progress)。- README(中英)新增覆盖全部
ClientOptions字段的选项索引表。 CODE_AUDIT.md记录了全部发现项、严重级别与处理状态。- CI:鸿蒙 HAR 在每个 PR 上编译并做类型检查;
patch_core在 ASan/UBSan 下运行并附带有界的 libFuzzer;原生 spec 一致性检查守住四端绑定;C++ 常量与 Java/ArkTS 镜像自动比对。