Skip to content

feat: native module substitution#20

Draft
CAMOBAP wants to merge 1 commit intomainfrom
feature/turbo-substitution
Draft

feat: native module substitution#20
CAMOBAP wants to merge 1 commit intomainfrom
feature/turbo-substitution

Conversation

@CAMOBAP
Copy link
Collaborator

@CAMOBAP CAMOBAP commented Mar 5, 2026

TurboModule Substitution for Sandboxed Native Modules

Summary

Adds a TurboModule substitution mechanism that allows host apps to transparently replace native modules inside sandboxes with scoped, security-hardened implementations. When a sandboxed JS bundle requests a module like RNFSManager, the sandbox delegate intercepts the resolution and provides a SandboxedRNFSManager instead — one that jails all file paths to a per-origin directory.

Motivation

Previously, sandboxed React Native instances shared the same native module instances as the host app. A sandboxed bundle using react-native-fs or AsyncStorage could read/write the host's data, defeating the purpose of isolation. This PR closes that gap by allowing the host to declare substitution mappings:

<SandboxReactNativeView
  turboModuleSubstitutions={{
    RNFSManager: 'SandboxedRNFSManager',
    FileAccess: 'SandboxedFileAccess',
    RNCAsyncStorage: 'SandboxedAsyncStorage',
  }}
/>

How it works

  1. New prop turboModuleSubstitutions — a Record<string, string> mapping requested module names to replacement module names. Substituted modules are implicitly allowed (no need to also list them in allowedTurboModules).

  2. RCTSandboxAwareModule / ISandboxAwareModule protocols — ObjC and C++ interfaces that substituted modules can adopt to receive sandbox context (origin, requested name, resolved name) at configuration time.

  3. Delegate-level interception via getTurboModule:jsInvoker: — the sandbox delegate constructs substituted ObjC modules directly in the C++ TurboModule resolution path, bypassing RCTTurboModuleManager's internal instance creation chain (which uses weak delegate references that can lose track of configured instances). Each module is created once, configured with sandbox context, and wrapped in ObjCInteropTurboModule.

  4. Unified sandbox root — all sandboxed file/storage modules share a single directory tree per origin: <AppSupport>/<bundleId>/Sandboxes/<origin>/, with subdirectories for Documents, Caches, tmp, Library, and AsyncStorage.

  5. Security hardening:

    • Modules reject all operations until explicitly configured via configureSandboxWithOrigin: — no default-origin backdoor.
    • Path jailing: all file paths are resolved and normalized, then validated to stay within the sandbox root. Relative paths resolve against the sandbox Documents directory. Traversal attempts (e.g., ../../) are rejected.

What's included

  • packages/react-native-sandbox: New turboModuleSubstitutions prop, RCTSandboxAwareModule / ISandboxAwareModule protocols, delegate substitution logic with SandboxNativeMethodCallInvoker for proper method queue dispatch.
  • apps/fs-experiment: Three reference sandboxed module implementations:
    • SandboxedRNFSManager — wraps react-native-fs with per-origin file jailing
    • SandboxedFileAccess — wraps react-native-file-access with per-origin file jailing
    • SandboxedRNCAsyncStorage — wraps @react-native-async-storage with per-origin storage isolation

Demo

  • Launch fs-experiment app with substitution toggle off — sandbox uses real modules, can read/write host data
  • Toggle substitution on — sandbox switches to sandboxed modules, writes go to isolated Sandboxes/<origin>/ directory
  • Verify host writes are not visible from sandbox and vice versa
  • Verify path traversal attempts (e.g., ../../Library/...) are rejected
  • Verify AsyncStorage keys are isolated between host and sandbox
  • Verify toggling substitution off and on correctly recreates the sandbox instance

@CAMOBAP CAMOBAP self-assigned this Mar 5, 2026
@CAMOBAP CAMOBAP force-pushed the feature/turbo-substitution branch from 9c465f2 to 3d8c973 Compare March 5, 2026 10:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants