diff --git a/app/components/Views/Settings/ExperimentalSettings/__snapshots__/index.test.tsx.snap b/app/components/Views/Settings/ExperimentalSettings/__snapshots__/index.test.tsx.snap
index 74479e42982..ce70653185c 100644
--- a/app/components/Views/Settings/ExperimentalSettings/__snapshots__/index.test.tsx.snap
+++ b/app/components/Views/Settings/ExperimentalSettings/__snapshots__/index.test.tsx.snap
@@ -98,6 +98,113 @@ Array [
>
Security
+
+
+
+ Code Lockdown
+
+
+
+
+ This is a test feature that blocks any changes to the app's JavaScript code without permission.
+
+
+
+ Learn more
+
+
+ .
+
+
Security
+
+
+
+ Code Lockdown
+
+
+
+
+ This is a test feature that blocks any changes to the app's JavaScript code without permission.
+
+
+
+ Learn more
+
+
+ .
+
+
{
const securityAlertsEnabled = useSelector(selectIsSecurityAlertsEnabled);
+ const [sesEnabled, setSesEnabled] = useState(
+ storage.getBoolean('is-ses-enabled'),
+ );
+
+ const toggleSesEnabled = () => {
+ storage.set('is-ses-enabled', !sesEnabled);
+ setSesEnabled(!sesEnabled);
+ };
+
const isFullScreenModal = route?.params?.isFullScreenModal;
const theme = useTheme();
@@ -81,6 +96,8 @@ const ExperimentalSettings = ({ navigation, route }: Props) => {
navigation.navigate('WalletConnectSessionsView');
}, [navigation]);
+ const openSesLink = () => Linking.openURL(SES_URL);
+
const WalletConnectSettings: FC = () => (
<>
@@ -106,13 +123,15 @@ const ExperimentalSettings = ({ navigation, route }: Props) => {
const BlockaidSettings: FC = () => (
<>
-
- {strings('app_settings.security_heading')}
-
+ {Device.isAndroid() && (
+
+ {strings('app_settings.security_heading')}
+
+ )}
{strings('experimental_settings.security_alerts')}
@@ -153,9 +172,54 @@ const ExperimentalSettings = ({ navigation, route }: Props) => {
>
);
+ const SesSettings: FC = () => (
+ <>
+
+ {strings('app_settings.security_heading')}
+
+
+
+
+ {strings('app_settings.ses_heading')}
+
+
+
+
+ {strings('app_settings.ses_description')}{' '}
+
+ .
+
+
+ >
+ );
+
return (
+ {Device.isIos() && }
{isBlockaidFeatureEnabled() && }
);
diff --git a/app/constants/urls.ts b/app/constants/urls.ts
index ed8e13d91c8..30d848399a0 100644
--- a/app/constants/urls.ts
+++ b/app/constants/urls.ts
@@ -19,6 +19,10 @@ export const CONNECTING_TO_A_DECEPTIVE_SITE =
// Policies
export const CONSENSYS_PRIVACY_POLICY = 'https://consensys.net/privacy-policy/';
+// SES
+export const SES_URL =
+ 'https://github.com/endojs/endo/blob/master/packages/ses/README.md';
+
// Keystone
export const KEYSTONE_SUPPORT = 'https://keyst.one/mmm';
export const KEYSTONE_LEARN_MORE =
diff --git a/locales/languages/en.json b/locales/languages/en.json
index 0a106fa7638..a4f0387b5b0 100644
--- a/locales/languages/en.json
+++ b/locales/languages/en.json
@@ -759,6 +759,9 @@
"invalid_password": "Invalid password",
"invalid_password_message": "The password was not correct. Please try again.",
"security_heading": "Security",
+ "ses_heading": "Code Lockdown",
+ "ses_description": "This is a test feature that blocks any changes to the app's JavaScript code without permission.",
+ "ses_link": "Learn more",
"privacy_heading": "Privacy",
"failed_to_fetch_chain_id": "Could not fetch chain ID. Is your RPC URL correct?",
"endpoint_returned_different_chain_id": "The endpoint returned a different chain ID: %{chainIdReturned}",
diff --git a/package.json b/package.json
index f5ed5d12e10..b1ed7e41b0f 100644
--- a/package.json
+++ b/package.json
@@ -195,6 +195,7 @@
"@metamask/post-message-stream": "7.0.0",
"@metamask/ppom-validator": "0.22.0",
"@metamask/preferences-controller": "^4.0.0",
+ "@metamask/react-native-button": "^3.0.0",
"@metamask/scure-bip39": "^2.1.0",
"@metamask/sdk-communication-layer": "^0.12.0",
"@metamask/signature-controller": "4.0.1",
@@ -296,7 +297,6 @@
"react-native-blob-jsi-helper": "^0.3.1",
"react-native-branch": "^5.6.2",
"react-native-browser-polyfill": "0.1.2",
- "@metamask/react-native-button": "^3.0.0",
"react-native-camera": "^3.36.0",
"react-native-confetti": "^0.1.0",
"react-native-confetti-cannon": "^1.5.0",
diff --git a/patches/react-native+0.71.15.patch b/patches/react-native+0.71.15.patch
index 9ed3e02dcfd..331f0abf89d 100644
--- a/patches/react-native+0.71.15.patch
+++ b/patches/react-native+0.71.15.patch
@@ -1,31 +1,15 @@
diff --git a/node_modules/react-native/Libraries/Core/InitializeCore.js b/node_modules/react-native/Libraries/Core/InitializeCore.js
-index 1379ffd..8b198a9 100644
+index 1379ffd..f2825f5 100644
--- a/node_modules/react-native/Libraries/Core/InitializeCore.js
+++ b/node_modules/react-native/Libraries/Core/InitializeCore.js
-@@ -24,26 +24,53 @@
+@@ -24,26 +24,37 @@
'use strict';
+const Platform = require('../Utilities/Platform');
+
-+const IS_LOCKDOWN_ENABLED = true;
-+
-+if (IS_LOCKDOWN_ENABLED && Platform.OS === 'ios' && !global?.HermesInternal && !__DEV__) {
-+ require('../../../../ses.cjs');
-+ /**
-+ * Without consoleTaming: 'unsafe' causes:
-+ * - Attempting to define property on object that is not extensible.
-+ * Without errorTrapping 'none' causes:
-+ * - TypeError: undefined is not a function (near '...globalThis.process.on...')
-+ * Without unhandledRejectionTrapping 'none' causes:
-+ * - TypeError: globalThis.process.on is not a function. (In 'globalThis.process.on('unhandledRejection', h.unhandledRejectionHandler)', 'globalThis.process.on' is undefined)
-+ * overrideTaming 'severe' is ideal (default override?)
-+ * Nb: global.process is only partially shimmed, which confuses SES
-+ * Nb: All are Unhandled JS Exceptions, since we call lockdown before setUpErrorHandling
-+ */
-+ repairIntrinsics({ errorTaming: 'unsafe', consoleTaming: 'unsafe', errorTrapping: 'none', unhandledRejectionTrapping: 'none', overrideTaming: 'severe', stackFiltering: 'verbose' });
-+ require('reflect-metadata'); // Vetted shim required to fix: https://github.com/LavaMoat/docs/issues/26
-+ hardenIntrinsics();
++if (Platform.OS === 'ios' && !global?.HermesInternal) {
++ require('./setUpSes');
+}
+
const start = Date.now();
@@ -58,6 +42,72 @@ index 1379ffd..8b198a9 100644
const GlobalPerformanceLogger = require('../Utilities/GlobalPerformanceLogger');
// We could just call GlobalPerformanceLogger.markPoint at the top of the file,
+diff --git a/node_modules/react-native/Libraries/Core/setUpSes.js b/node_modules/react-native/Libraries/Core/setUpSes.js
+new file mode 100644
+index 0000000..6013411
+--- /dev/null
++++ b/node_modules/react-native/Libraries/Core/setUpSes.js
+@@ -0,0 +1,60 @@
++/**
++ * Copyright (c) 2024 MetaMask
++ *
++ * Permission to use, copy, modify, and/or distribute this software for any
++ * purpose with or without fee is hereby granted, provided that the above
++ * copyright notice and this permission notice appear in all copies.
++ *
++ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
++ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
++ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
++ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
++ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
++ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
++ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
++ *
++ * @flow strict-local
++ * @format
++ */
++
++'use strict';
++
++/**
++ * Set up Hardened JS (SES) via InitializeCore,
++ * provided the given MMKV storage key exists.
++ */
++
++import { MMKV } from 'react-native-mmkv';
++
++const storage = new MMKV(); // id: mmkv.default
++var isSesEnabled = storage.getBoolean('is-ses-enabled');
++
++// Enable SES by default
++if (isSesEnabled === undefined) {
++ isSesEnabled = true;
++ storage.set('is-ses-enabled', true);
++}
++
++/**
++ * SES disabled in debug-mode for now like we do in metamask-extension.
++ * To prevent react-jsx-runtime.development.js obscuring errors.
++ * See: https://github.com/MetaMask/metamask-mobile/issues/7923
++ */
++
++if (isSesEnabled && !__DEV__) {
++ require('../../../../ses.cjs');
++ /**
++ * Without consoleTaming: 'unsafe' causes:
++ * - Attempting to define property on object that is not extensible.
++ * Without errorTrapping 'none' causes:
++ * - TypeError: undefined is not a function (near '...globalThis.process.on...')
++ * Without unhandledRejectionTrapping 'none' causes:
++ * - TypeError: globalThis.process.on is not a function. (In 'globalThis.process.on('unhandledRejection', h.unhandledRejectionHandler)', 'globalThis.process.on' is undefined)
++ * overrideTaming 'severe' is ideal (default override?)
++ * Nb: global.process is only partially shimmed, which confuses SES
++ * Nb: All are Unhandled JS Exceptions, since we call lockdown before setUpErrorHandling
++ */
++ repairIntrinsics({ errorTaming: 'unsafe', consoleTaming: 'unsafe', errorTrapping: 'none', unhandledRejectionTrapping: 'none', overrideTaming: 'severe', stackFiltering: 'verbose' });
++ require('reflect-metadata'); // Vetted shim required to fix: https://github.com/LavaMoat/docs/issues/26
++ hardenIntrinsics();
++}
diff --git a/node_modules/react-native/ReactAndroid/build.gradle b/node_modules/react-native/ReactAndroid/build.gradle
index d03f3dd..816f559 100644
--- a/node_modules/react-native/ReactAndroid/build.gradle
@@ -187,4 +237,4 @@ index 290bd23..20d85e0 100644
+ }
}
return inputConnection;
- }
+ }
\ No newline at end of file