security: harden loader — remove unverified load(), atomic verified install - #2
Merged
Merged
Conversation
…nstall - Remove the unverified load() path: iOS load:, Android load() + downloadToCache, the JS load/loadUnverified exports, and the BundlePrompt UI. Only loadVerified remains — loading a remote bundle without native SHA-256 verification is an unauthenticated RCE primitive. - Android: download to a temp file, verify, then atomically promote it to the canonical verified-bundle.jsbundle only after the hash matches; delete the temp on mismatch or error. The canonical path never holds unverified/partial bytes (fixes verify-after-write TOCTOU). - iOS: cap the downloaded bundle at 64 MB (parity with Android) before it is hashed, written, or loaded. - Tests: drop the removed load() cases; add VerifyAndInstallTest. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Remove unverified-load / BundlePrompt / Metro-tunnel docs and the load(url) API row. - Fix stale native descriptions: iOS uses NSUserDefaults + loadSourceForBridge (not KVC bundleURL); Android downloads to temp then atomically promotes. - SECURITY.md: drop obsolete mBundleLoader-reflection / KVC residual risks; document the removed unverified path, atomic verified install, size cap, and the host-driven session scoping. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Review follow-up: the JS native-module mock no longer declares or stubs the removed native `load` method, and the iOS mock-bridge comment now reflects that the module writes the pending URL to NSUserDefaults and calls [_bridge reload] only (it no longer sets a value on the bridge).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Overview
Security hardening of the bundle loader, following up on the app-sec review of the dynamic dev-bundle feature. The goal is to make the library safe in isolation: it should never load a remote bundle without native integrity verification, and never leave unverified or partial bytes on disk.
load()path entirely — iOSload:, Androidload()+downloadToCache, the JSload/loadUnverifiedexports, and theBundlePromptURL-typing UI. Loading a remote bundle without native SHA-256 verification is an unauthenticated RCE primitive; onlyloadVerified(url, sha256)remains.verified-bundle.jsbundle(same-directory rename) only on a hash match; delete the temp on any mismatch or error. The canonical path never holds unverified or partial bytes (closes the verify-after-write TOCTOU).No version bump in this PR — the version will be bumped in a separate commit after merge so the bump is visible and decoupled from these changes.
Session-scoping (clearing the active/pending remote bundle on cold start) intentionally stays host-app-driven; it must run in the app's boot path before RN loads, which a RN native module can't reach. See
SECURITY.md→ Accepted residual risks.Verification
verify-pack(tarball allowlist).VerifyAndInstallTest— promote-on-match, delete+no-target-on-mismatch, no-clobber, replace).