Created
April 17, 2025 16:33
-
-
Save leotm/4afbbe5d9129b155ddc82f7b53d16b96 to your computer and use it in GitHub Desktop.
react-native-lockdown serializer: more elaborate version that hooks into the RN InitializeCore moduleId to make sure it runs first
This file contains 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
const { warn } = console | |
const { assign } = Object | |
const path = require('node:path') | |
const defaultGetRunModuleStatement = (moduleId) => `__r(${moduleId})` | |
module.exports = { | |
lockdownSerializer: ({ hermesRuntime = false } = {}, originalConfig) => { | |
if (originalConfig.getRunModuleStatement) { | |
warn( | |
'LavaMoat: You are using getRunModuleStatement in serializer config. Lavamoat will attempt to wrap it without breaking it, but if you are doing something unusual, we might affect how it works' | |
) | |
} | |
const config = assign({}, originalConfig) | |
const InitializeCoreRegExp = /react-native.*InitializeCore/ | |
let reactNativeInitializeCoreModuleId | |
config.createModuleIdFactory = () => { | |
const moduleIdIndex = [] | |
const moduleIdFactory = | |
// originalConfig.createModuleIdFactory() || // TypeError: originalConfig.createModuleIdFactory is not a function | |
originalConfig.createModuleIdFactory || | |
((mPath) => { | |
moduleIdIndex.push(mPath) | |
return moduleIdIndex.length - 1 | |
}) | |
return (path) => { | |
// const moduleId = moduleIdFactory(moduleId) // ReferenceError: Cannot access 'moduleId' before initialization | |
const moduleId = moduleIdFactory(path) | |
if (InitializeCoreRegExp.test(path)) { | |
if (reactNativeInitializeCoreModuleId) { | |
warn( | |
'LavaMoat: fond another module that seems to be the react-native InitializeCore modules. This is suspicious. ' + | |
path | |
) | |
} else { | |
reactNativeInitializeCoreModuleId = moduleId | |
} | |
} | |
} | |
} | |
// getRunModuleStatement | |
let firstCallGetRunModuleStatement = true | |
const previousGetRunModuleStatement = | |
originalConfig.getRunModuleStatement ?? defaultGetRunModuleStatement | |
config.getRunModuleStatement = (moduleId) => { | |
// assumes react-native InitializeCore is the first thing that gets required. | |
const runModuleStatement = previousGetRunModuleStatement(moduleId) | |
if (firstCallGetRunModuleStatement) { | |
firstCallGetRunModuleStatement = false | |
if (reactNativeInitializeCoreModuleId === moduleId) { | |
return runModuleStatement + ';hardenIntrinsics();' | |
} else { | |
throw Error( | |
'LavaMoat: the first thing to run in your bundle should be react-native InitializeCore module, but something else is running first. This is suspicious. Lockdown cannot be safely applied' | |
) | |
} | |
} | |
return runModuleStatement | |
} | |
// Default RN JS polyfills | |
if (!config.getPolyfills) { | |
config.getPolyfills = require('@react-native/js-polyfills') | |
} else if (typeof config.getPolyfills !== 'function') { | |
throw new Error('serializer.getPolyfills must be a function') | |
} | |
// SES | |
const ses = hermesRuntime | |
? require.resolve('ses/hermes') | |
: require.resolve('ses') | |
const repair = require.resolve('./repair.js') | |
// getRunModuleStatement | |
const originalPolyfills = config.getPolyfills | |
config.getPolyfills = () => { | |
const polyfills = originalPolyfills() | |
// polyfills = polyfills.flat() // if user forgets to spread an array from e.g. RN js polyfills | |
// this.validatePolyfills(polyfills) | |
return [ses, repair, ...polyfills] | |
} | |
return config | |
}, | |
validatePolyfills: function validatePolyfills(polyfills) { | |
for (const polyfill of polyfills) { | |
if (!Array.isArray(polyfills)) { | |
throw new Error( | |
`Expected polyfills to be an array of strings, but received ${typeof polyfills}` | |
) | |
} | |
if (typeof polyfill !== 'string') { | |
if (typeof polyfill === 'function') { | |
throw new Error( | |
`Expected polyfills to be an array of strings, but found a function. Looks like you're passing react-native/js-polyfills but not calling the function they export. Yes, it's not very intuitive, but it is what it is.` | |
) | |
} | |
throw new Error( | |
`Expected polyfills to be an array of strings, but received ${typeof polyfill}` | |
) | |
} else { | |
// make sure the polyfill is a resolved path not just a package name | |
if (!path.isAbsolute(polyfill) && !polyfill.startsWith('.')) { | |
throw new Error( | |
`Polyfill must be a resolved path, not just a package name: ${polyfill}` | |
) | |
} | |
} | |
} | |
}, | |
} | |
// ➜ RN07215SES git:(main) ✗ yarn bundle:android:dev | |
// warning: the transform cache was reset. | |
// Welcome to Metro v0.76.9 | |
// Fast - Scalable - Integrated | |
// LavaMoat: fond another module that seems to be the react-native InitializeCore modules. This is suspicious. /Users/leo/Documents/GitHub/RN07215SES/node_modules/react-native/Libraries/Core/InitializeCore.js | |
// LavaMoat: fond another module that seems to be the react-native InitializeCore modules. This is suspicious. /Users/leo/Documents/GitHub/RN07215SES/node_modules/react-native/Libraries/ReactPrivate/ReactNativePrivateInitializeCore.js | |
// LavaMoat: fond another module that seems to be the react-native InitializeCore modules. This is suspicious. /Users/leo/Documents/GitHub/RN07215SES/node_modules/react-native/Libraries/Core/InitializeCore.js | |
// LavaMoat: fond another module that seems to be the react-native InitializeCore modules. This is suspicious. /Users/leo/Documents/GitHub/RN07215SES/node_modules/react-native/Libraries/ReactPrivate/ReactNativePrivateInitializeCore.js | |
// LavaMoat: fond another module that seems to be the react-native InitializeCore modules. This is suspicious. /Users/leo/Documents/GitHub/RN07215SES/node_modules/react-native/Libraries/Core/InitializeCore.js | |
// LavaMoat: fond another module that seems to be the react-native InitializeCore modules. This is suspicious. /Users/leo/Documents/GitHub/RN07215SES/node_modules/react-native/Libraries/Core/InitializeCore.js | |
// error LavaMoat: the first thing to run in your bundle should be react-native InitializeCore module, but something else is running first. This is suspicious. Lockdown cannot be safely applied. | |
// Error: LavaMoat: the first thing to run in your bundle should be react-native InitializeCore module, but something else is running first. This is suspicious. Lockdown cannot be safely applied | |
// at config.getRunModuleStatement (/Users/leo/Documents/GitHub/RN07215SES/node_modules/@lavamoat/react-native-lockdown/src/index.js:58:17) | |
// at getAppendScripts (/Users/leo/Documents/GitHub/RN07215SES/node_modules/metro/src/lib/getAppendScripts.js:32:30) | |
// at baseJSBundle (/Users/leo/Documents/GitHub/RN07215SES/node_modules/metro/src/DeltaBundler/Serializers/baseJSBundle.js:41:5) | |
// at Server.build (/Users/leo/Documents/GitHub/RN07215SES/node_modules/metro/src/Server.js:161:9) | |
// at process.processTicksAndRejections (node:internal/process/task_queues:95:5) | |
// at async buildBundleWithConfig (/Users/leo/Documents/GitHub/RN07215SES/node_modules/@react-native-community/cli-plugin-metro/build/commands/bundle/buildBundle.js:88:20) | |
// at async Command.handleAction (/Users/leo/Documents/GitHub/RN07215SES/node_modules/@react-native-community/cli/build/index.js:111:9) | |
// info Run CLI with --verbose flag for more details. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment