Execute React Native micro-apps with confidence using react-native-sandbox
npm install @callstack/react-native-sandboxreact-native-sandbox is a library for running multiple, isolated React Native instances within a single application. This allows you to embed third-party or feature-specific "micro-apps" in a sandboxed environment, preventing uncontrolled interference with the main app by providing a clear API for communication (postMessage/onMessage).
This project was born from the need to safely run third-party code within a react-native application. The core requirements are:
- Isolation: Run external code in a secure, sandboxed environment.
- Safety: Prevent direct access to the host application's code and data.
- Communication: Provide a safe and explicit API for communication between the host and the sandboxed instances.
Note that
postMessageonly supports serializable data (similar toWindow.postMessagein web browsers), meaning no functions, native state, or non-serializable objects can be passed.
react-native-sandbox provides the API to create these sandboxed React Native instances with a simple component-based API, requiring no native code to be written by the consumer.
This project is structured as a monorepo.
packages/react-native-sandbox: the core libraryapps/*: examples
To run the examples:
-
Install dependencies:
bun install cd apps/<specific-example> bun install
-
Run the example application:
bun ios
Here is a brief overview of how to use the library.
import React, { useRef } from 'react';
import { View, Button } from 'react-native';
import SandboxReactNativeView, { SandboxReactNativeViewRef } from 'react-native-sandbox';
function HostApp() {
const sandboxRef = useRef<SandboxReactNativeViewRef>(null);
const handleMessage = (message) => console.log("Message received from sandbox:", message);
const handleError = (error) => console.error("Error in sandbox:", error);
const sendMessageToSandbox = () => sandboxRef.current?.postMessage({ data: "Hello from the host!" });
return (
<View>
<Button onPress={sendMessageToSandbox} title="Send to Sandbox" />
<SandboxReactNativeView ref={sandboxRef}
jsBundleSource={"sandbox"} // The JS bundle: file name or URL
componentName={"SandboxApp"} // Name of component registered in bundle provided with jsBundleSource
onMessage={handleMessage}
onError={handleError}
/>
</View>
);
}import React, { useState, useEffect, useCallback } from 'react';
import { View, Button, Text } from 'react-native';
// No import required, API available through global
declare global {
var postMessage: (message: object) => void;
var setOnMessage: (handler: (payload: object) => void) => void;
}
function SandboxApp() {
const [data, setData] = useState<string | undefined>();
// Listen for messages from the host
const onMessage = useCallback((payload: unknown) => {
setData(JSON.stringify(payload));
}, []);
useEffect(() => {
globalThis.setOnMessage(onMessage);
}, [onMessage]);
// Send a message back to the host
const postMessageToHost = () => {
// `postMessage` is also injected into the global scope
globalThis.postMessage({ data: 'Hello from the Sandbox!' });
};
return (
<View>
<Button onPress={postMessageToHost} title="Send Data to Host" />
<Text>Received: {data}</Text>
</View>
);
}
AppRegistry.registerComponent("SandboxApp", () => App);Full examples:
apps/demo: Security demo.apps/side-by-side: An example application with two sandbox instances.apps/recursive: An example application with few nested sandbox instances.apps/p2p-chat: Direct sandbox-to-sandbox chat demo.apps/p2p-counter: Direct sandbox-to-sandbox communication demo.apps/fs-experiment: File system & storage isolation with TurboModule substitutions.
For comprehensive API documentation, installation instructions, and advanced usage patterns, see the package documentation.
We're actively working on expanding the capabilities of react-native-sandbox. Here's what's planned:
- Android Support - Full cross-platform compatibility
- Inter-Sandbox Communication - Secure direct communication between sandbox instances
- RE.Pack Integration - Advanced bundling and module federation
- Hot-reloading for sandbox instances in development
- Dynamic bundle fetching from remote sources
- Optimized bundle splitting for sandbox applications
- Module federation capabilities
- Enhanced Security Features - Advanced security mechanisms
- Custom permission system for sandbox instances
- Resource usage limits and monitoring
- Sandbox capability restrictions
- Unresponsiveness detection
- TurboModule Substitutions - Replace native module implementations per sandbox
- Configurable via
turboModuleSubstitutionsprop (JS/TS only) - Sandbox-aware modules receive origin context for per-instance scoping
- TurboModules (New Architecture / Fabric) only
- Configurable via
- Storage & File System Isolation - Secure data partitioning
- Per-sandbox AsyncStorage isolation via scoped storage directories
- Sandboxed file system access (react-native-fs, react-native-file-access) with path jailing
- All directory constants overridden to sandbox-scoped paths
- Network/system operations blocked in sandboxed FS modules
- Developer Tools - Enhanced debugging and development experience
Contributions and feedback on these roadmap items are welcome! Please check our issues for detailed discussions on each feature.
A primary security concern when running multiple React Native instances is the potential for state sharing through native modules, especially TurboModules.
- Static State: If a TurboModule is implemented with static fields or as a singleton in native code, this single instance will be shared across all React Native instances (the host and all sandboxes).
- Data Leakage: One sandbox could use a shared TurboModule to store data, which could then be read by another sandbox or the host. This breaks the isolation model.
- Unintended Side-Effects: A sandbox could call a method on a shared module that changes its state, affecting the behavior of the host or other sandboxes in unpredictable ways.
To address this, react-native-sandbox provides two mechanisms:
-
TurboModule Allowlisting β Use the
allowedTurboModulesprop to control which native modules the sandbox can access. Only modules in this list are resolved; all others return a stub that rejects with a clear error. -
TurboModule Substitutions β Use the
turboModuleSubstitutionsprop to transparently replace a module with a sandbox-aware alternative. For example, when sandbox JS requestsRNCAsyncStorage, the host can resolve different implementation likeSandboxedAsyncStorageinstead β an implementation that scopes storage to the sandbox's origin. Substituted modules that conform toRCTSandboxAwareModule(ObjC) orISandboxAwareModule(C++) receive the sandbox context (origin, requested name, resolved name) after instantiation.
<SandboxReactNativeView
allowedTurboModules={['RNFSManager', 'FileAccess', 'RNCAsyncStorage']}
turboModuleSubstitutions={{
RNFSManager: 'SandboxedRNFSManager',
FileAccess: 'SandboxedFileAccess',
RNCAsyncStorage: 'SandboxedAsyncStorage',
}}
/>Default Allowlist: A baseline set of essential React Native modules is allowed by default (e.g., EventDispatcher, AppState, Appearance, Networking, DeviceInfo, KeyboardObserver, and others required for basic rendering and dev tooling). See the full list in source. Third-party modules and storage/FS modules are not included β they must be explicitly added via allowedTurboModules or provided through turboModuleSubstitutions.
- Resource Exhaustion (Denial of Service): A sandboxed instance could intentionally or unintentionally consume excessive CPU or memory, potentially leading to a denial-of-service attack that slows down or crashes the entire application. The host should be prepared to monitor and terminate misbehaving instances.
- Persistent Storage Conflicts: Standard APIs like
AsyncStorageare not instance-aware by default, potentially allowing a sandbox to read or overwrite data stored by the host or other sandboxes. UseturboModuleSubstitutionsto replace these modules with sandbox-aware implementations that scope data per origin. - File System Path Jailing: Sandboxed file system modules (
SandboxedRNFSManager,SandboxedFileAccess) override directory constants and validate all path arguments, ensuring file operations are confined to a per-origin sandbox directory. Paths outside the sandbox root are rejected withEPERM. - Network Operations Blocked: Sandboxed FS modules block download/upload/fetch operations to prevent data exfiltration.
See the apps/fs-experiment example for a working demonstration.
- iOS
NSNotificationCenter: The underlying React Native framework uses the defaultNSNotificationCenterfor internal communication. Because the same framework instance is shared between the host and sandboxes, it is theoretically possible for an event in one JS instance to trigger a notification that affects another. This could lead to unintended state changes or interference. While not observed during development, this remains a potential risk.