Skip to content

fix(native-profiler): prevent shell injection via device_id on iOS#378

Draft
latekvo wants to merge 1 commit into
mainfrom
fix/native-profiler-ios-shell-injection
Draft

fix(native-profiler): prevent shell injection via device_id on iOS#378
latekvo wants to merge 1 commit into
mainfrom
fix/native-profiler-ios-shell-injection

Conversation

@latekvo

@latekvo latekvo commented Jun 19, 2026

Copy link
Copy Markdown
Member

Summary

The iOS native profiler interpolated the caller-supplied device_id (udid) directly into shell-interpreted execSync template strings, so an unvalidated device_id could execute arbitrary shell commands.

In packages/tool-server/src/tools/profiler/native-profiler/platforms/ios.ts:

  • execSync(\xcrun simctl spawn ${udid} launchctl list`)`
  • execSync(\xcrun simctl listapps ${udid} | plutil -convert json -o - -`)`

device_id is declared z.string() with no format constraint and resolveDevice() returns it unchanged, so a value like `x"; touch /tmp/pwned #` runs as shell.

Fix

  • Convert both call sites to execFileSync arg-arrays (no shell). The simctl listapps | plutil pipe is split into two in-process stages so nothing is shell-interpreted.
  • Add a regression test asserting a hostile udid is only ever passed as a discrete argv element, with execSync mocked to fail loudly if it is ever reintroduced.

Context

This fix previously existed on an old branch but was lost when the code moved from tools/profiler/ios-profiler/ios-profiler-start.ts to native-profiler/platforms/ios.ts.

Verification

  • npm run build (tsc) ✓ · typecheck:tests ✓ · prettier --check ✓ · eslint --max-warnings 0
  • New test passes; reverting the fix back to execSync makes it fail (the payload booted"; touch /tmp/argent-pwned # reaches the shell command string), confirming it is a real guard.

The iOS native profiler interpolated the caller-supplied device_id (`udid`)
directly into shell-interpreted `execSync` template strings
(`xcrun simctl spawn ${udid} launchctl list` and
`xcrun simctl listapps ${udid} | plutil ...`). device_id is an unvalidated
`z.string()`, so a value like `x; <cmd>` executed arbitrary shell commands.

Switch both call sites to `execFileSync` arg-arrays (no shell), splitting the
`simctl listapps | plutil` pipe into two in-process stages. Add a regression
test asserting the udid is only ever passed as a discrete argv element.

The fix previously existed on a branch but was lost when this code moved from
tools/profiler/ios-profiler/ to native-profiler/platforms/ios.ts.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant