@ exodus/ react-native-bundle-loader (英语)
装入远程 React 原生 JS 捆绑, 可选 hash 定位完整性核查 在桥重装之前.
这是Exodus安全坚固的叉子
react-native-bundle-loader(原始内容存档于Jusbrasil;上游GitHub repo已删除;见 来源 详细情况请查阅.
威胁模式
装入远程JS捆绑是通过构建,远程代码执行在主机应用程序内部. 该图书馆仅供内部/开发建筑使用,没有带外、静态地悬挂的地物旗,不得将其运入储存的建筑. 见 SECURITY.md。 。 。 .
这个 loadVerified() API关闭了主导运行时间风险:它以本地方式下载捆绑,用平台加密方式将字节(iOS: CommonCrypto CC_SHA256,Android: (中文(简体) ) MessageDigest SHA-256),将散列比作恒定时间内的呼叫器提供文摘,而后再从app-private存储中加载已验证的字节. 任何在取回和再装入之间突变的响应都会被拒绝 .
安装
yarn add @exodus/react-native-bundle-loader
iOS: (英语)
cd ios && pod install
Android:既需要Gradle接线,也需要主机应用程序的更改——参见 Android 集成系统 见下文.
使用量
验证装载( 建议)
import BundleLoader from '@exodus/react-native-bundle-loader';
await BundleLoader.loadVerified(
'https://bundles.example.com/main.jsbundle',
// Lower-case hex sha256, exactly 64 chars
'4f1b9c…ec'
);
行为:
- URL必须使用
https:计划. - 所期望的sha256必须是64个特征的六角弦.
- 下载,SHA-256散列,以及恒时相比较,都发生在本地代码中. 这样可以避开赫耳墨斯河
RangeError联署材料方面response.arrayBuffer()大型捆绑上的原因(~70 MB). - 匹配时,字节被写入到app-私密存储并加载捆绑(见下文平台注释).
- 在不匹配时, 会出现出错, 而当前捆绑则未受影响 .
- 远程捆绑活动 仅一届. 下个寒冷开始返回本地捆绑——匹配消费者从开发者预览工具中期待的行为.
工作在iOS和Android上.
图书馆曝光 仅限 已验证路径。 没有未经证实的
load()API: 装入没有本地 SHA-256 验证的远程捆绑是一个 未认证的远程代码执行原始,因此被去除.
平台支持
| 能力 | iOS系统 | 机器人 |
|---|---|---|
loadVerified(url, sha256) | ✅ | ✅ |
runningMode() | ✅ | ✅ |
如何装入
iOS系统 下载和通过本地方式验证捆绑 NSURLSession + 键 CommonCrypto CC_SHA256,然后将已核实的字节写入 NSTemporaryDirectory() 与 NSDataWritingAtomic | NSDataWritingFileProtectionComplete (核查前无一文). 它存储文件 URL 在 NSUserDefaults 下级 RNBundleLoaderPendingURLKey 和电话 [bridge reload]; 主机应用程序 loadSourceForBridge: 读取待定的 URL 并从中加载, 所以桥本身的 bundleURL ——因此 SourceCode.scriptURL ——从不突变,保持资产分辨率正确. 这是一次在程序内重载;在ARC下,旧桥(及其赫尔墨斯跑道时间)在新桥分配前被放出,所以没有双模峰.
机器人 使用进程重启,而不是进程内桥接互换。 原因:Android的ART垃圾收集器是非决定性的. 当新 React 上下文和已有上下文一起创建时, ART 并不能保证旧的 Hermes runtime 的本土堆积在新运行时间分配前被释放出. 在真实世界捆绑大小(~50 MB of Hermes bytecode)上,这会导致OOM. 重新启动的过程通过确保只运行一次就能完全避免问题.
在下载和散列验证后,模块:
- 下载到 a 每个呼叫唯一的临时文件 (单位:千美元)
File.createTempFile内Context.getCacheDir()),验证SHA-256,然后 解剖促进 给Context.getCacheDir()/verified-bundle.jsbundle——犬行道从不持有未经验证或部分字节. 每个调用(而不是共享的固定路径)的独特临时,意味着两个相重叠的负载永远无法在验证和重命名之间互换对方的文件. 在散列不匹配或下载出错时,临时文件被删除,而当前的捆绑则未受影响. - 设置一发旗帜
SharedPreferences(单位:千美元)"BundleLoader"编号 :"pending_remote_bundle"),使用同步键commit()所以旗帜能活过即将到来的过程. - 通过
startActivity+ 键Process.killProcess。 。 。 .
在下一次发布时,主机应用读取了旗子,禁用地铁(所以) ReactInstanceManager 不查询打包器而忽略文件——通过RN 0.78的字节代码分析确认有必要,并服务 verified-bundle.jsbundle 作为联署材料,供本届会议使用。 旗被先用后再重新启动,然后返回地铁.
Android 集成系统
因为Android需要主机应用程序的修改而不能被封入模块本身,所以需要以下的人工步骤.
1. 牵引线
settings.gradle 有条件地包括次级项目(单元是: devDependency; prod CI 运行 yarn install --production 而目录将不存在 :
def bundleLoaderDir = new File(rootProject.projectDir, '../node_modules/@exodus/react-native-bundle-loader/android')
if (bundleLoaderDir.exists()) {
include ':@exodus_react-native-bundle-loader'
project(':@exodus_react-native-bundle-loader').projectDir = bundleLoaderDir
}
app/build.gradle ——仅依赖调试构建 :
if (new File("$rootDir/../node_modules/@exodus/react-native-bundle-loader/android").exists()) {
debugImplementation project(':@exodus_react-native-bundle-loader')
}
2. 登记一揽子计划
单位 MainApplication.java内部 getPackages(),通过反射添加软件包,这样缺失的模块(在 prod CI 中缺失)不会导致编译时间错误:
if (BuildConfig.DEBUG) {
// devDependency absent in prod CI (yarn install --production); reflection avoids a compile-time import
try {
packages.add((ReactPackage) Class.forName("com.reactnativebundleloader.BundleLoaderPackage")
.getDeclaredConstructor().newInstance());
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
3. 将钩捆装入反大气层
将这三种方法添加到您的 ReactNativeHost 匿名子类 MainApplication.java数字 :
import java.io.File;
// ...
@Override
public boolean getUseDeveloperSupport() {
if (BuildConfig.DEBUG && hasPendingRemoteBundle()) {
// Must disable dev support: when enabled and Metro is reachable,
// ReactInstanceManager queries the packager and ignores getJSBundleFile().
return false;
}
// No pending bundle — clear the active flag so runningMode() returns LOCAL.
getSharedPreferences("BundleLoader", MODE_PRIVATE)
.edit().remove("active_remote_bundle").apply();
return BuildConfig.DEBUG;
}
@Override
protected String getJSBundleFile() {
if (BuildConfig.DEBUG && hasPendingRemoteBundle()) {
File cachedBundle = new File(getCacheDir(), "verified-bundle.jsbundle");
if (cachedBundle.exists()) {
// Consume the one-shot latch: next restart goes back to Metro.
getSharedPreferences("BundleLoader", MODE_PRIVATE).edit()
.remove("pending_remote_bundle")
.putBoolean("active_remote_bundle", true)
.apply();
return cachedBundle.getAbsolutePath();
}
}
return null;
}
private boolean hasPendingRemoteBundle() {
return getSharedPreferences("BundleLoader", MODE_PRIVATE)
.getBoolean("pending_remote_bundle", false);
}
共享首选项( E)"BundleLoader", (中文(简体) ), "pending_remote_bundle", (中文(简体) ), "active_remote_bundle")必须与定义在 BundleLoaderModule (单位:千美元)PREFS_NAME, (中文(简体) ), PREFS_PENDING_KEY, (中文(简体) ), PREFS_ACTIVE_KEY) (中文(简体) ).
证明
这是叉子 [email protected] 最初由Jusbrasil出版(2020-10-21,npm出版商) helielson,承诺 ec3d4520) (中文(简体) ). 上游 GitHub 重播时间 github.com/jusbrasil/react-native-bundle-loader 后来被删除。 通过 v.1.0 释放承诺保存完整的原始 git 历史; Android 执行是由 [email protected] 输入 mldb/react-native-bundle-loader 2021年出镜.
警卫
见 SECURITY.md 威胁模式、可接受的剩余风险和披露程序.
许可证
麻省理工学院