Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,9 @@ jobs:
- name: Validate cmux profiling support scripts
run: ./tests/test_start_cmux_profiling.sh

- name: Validate bundled provider runtime guard
run: ./tests/test_bundled_provider_runtime_guard.sh

- name: Validate Xcode SourcePackages cache sanitizer
run: python3 tests/test_ci_sanitize_xcode_source_packages_cache.py

Expand Down Expand Up @@ -1379,6 +1382,8 @@ jobs:
- name: Validate Release artifact slices
run: |
set -euo pipefail
./scripts/verify-no-bundled-agent-runtimes.sh \
build-universal/Build/Products/Release/cmux.app
APP_BINARY="build-universal/Build/Products/Release/cmux.app/Contents/MacOS/cmux"
CLI_BINARY="build-universal/Build/Products/Release/cmux.app/Contents/Resources/bin/cmux"
HELPER_BINARY="build-universal/Build/Products/Release/cmux.app/Contents/Resources/bin/ghostty"
Expand Down
7 changes: 7 additions & 0 deletions .github/workflows/nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,13 @@ jobs:
CMUX_APP_PATH="build-universal/Build/Products/Release/cmux.app" \
./tests/test_bundled_ghostty_theme_picker_helper.sh

- name: Verify no bundled provider runtimes in nightly app
if: needs.decide.outputs.should_publish != 'true' || steps.current_head_prebuild.outputs.still_current == 'true'
run: |
set -euo pipefail
./scripts/verify-no-bundled-agent-runtimes.sh \
build-universal/Build/Products/Release/cmux.app
Comment thread
austinywang marked this conversation as resolved.

- name: Verify nightly binary architectures
if: needs.decide.outputs.should_publish != 'true' || steps.current_head_prebuild.outputs.still_current == 'true'
run: |
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,8 @@ jobs:
if: steps.guard_release_assets.outputs.skip_all != 'true'
run: |
set -euo pipefail
./scripts/verify-no-bundled-agent-runtimes.sh \
build-universal/Build/Products/Release/cmux.app
APP_BINARY="build-universal/Build/Products/Release/cmux.app/Contents/MacOS/cmux"
CLI_BINARY="build-universal/Build/Products/Release/cmux.app/Contents/Resources/bin/cmux"
HELPER_BINARY="build-universal/Build/Products/Release/cmux.app/Contents/Resources/bin/ghostty"
Expand Down
92 changes: 92 additions & 0 deletions scripts/verify-no-bundled-agent-runtimes.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
#!/usr/bin/env bash
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
EXPECTED_GROK_WRAPPER="$SCRIPT_DIR/../Resources/bin/grok"

usage() {
cat <<'EOF'
Usage: scripts/verify-no-bundled-agent-runtimes.sh <cmux.app>

Verifies that a built cmux app bundle does not ship provider executables or a
Bun standalone runtime in Contents/Resources/bin.
EOF
}

if [ "${1:-}" = "-h" ] || [ "${1:-}" = "--help" ]; then
usage
exit 0
fi

if [ "$#" -ne 1 ]; then
usage >&2
exit 2
fi

APP_PATH="$1"
BIN_DIR="$APP_PATH/Contents/Resources/bin"

if [ ! -d "$APP_PATH" ]; then
echo "error: app bundle not found at $APP_PATH" >&2
exit 1
fi

if [ ! -d "$BIN_DIR" ]; then
echo "error: bundled bin directory not found at $BIN_DIR" >&2
exit 1
fi

is_allowed_binary_name() {
case "$1" in
# `grok` is allowed only when it matches cmux's checked-in wrapper script;
# it still goes through the Bun-standalone signature scan below.
cmux|ghostty|cmux-claude-wrapper|cmux-codex-wrapper|grok|open|start-cmux-profiling|submit-cmux-profile)
return 0
;;
*)
return 1
;;
esac
}

looks_like_bun_standalone() {
local path="$1"
Comment thread
austinywang marked this conversation as resolved.
strings -a "$path" 2>/dev/null | grep -E '(/\$bunfs/|StandaloneExecutable|Bun v[0-9]+\.[0-9]+\.[0-9]+)' >/dev/null
}

is_checked_in_grok_wrapper() {
local path="$1"
[ -f "$EXPECTED_GROK_WRAPPER" ] && cmp -s "$EXPECTED_GROK_WRAPPER" "$path"
}

relative_to_app() {
local path="$1"
printf '%s\n' "${path#"$APP_PATH"/}"
}

violations=()

while IFS= read -r -d '' file; do
name="$(basename "$file")"
if ! is_allowed_binary_name "$name"; then
violations+=("unexpected bundled bin entry: $(relative_to_app "$file")")
continue
fi
if [ ! -x "$file" ] && [ ! -L "$file" ]; then
violations+=("allowed bin entry is not executable: $(relative_to_app "$file")")
fi
if [ "$name" = "grok" ] && ! is_checked_in_grok_wrapper "$file"; then
violations+=("grok wrapper does not match checked-in cmux wrapper: $(relative_to_app "$file")")
fi
if looks_like_bun_standalone "$file"; then
violations+=("Bun standalone runtime signature: $(relative_to_app "$file")")
fi
done < <(find "$BIN_DIR" \( -type f -o -type l \) -print0)
Comment thread
coderabbitai[bot] marked this conversation as resolved.

