Skip to content

Commit 8ec0143

Browse files
committed
feat: add security demo app
1 parent 079156e commit 8ec0143

55 files changed

Lines changed: 4307 additions & 4 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ This project is structured as a monorepo.
3636
- [`packages/react-native-sandbox`](./packages/react-native-sandbox/): the core library.
3737
- [`apps/side-by-side`](./apps/side-by-side/README.md): An example application with two sandbox instances.
3838
- [`apps/recursive`](./apps/recursive/README.md): An example application with few nested sandbox instances.
39+
- [`apps/demo`](./apps/demo/README.md): Security demo.
3940

4041
To run the examples:
4142

@@ -134,7 +135,7 @@ AppRegistry.registerComponent("SandboxApp", () => App);
134135
We're actively working on expanding the capabilities of `react-native-sandbox`. Here's what's planned:
135136
136137
- [ ] **Android Support** - Full cross-platform compatibility
137-
- [ ] **Inter-Sandbox Communication** - Secure communication between sandbox instances
138+
- [ ] **Inter-Sandbox Communication** - Secure direct communication between sandbox instances
138139
- [ ] **[RE.Pack](https://github.com/callstack/repack) Integration** - Advanced bundling and module federation
139140
- Hot-reloading for sandbox instances in development
140141
- Dynamic bundle fetching from remote sources
@@ -144,6 +145,7 @@ We're actively working on expanding the capabilities of `react-native-sandbox`.
144145
- Custom permission system for sandbox instances
145146
- Resource usage limits and monitoring
146147
- Sandbox capability restrictions
148+
- Unresponsiveness detection
147149
- [ ] **Storage Isolation** - Secure data partitioning
148150
- Per-sandbox AsyncStorage isolation
149151
- Secure file system access controls

apps/demo/.bundle/config

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
BUNDLE_PATH: "vendor/bundle"
2+
BUNDLE_FORCE_RUBY_PLATFORM: 1

apps/demo/App.tsx

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import React from 'react'
2+
import {SafeAreaView, StyleSheet, Text, View} from 'react-native'
3+
import SandboxReactNativeView from 'react-native-sandbox'
4+
import Toast from 'react-native-toast-message'
5+
6+
import CrashIfYouCanDemo from './CrashIfYouCanDemo'
7+
8+
const SideBySideDemo: React.FC = () => {
9+
return (
10+
<SafeAreaView>
11+
<View style={styles.container}>
12+
<View style={styles.column}>
13+
<Text style={styles.header}>Main App</Text>
14+
<CrashIfYouCanDemo />
15+
</View>
16+
<View style={styles.divider} />
17+
<View style={styles.column}>
18+
<Text style={styles.header}>Sandboxed</Text>
19+
<SandboxReactNativeView
20+
style={styles.sandboxView}
21+
jsBundleSource={'sandbox'}
22+
moduleName={'CrashIfYouCanDemo'}
23+
onError={error => {
24+
const message = `Got ${error.isFatal ? 'fatal' : 'non-fatal'} error from sandbox`
25+
console.warn(message, error)
26+
Toast.show({
27+
type: 'error',
28+
text1: message,
29+
text2: `${error.name}: ${error.message}`,
30+
visibilityTime: 5000,
31+
})
32+
return false
33+
}}
34+
/>
35+
</View>
36+
</View>
37+
<Toast />
38+
</SafeAreaView>
39+
)
40+
}
41+
42+
const styles = StyleSheet.create({
43+
container: {
44+
flexDirection: 'row',
45+
padding: 16,
46+
justifyContent: 'space-between',
47+
},
48+
column: {
49+
flex: 1,
50+
padding: 8,
51+
},
52+
divider: {
53+
width: 1,
54+
backgroundColor: '#ccc',
55+
},
56+
header: {
57+
fontWeight: 'bold',
58+
fontSize: 16,
59+
marginBottom: 8,
60+
textAlign: 'center',
61+
},
62+
sandboxView: {
63+
flex: 1,
64+
padding: 30,
65+
},
66+
})
67+
68+
export default SideBySideDemo

apps/demo/CrashIfYouCanDemo.tsx

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import React from 'react'
2+
import {Button, NativeModules, ScrollView, StyleSheet, View} from 'react-native'
3+
4+
export default function CrashIfYouCanDemo() {
5+
const triggerCrash = () => {
6+
// @ts-ignore
7+
global.nonExistentMethod() // Should crash the app
8+
}
9+
10+
const overwriteGlobal = () => {
11+
// Overwrite console.log to something harmful
12+
console.log = () => {
13+
throw new Error('console.log has been hijacked!')
14+
}
15+
console.log('This will now throw') // This will crash or break logs
16+
}
17+
18+
const accessBlockedTurboModule = () => {
19+
const FileReaderModule = NativeModules.FileReaderModule
20+
FileReaderModule.readAsText('/some/file.txt')
21+
.then((text: string) => console.log(text))
22+
.catch((err: any) => console.log(err.message))
23+
}
24+
25+
const infiniteLoop = () => {
26+
while (true) {}
27+
}
28+
29+
return (
30+
<ScrollView contentContainerStyle={styles.container}>
31+
<Button title="1. Crash App (undefined global)" onPress={triggerCrash} />
32+
<View style={styles.spacer} />
33+
<Button
34+
title="2. Overwrite Global (console.log)"
35+
onPress={overwriteGlobal}
36+
/>
37+
<View style={styles.spacer} />
38+
<Button
39+
title="3. Access Blocked TurboModule"
40+
onPress={accessBlockedTurboModule}
41+
/>
42+
<View style={styles.spacer} />
43+
<Button title="4. Infinite Loop" onPress={infiniteLoop} />
44+
<View style={styles.bottom} />
45+
</ScrollView>
46+
)
47+
}
48+
49+
const styles = StyleSheet.create({
50+
container: {
51+
padding: 16,
52+
justifyContent: 'center',
53+
},
54+
spacer: {
55+
height: 16,
56+
},
57+
bottom: {
58+
height: 36,
59+
},
60+
})

apps/demo/README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Security Demo
2+
3+
![Platform: iOS](https://img.shields.io/badge/platform-iOS-blue.svg)
4+
5+
This demo showcases a side-by-side comparison between a regular React Native component and one executed inside a `react-native-sandbox`ed environment. The `CrashIfYouCanDemo` component intentionally triggers various types of failures — such as accessing undefined globals, overwriting global variables, invoking blocked native modules, infinite loops. When run in the main app context, these actions can crash or destabilize the app. However, when isolated inside the `SandboxReactNativeView`, they are safely contained without affecting the host application. This demonstrates how sandboxing can enhance reliability and security in untrusted or third-party React Native code execution.
6+
7+
## Screenshot
8+
9+
<div style="display: flex; flex-wrap: wrap; gap: 10px; align: center">
10+
<img src="./docs/screenshot.png" width="240" />
11+
</div>

apps/demo/__tests__/App.test.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/**
2+
* @format
3+
*/
4+
5+
import React from 'react'
6+
import ReactTestRenderer from 'react-test-renderer'
7+
8+
import App from '../App'
9+
10+
test('renders correctly', async () => {
11+
await ReactTestRenderer.act(() => {
12+
ReactTestRenderer.create(<App />)
13+
})
14+
})

apps/demo/android/app/build.gradle

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
apply plugin: "com.android.application"
2+
apply plugin: "org.jetbrains.kotlin.android"
3+
apply plugin: "com.facebook.react"
4+
5+
/**
6+
* This is the configuration block to customize your React Native Android app.
7+
* By default you don't need to apply any configuration, just uncomment the lines you need.
8+
*/
9+
react {
10+
/* Folders */
11+
// The root of your project, i.e. where "package.json" lives. Default is '../..'
12+
// root = file("../../")
13+
// The folder where the react-native NPM package is. Default is ../../node_modules/react-native
14+
// reactNativeDir = file("../../node_modules/react-native")
15+
// The folder where the react-native Codegen package is. Default is ../../node_modules/@react-native/codegen
16+
// codegenDir = file("../../node_modules/@react-native/codegen")
17+
// The cli.js file which is the React Native CLI entrypoint. Default is ../../node_modules/react-native/cli.js
18+
// cliFile = file("../../node_modules/react-native/cli.js")
19+
20+
/* Variants */
21+
// The list of variants to that are debuggable. For those we're going to
22+
// skip the bundling of the JS bundle and the assets. By default is just 'debug'.
23+
// If you add flavors like lite, prod, etc. you'll have to list your debuggableVariants.
24+
// debuggableVariants = ["liteDebug", "prodDebug"]
25+
26+
/* Bundling */
27+
// A list containing the node command and its flags. Default is just 'node'.
28+
// nodeExecutableAndArgs = ["node"]
29+
//
30+
// The command to run when bundling. By default is 'bundle'
31+
// bundleCommand = "ram-bundle"
32+
//
33+
// The path to the CLI configuration file. Default is empty.
34+
// bundleConfig = file(../rn-cli.config.js)
35+
//
36+
// The name of the generated asset file containing your JS bundle
37+
// bundleAssetName = "MyApplication.android.bundle"
38+
//
39+
// The entry file for bundle generation. Default is 'index.android.js' or 'index.js'
40+
// entryFile = file("../js/MyApplication.android.js")
41+
//
42+
// A list of extra flags to pass to the 'bundle' commands.
43+
// See https://github.com/react-native-community/cli/blob/main/docs/commands.md#bundle
44+
// extraPackagerArgs = []
45+
46+
/* Hermes Commands */
47+
// The hermes compiler command to run. By default it is 'hermesc'
48+
// hermesCommand = "$rootDir/my-custom-hermesc/bin/hermesc"
49+
//
50+
// The list of flags to pass to the Hermes compiler. By default is "-O", "-output-source-map"
51+
// hermesFlags = ["-O", "-output-source-map"]
52+
53+
/* Autolinking */
54+
autolinkLibrariesWithApp()
55+
}
56+
57+
/**
58+
* Set this to true to Run Proguard on Release builds to minify the Java bytecode.
59+
*/
60+
def enableProguardInReleaseBuilds = false
61+
62+
/**
63+
* The preferred build flavor of JavaScriptCore (JSC)
64+
*
65+
* For example, to use the international variant, you can use:
66+
* `def jscFlavor = io.github.react-native-community:jsc-android-intl:2026004.+`
67+
*
68+
* The international variant includes ICU i18n library and necessary data
69+
* allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that
70+
* give correct results when using with locales other than en-US. Note that
71+
* this variant is about 6MiB larger per architecture than default.
72+
*/
73+
def jscFlavor = 'io.github.react-native-community:jsc-android:2026004.+'
74+
75+
android {
76+
ndkVersion rootProject.ext.ndkVersion
77+
buildToolsVersion rootProject.ext.buildToolsVersion
78+
compileSdk rootProject.ext.compileSdkVersion
79+
80+
namespace "com.demo"
81+
defaultConfig {
82+
applicationId "com.demo"
83+
minSdkVersion rootProject.ext.minSdkVersion
84+
targetSdkVersion rootProject.ext.targetSdkVersion
85+
versionCode 1
86+
versionName "1.0"
87+
}
88+
signingConfigs {
89+
debug {
90+
storeFile file('debug.keystore')
91+
storePassword 'android'
92+
keyAlias 'androiddebugkey'
93+
keyPassword 'android'
94+
}
95+
}
96+
buildTypes {
97+
debug {
98+
signingConfig signingConfigs.debug
99+
}
100+
release {
101+
// Caution! In production, you need to generate your own keystore file.
102+
// see https://reactnative.dev/docs/signed-apk-android.
103+
signingConfig signingConfigs.debug
104+
minifyEnabled enableProguardInReleaseBuilds
105+
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
106+
}
107+
}
108+
}
109+
110+
dependencies {
111+
// The version of react-native is set by the React Native Gradle Plugin
112+
implementation("com.facebook.react:react-android")
113+
114+
if (hermesEnabled.toBoolean()) {
115+
implementation("com.facebook.react:hermes-android")
116+
} else {
117+
implementation jscFlavor
118+
}
119+
}
2.2 KB
Binary file not shown.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Add project specific ProGuard rules here.
2+
# By default, the flags in this file are appended to flags specified
3+
# in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt
4+
# You can edit the include path and order by changing the proguardFiles
5+
# directive in build.gradle.
6+
#
7+
# For more details, see
8+
# http://developer.android.com/guide/developing/tools/proguard.html
9+
10+
# Add any project specific keep options here:
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
3+
xmlns:tools="http://schemas.android.com/tools">
4+
5+
<application
6+
android:usesCleartextTraffic="true"
7+
tools:targetApi="28"
8+
tools:ignore="GoogleAppIndexingWarning"/>
9+
</manifest>

0 commit comments

Comments
 (0)