[GH-ISSUE #4181] [Bug] open_binary follows a symlinked Contents/MacOS out of the app bundle #1637

Closed
opened 2026-10-16 21:08:16 +00:00 by bsakaguchi · 2 comments
Owner

Originally created by @JakubIwicki on GitHub (Oct 15, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/4181

Version: rea-agents 6.4.0 (Linux x64). dist/application/AppBundleExecutable.js is unchanged in 6.5.0.

What happens: open_binary on an .app whose Contents/MacOS is a symlink to a directory outside the bundle opens a file outside the bundle.

Repro:

mkdir -p /tmp/repro/Evil.app/Contents
ln -s /usr/bin /tmp/repro/Evil.app/Contents/MacOS
cat > /tmp/repro/Evil.app/Contents/Info.plist <<'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<plist version="1.0"><dict><key>CFBundleExecutable</key><string>true</string></dict></plist>
EOF

Then open_binary with path: /tmp/repro/Evil.app returns:

{"result":{"path":"/usr/bin/true","format":"elf","kind":"executable", ...}}

Why: resolveAppBundleExecutable checks isPathWithinRoot(realpath(programs), realpath(executable)). Both sides are resolved, so when programs itself is a symlink the check compares /usr/bin with /usr/bin/true and passes. The bundle root is never part of the check. layoutPlistPresent also uses stat, so a symlinked Info.plist pointing outside the bundle is accepted too.

Expected: reject (or at least report) a program file whose canonical path is outside the canonical bundle root, the same way app program file leaves Contents/MacOS is reported today.

Impact: an untrusted bundle can point analysis at any readable host file. Callers that confine rea to a directory by checking the path argument (we do this with a PreToolUse hook) cannot see this, because the argument itself is inside the allowed tree.

Originally created by @JakubIwicki on GitHub (Oct 15, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/4181 **Version:** rea-agents 6.4.0 (Linux x64). `dist/application/AppBundleExecutable.js` is unchanged in 6.5.0. **What happens:** `open_binary` on an `.app` whose `Contents/MacOS` is a symlink to a directory outside the bundle opens a file outside the bundle. **Repro:** ```sh mkdir -p /tmp/repro/Evil.app/Contents ln -s /usr/bin /tmp/repro/Evil.app/Contents/MacOS cat > /tmp/repro/Evil.app/Contents/Info.plist <<'EOF' <?xml version="1.0" encoding="UTF-8"?> <plist version="1.0"><dict><key>CFBundleExecutable</key><string>true</string></dict></plist> EOF ``` Then `open_binary` with `path: /tmp/repro/Evil.app` returns: ```json {"result":{"path":"/usr/bin/true","format":"elf","kind":"executable", ...}} ``` **Why:** `resolveAppBundleExecutable` checks `isPathWithinRoot(realpath(programs), realpath(executable))`. Both sides are resolved, so when `programs` itself is a symlink the check compares `/usr/bin` with `/usr/bin/true` and passes. The bundle root is never part of the check. `layoutPlistPresent` also uses `stat`, so a symlinked `Info.plist` pointing outside the bundle is accepted too. **Expected:** reject (or at least report) a program file whose canonical path is outside the canonical bundle root, the same way `app program file leaves Contents/MacOS` is reported today. **Impact:** an untrusted bundle can point analysis at any readable host file. Callers that confine rea to a directory by checking the `path` argument (we do this with a PreToolUse hook) cannot see this, because the argument itself is inside the allowed tree.
Author
Owner

@gongfpp commented on GitHub (Oct 15, 2026):

I'd like to take this issue and investigate a focused fix with regression coverage for the app-bundle symlink boundary. I'll keep the change limited to the selected bundle and report the platform coverage.

<!-- gh-comment-id:6197965043 --> @gongfpp commented on GitHub (Oct 15, 2026): I'd like to take this issue and investigate a focused fix with regression coverage for the app-bundle symlink boundary. I'll keep the change limited to the selected bundle and report the platform coverage.
Author
Owner

@morluto commented on GitHub (Oct 15, 2026):

Fixed on main in 5fb6edc34. The resolver checks canonical bundle → programs directory → executable containment in order. An escaping programs-directory symlink is rejected before child lookup, including when the external executable is missing; aliases within the canonical bundle remain supported.

The real filesystem escape and missing-child regressions both failed before the fix. All 66 focused source/filesystem checks passed afterward, and exact integrated revision CI passed: https://github.com/morluto/rea/actions/runs/38785488273

<!-- gh-comment-id:6199521126 --> @morluto commented on GitHub (Oct 15, 2026): Fixed on main in 5fb6edc34. The resolver checks canonical bundle → programs directory → executable containment in order. An escaping programs-directory symlink is rejected before child lookup, including when the external executable is missing; aliases within the canonical bundle remain supported. The real filesystem escape and missing-child regressions both failed before the fix. All 66 focused source/filesystem checks passed afterward, and exact integrated revision CI passed: https://github.com/morluto/rea/actions/runs/38785488273
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#1637
No description provided.