Skip to content

Instantly share code, notes, and snippets.

@leotm
Created April 17, 2025 16:33
Show Gist options
  • Save leotm/4afbbe5d9129b155ddc82f7b53d16b96 to your computer and use it in GitHub Desktop.
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
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