if [ "${#violations[@]}" -gt 0 ]; then
echo "error: cmux app bundle contains forbidden bundled provider runtimes:" >&2
printf ' %s\n' "${violations[@]}" >&2
exit 1
fi

echo "verified no bundled provider runtimes: $APP_PATH"
152 changes: 152 additions & 0 deletions tests/test_bundled_provider_runtime_guard.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
#!/usr/bin/env bash
set -euo pipefail

ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
VERIFY_SCRIPT="$ROOT_DIR/scripts/verify-no-bundled-agent-runtimes.sh"

if [ ! -x "$VERIFY_SCRIPT" ]; then
echo "FAIL: missing bundled provider runtime verifier at $VERIFY_SCRIPT" >&2
exit 1
fi

TMP_DIR="$(mktemp -d "${TMPDIR:-/tmp}/cmux-bundled-runtime-guard.XXXXXX")"
trap 'rm -rf "$TMP_DIR"' EXIT

make_app() {
local app_path="$1"
mkdir -p "$app_path/Contents/Resources/bin"
}

write_executable() {
local path="$1"
local body="$2"
mkdir -p "$(dirname "$path")"
printf '%s\n' "$body" > "$path"
chmod 0755 "$path"
}

copy_grok_wrapper() {
local path="$1"
mkdir -p "$(dirname "$path")"
install -m 0755 "$ROOT_DIR/Resources/bin/grok" "$path"
}

GOOD_APP="$TMP_DIR/good/cmux.app"
make_app "$GOOD_APP"
write_executable "$GOOD_APP/Contents/Resources/bin/cmux" "#!/bin/sh"
write_executable "$GOOD_APP/Contents/Resources/bin/ghostty" "#!/bin/sh"
write_executable "$GOOD_APP/Contents/Resources/bin/cmux-claude-wrapper" "#!/bin/sh"
write_executable "$GOOD_APP/Contents/Resources/bin/cmux-codex-wrapper" "#!/bin/sh"
copy_grok_wrapper "$GOOD_APP/Contents/Resources/bin/grok"
write_executable "$GOOD_APP/Contents/Resources/bin/open" "#!/bin/sh"
write_executable "$GOOD_APP/Contents/Resources/bin/start-cmux-profiling" "#!/bin/sh"
write_executable "$GOOD_APP/Contents/Resources/bin/submit-cmux-profile" "#!/bin/sh"
"$VERIFY_SCRIPT" "$GOOD_APP"
Comment thread
austinywang marked this conversation as resolved.
Comment thread
austinywang marked this conversation as resolved.

BAD_GROK_APP="$TMP_DIR/bad-grok-wrapper/cmux.app"
make_app "$BAD_GROK_APP"
write_executable "$BAD_GROK_APP/Contents/Resources/bin/cmux" "#!/bin/sh"
write_executable "$BAD_GROK_APP/Contents/Resources/bin/grok" "#!/bin/sh"
if "$VERIFY_SCRIPT" "$BAD_GROK_APP" >"$TMP_DIR/grok-wrapper.out" 2>&1; then
echo "FAIL: verifier allowed a non-cmux grok wrapper" >&2
exit 1
fi
if ! grep -Fq "grok wrapper does not match checked-in cmux wrapper: Contents/Resources/bin/grok" "$TMP_DIR/grok-wrapper.out"; then
echo "FAIL: grok wrapper rejection did not name the offending path" >&2
cat "$TMP_DIR/grok-wrapper.out" >&2
exit 1
fi

for forbidden in claude opencode codex pi bun bunx; do
BAD_APP="$TMP_DIR/bad-$forbidden/cmux.app"
make_app "$BAD_APP"
write_executable "$BAD_APP/Contents/Resources/bin/cmux" "#!/bin/sh"
write_executable "$BAD_APP/Contents/Resources/bin/$forbidden" "#!/bin/sh"
OUTPUT="$TMP_DIR/$forbidden.out"
if "$VERIFY_SCRIPT" "$BAD_APP" >"$OUTPUT" 2>&1; then
echo "FAIL: verifier allowed bundled $forbidden executable" >&2
exit 1
fi
if ! grep -Fq "Contents/Resources/bin/$forbidden" "$OUTPUT"; then
echo "FAIL: verifier rejection for $forbidden did not name the offending path" >&2
cat "$OUTPUT" >&2
exit 1
fi
done
Comment thread
austinywang marked this conversation as resolved.
Comment thread
austinywang marked this conversation as resolved.

NONEXEC_FORBIDDEN_APP="$TMP_DIR/bad-nonexec-bun/cmux.app"
make_app "$NONEXEC_FORBIDDEN_APP"
write_executable "$NONEXEC_FORBIDDEN_APP/Contents/Resources/bin/cmux" "#!/bin/sh"
printf '%s\n' 'Bun v1.3.14 StandaloneExecutable /$bunfs/root' > "$NONEXEC_FORBIDDEN_APP/Contents/Resources/bin/bun"
chmod 0644 "$NONEXEC_FORBIDDEN_APP/Contents/Resources/bin/bun"
if "$VERIFY_SCRIPT" "$NONEXEC_FORBIDDEN_APP" >"$TMP_DIR/nonexec-bun.out" 2>&1; then
echo "FAIL: verifier allowed a non-executable bundled bun file" >&2
exit 1
fi
if ! grep -Fq "unexpected bundled bin entry: Contents/Resources/bin/bun" "$TMP_DIR/nonexec-bun.out"; then
echo "FAIL: non-executable bun rejection did not name the offending path" >&2
cat "$TMP_DIR/nonexec-bun.out" >&2
exit 1
fi

NONEXEC_ALLOWED_APP="$TMP_DIR/bad-nonexec-allowed/cmux.app"
make_app "$NONEXEC_ALLOWED_APP"
printf '%s\n' '#!/bin/sh' > "$NONEXEC_ALLOWED_APP/Contents/Resources/bin/cmux"
chmod 0644 "$NONEXEC_ALLOWED_APP/Contents/Resources/bin/cmux"
if "$VERIFY_SCRIPT" "$NONEXEC_ALLOWED_APP" >"$TMP_DIR/nonexec-allowed.out" 2>&1; then
echo "FAIL: verifier allowed a non-executable allowlisted bin entry" >&2
exit 1
fi
if ! grep -Fq "allowed bin entry is not executable: Contents/Resources/bin/cmux" "$TMP_DIR/nonexec-allowed.out"; then
echo "FAIL: non-executable allowlisted rejection did not name the offending path" >&2
cat "$TMP_DIR/nonexec-allowed.out" >&2
exit 1
fi

SYMLINK_APP="$TMP_DIR/bad-symlink/cmux.app"
make_app "$SYMLINK_APP"
write_executable "$SYMLINK_APP/Contents/Resources/bin/cmux" "#!/bin/sh"
ln -s cmux "$SYMLINK_APP/Contents/Resources/bin/claude"
if "$VERIFY_SCRIPT" "$SYMLINK_APP" >"$TMP_DIR/symlink.out" 2>&1; then
echo "FAIL: verifier allowed a symlinked bundled claude executable" >&2
exit 1
fi
if ! grep -Fq "Contents/Resources/bin/claude" "$TMP_DIR/symlink.out"; then
echo "FAIL: symlink rejection did not name the offending path" >&2
cat "$TMP_DIR/symlink.out" >&2
exit 1
fi

BUN_SIGNATURE_APP="$TMP_DIR/bad-bun-signature/cmux.app"
make_app "$BUN_SIGNATURE_APP"
write_executable "$BUN_SIGNATURE_APP/Contents/Resources/bin/cmux" '#!/bin/sh
printf "%s\n" "Bun v1.3.14 StandaloneExecutable /$bunfs/root"'
if "$VERIFY_SCRIPT" "$BUN_SIGNATURE_APP" >"$TMP_DIR/bun-signature.out" 2>&1; then
echo "FAIL: verifier allowed an allowlisted executable with a Bun standalone signature" >&2
exit 1
fi
if ! grep -Fq "Bun standalone runtime signature: Contents/Resources/bin/cmux" "$TMP_DIR/bun-signature.out"; then
echo "FAIL: Bun signature rejection did not name the offending path" >&2
cat "$TMP_DIR/bun-signature.out" >&2
exit 1
fi

LARGE_BUN_SIGNATURE_APP="$TMP_DIR/bad-large-bun-signature/cmux.app"
make_app "$LARGE_BUN_SIGNATURE_APP"
{
printf '%s\n' '#!/bin/sh'
printf '%s\n' 'Bun v1.3.14 StandaloneExecutable /$bunfs/root'
awk 'BEGIN { for (i = 0; i < 5000; i++) print "cmux padding line after the early Bun signature" }'
} > "$LARGE_BUN_SIGNATURE_APP/Contents/Resources/bin/cmux"
chmod 0755 "$LARGE_BUN_SIGNATURE_APP/Contents/Resources/bin/cmux"
if "$VERIFY_SCRIPT" "$LARGE_BUN_SIGNATURE_APP" >"$TMP_DIR/large-bun-signature.out" 2>&1; then
echo "FAIL: verifier allowed a large allowlisted executable with an early Bun standalone signature" >&2
exit 1
fi
if ! grep -Fq "Bun standalone runtime signature: Contents/Resources/bin/cmux" "$TMP_DIR/large-bun-signature.out"; then
echo "FAIL: large Bun signature rejection did not name the offending path" >&2
cat "$TMP_DIR/large-bun-signature.out" >&2
exit 1
fi

echo "PASS: bundled provider runtime guard rejects stale provider and Bun executables"
Loading