Skip to content

Instantly share code, notes, and snippets.

@cielavenir
Last active March 20, 2024 20:18
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save cielavenir/02f322e322a2a3555dbf2b38f2fedd59 to your computer and use it in GitHub Desktop.
Save cielavenir/02f322e322a2a3555dbf2b38f2fedd59 to your computer and use it in GitHub Desktop.
zoom sandbox-exec for macOS
  1. Download Zoom.pkg from https://zoom.us/download
  2. Extract it using https://www.timdoug.com/unpkg/
  3. Now you have Zoom/zoom.us.app
  4. Launch Zoom by zoom.sh Zoom/zoom.us.app/Contents/MacOS/zoom.us

caveats:

  • Zoom will fail to start meeting for the first time. Just launch again.
  • Zoom will tell that crash happened, but you should ignore it.
#!/bin/sh
# Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
# DISCLAIMER: THE WORKS ARE WITHOUT WARRANTY.
sandbox-exec -p "
(version 1)
(allow default)
(deny file-write*)
(allow file-write*
(subpath \"${HOME}/Library/Application Support/zoom.us\")
(subpath \"${HOME}/Library/Logs/zoom.us\")
(subpath \"${HOME}/Library/WebKit/us.zoom.xos\")
(subpath \"${HOME}/Library/Caches/us.zoom.xos\")
(subpath \"${HOME}/Library/Saved Application State/us.zoom.xos.savedState\")
(subpath \"/private/var/tmp\")
(subpath \"/private/tmp\")
(subpath \"/tmp\")
(subpath \"/private/var/folders\")
(subpath \"/var/folders\")
)
(deny file* (regex #\"/id_rsa$\"))
(deny file* (regex #\"/id_dsa$\"))
(deny file* (regex #\"/id_ecdsa$\"))
(deny file* (regex #\"/id_ed25519$\"))
(deny file* (regex #\"\\.pem$\"))
" "$@"
@inklesspen
Copy link

This works great for me, but with one small problem: Zoom forgets my login information every time. Do you know what path I need to allow to let me stay logged into the sandboxed zoom app?

@msanders
Copy link

msanders commented May 20, 2023

I updated this script to automatically apply the steps in the README and add additional sandbox restrictions:

#!/bin/sh
# Launch Zoom in a sandbox. See:
# https://gist.github.com/cielavenir/02f322e322a2a3555dbf2b38f2fedd59?permalink_comment_id=4573877#gistcomment-4573877
set -o errexit -o nounset

APP_NAME="zoom.us.app"
CACHE_DIR="${XDG_CACHE_HOME:-$HOME/.local/share}/zoom-sandbox"
FORMULA_URL="https://raw.githubusercontent.com/Homebrew/homebrew-cask/HEAD/Casks/z/zoom-for-it-admins.rb"
SCRIPT_NAME="$(basename "$0")"
TMP_SCRIPT_DIR="$(mktemp -d -t zoom-sandbox-script)"
VERBOSE=false

APP_INSTALL_DIR="$CACHE_DIR"
PKG_NAME="ZoomInstallerIT.pkg"
PKG_CACHE_PATH="$CACHE_DIR/$PKG_NAME"
PKG_EXTRACT_DIR="$TMP_SCRIPT_DIR/pkg"
PKG_TMP_DOWNLOAD_PATH="$TMP_SCRIPT_DIR/$PKG_NAME"

ZM_APP_SUPPORT_DIR="$HOME/Library/Application Support/zoom.us"
ZM_CACHE_DIR="$HOME/Library/Caches/us.zoom.xos"
ZM_IT_CONFIG_PATH="/Library/Preferences/us.zoom.config.plist"
ZM_LOG_DIR="$HOME/Library/Logs/zoom.us"
ZM_STATE_DIR="$HOME/Library/Saved Application State/us.zoom.xos.savedState"
ZM_WEBKIT_DIR="$HOME/Library/WebKit/us.zoom.xos"

show_usage() {
    cat <<USAGE
Usage: $SCRIPT_NAME [-hv]

Download and run Zoom within a sandbox on macOS.
USAGE
}

cleanup() (
    nohup rm -rf "$TMP_SCRIPT_DIR" "$APP_INSTALL_DIR/${APP_NAME:?}" \
        "$ZM_APP_SUPPORT_DIR" "$ZM_CACHE_DIR" "$ZM_LOG_DIR" "$ZM_STATE_DIR" \
        "$ZM_WEBKIT_DIR" \
        "$HOME/Library/Group Containers/"*.ZoomClient3rd >/dev/null 2>&1
)

update() (
    formula="$(curl -s "$FORMULA_URL")"
    latest_version="$(printf "%s\n" "$formula" |
        sed -En 's/^[[:space:]]*version[[:space:]]+"([0-9.]+)".*$/\1/p')"
    update_sha="$(printf "%s\n" "$formula" |
        sed -En 's/^[[:space:]]*sha256[[:space:]]+"([0-9a-z]+)".*$/\1/p')"
    if tar -tf "$PKG_CACHE_PATH" >/dev/null 2>&1; then
        # From https://apple.stackexchange.com/a/376301
        local_version="$(tar xOvf "$PKG_CACHE_PATH" "*.pkg/PackageInfo" 2>/dev/null |
            grep -F "<pkg-info" | sed "s#.* version=\"\([0-9.]\{1,\}\)\".*#\1#")"
    else
        local_version="0"
    fi

    # From https://unix.stackexchange.com/a/632349
    if ! {
        printf "%s\n" "$latest_version"
        printf "%s\n" "$local_version"
    } | sort --version-sort --check=silent; then
        echo "Downloading Zoom..."
        curl --location --progress-bar \
            "https://cdn.zoom.us/prod/$latest_version/ZoomInstallerIT.pkg" \
            --output "$PKG_TMP_DOWNLOAD_PATH"
        echo "Done."

        if ! printf "%s %s\n" "$update_sha" "$PKG_TMP_DOWNLOAD_PATH" |
            sha256sum --check --strict --quiet 2>/dev/null; then
            printf >&2 "Error: SHA256 mismatch\n"
            printf >&2 "Expected: %s\n" "$update_sha"
            printf >&2 "Actual: %s\n" "$(sha256sum "$PKG_CACHE_PATH" | cut -f 1 -d " ")"
            printf >&2 "Archive: %s\n" "$PKG_CACHE_PATH"
            exit 1
        fi

        mkdir -p "$CACHE_DIR"
        mv "$PKG_TMP_DOWNLOAD_PATH" "$CACHE_DIR"
    fi
)

plist_key_equal() (
    key="$1"
    expected_value="$2"
    plist="$3"

    set +o errexit
    value="$(/usr/libexec/PlistBuddy -c "Print :$key" "$plist" 2>/dev/null)"
    set -o errexit
    status=$?

    [ $status -eq 0 ] && [ "$value" = "$expected_value" ]
)

run_cmd_silent() {
    if "$VERBOSE"; then
        "$@"
    else
        "$@" >/dev/null 2>&1
    fi
}

run() (
    # See https://support.zoom.us/hc/en-us/articles/115001799006
    if ! plist_key_equal "AU2_EnableAutoUpdate" "false" "$ZM_IT_CONFIG_PATH" ||
        ! plist_key_equal "AU2_EnableUpdateAvailableBanner" "false" "$ZM_IT_CONFIG_PATH"; then
        printf "Updating %s..." "$ZM_IT_CONFIG_PATH"
        sudo /usr/libexec/PlistBuddy -c "Delete :AU2_EnableAutoUpdate" "$ZM_IT_CONFIG_PATH" >/dev/null 2>&1 || :
        sudo /usr/libexec/PlistBuddy -c "Delete :AU2_EnableUpdateAvailableBanner" "$ZM_IT_CONFIG_PATH" >/dev/null 2>&1 || :
        sudo /usr/libexec/PlistBuddy -c "Add :AU2_EnableAutoUpdate bool false" "$ZM_IT_CONFIG_PATH" >/dev/null
        sudo /usr/libexec/PlistBuddy -c "Add :AU2_EnableUpdateAvailableBanner bool false" "$ZM_IT_CONFIG_PATH" >/dev/null
        sudo chmod a+r "$ZM_IT_CONFIG_PATH"
        echo "Done."
    fi

    # Extract the app as-needed to prevent accidental launches.
    pkgutil --expand-full "$PKG_CACHE_PATH" "$PKG_EXTRACT_DIR"

    # The app must be launched outside of a temporary directory to enable URL
    # handling and notifications.
    mkdir -p "$APP_INSTALL_DIR"
    rm -rf "$APP_INSTALL_DIR/${APP_NAME:?}"
    mv "$PKG_EXTRACT_DIR/zoomus.pkg/Payload/$APP_NAME" "$APP_INSTALL_DIR/"

    # To debug, run the following before starting:
    #     log stream --style syslog | grep -F "Sandbox: zoom.us"
    run_cmd_silent sandbox-exec -p "
(version 1)
(allow default)
(deny file-read*)
(deny file-write*)
(import \"system.sb\")
(system-network)
(allow file-read-metadata)
(allow process-exec (subpath \"$APP_INSTALL_DIR/$APP_NAME\"))
(allow process-info-pidinfo)
(allow user-preference-read)
(allow user-preference-write (preference-domain \"us.zoom.xos\"))

(allow file-read-data
    (literal \"$HOME/Library/Preferences/com.apple.LaunchServices/com.apple.LaunchServices.plist\")
    (literal \"$HOME/Library/Preferences/com.apple.security.plist\")
    (literal \"$ZM_IT_CONFIG_PATH\")
    (literal \"/Library/Preferences/com.apple.ViewBridge.plist\"))

(allow file-read*
    (subpath \"$APP_INSTALL_DIR/$APP_NAME\")
    (subpath \"$HOME/Library/Input Methods\")
    (subpath \"$HOME/Library/Keyboard Layouts\")
    (subpath \"$HOME/Library/Spelling\")
    (subpath \"/Library/Audio/Plug-Ins/HAL\")
    (subpath \"/Library/CoreMediaIO/Plug-Ins/DAL\"))

(allow file-write*
    (subpath \"$HOME/Downloads\"))

(allow file-read* file-write*
    (subpath \"$ZM_APP_SUPPORT_DIR\")
    (subpath \"$ZM_CACHE_DIR\")
    (subpath \"$ZM_LOG_DIR\")
    (subpath \"$ZM_STATE_DIR\")
    (subpath \"$ZM_WEBKIT_DIR\")
    (regex (string-append
            \"^\"
            (regex-quote \"$HOME\")
            \"/Library/Group Containers/.*\\.ZoomClient3rd\/\"))
    (subpath \"/private/var/tmp\")
    (subpath \"/private/tmp\")
    (subpath \"/tmp\")
    (subpath \"/private/var/folders\")
    (subpath \"/var/folders\"))
    " "$APP_INSTALL_DIR/$APP_NAME/Contents/MacOS/zoom.us"
)

main() (
    while getopts "hv" option; do
        case $option in
            v)
                VERBOSE=true
                ;;
            h)
                show_usage
                exit
                ;;
            *)
                show_usage
                exit 1
                ;;
        esac
    done

    update
    run
)

trap 'cleanup &' EXIT HUP INT QUIT TERM
main "$@"

Installation

  1. Copy the above script to your preferred bin directory path:
# Downloads script to ~/bin/zoom. Can also be done manually.
mkdir -p ~/bin
curl --proto "=https" --tlsv1.2 -sSf "https://api.github.com/gists/02f322e322a2a3555dbf2b38f2fedd59/comments/4573877" | /usr/bin/python3 -c "import sys, json; print(json.load(sys.stdin)['body'])" | sed -n '/^<!--I-->/,/^<!--\/I-->/p' | sed '1,2d;$d;s/\r$//' | sed '$d' > ~/bin/zoom
  1. Update permissions to be user executable.
chmod u+x ~/bin/zoom
  1. Confirm it runs.
$ ~/bin/zoom -h
Usage: zoom [-hv]

Download and run Zoom within a sandbox on macOS.

Changelog

2024-03-20

  • Fixed bug with version check.

2024-02-14

  • Added cleanup hook for additional exit signals.

2023-11-13

  • Added permission to write files to ~/Downloads.

2023-10-04

  • Added verbose flag (-v).

2023-09-25

  • Now downloads IT version of Zoom to disable built-in auto updates and the associated daemon.
  • Removed various application support and cache directories after quitting. The only files persisted between launches are ~/Library/Preferences/us.zoom.xos.plist and /Library/Preferences/us.zoom.config.plist.
  • Added check for SHA after downloading.
  • Pared down sandbox permissions. Zoom no longer has full disk read permissions enabled by default (previously only write was disallowed).

License

These changes are made available under the terms of the original script and the MIT license. For a copy, see https://opensource.org/licenses/MIT.

@evb-gh
Copy link

evb-gh commented Jun 26, 2023

The updated script works flawlessly. Thank you Michael; you are a wizard ;)

@darcyforster
Copy link

Hi, can you please tell me which folder the zoom app is downloaded into? after running the script I cannot seem to find the application anywhere
thanks

@msanders
Copy link

msanders commented Oct 4, 2023

@darcyforster The Zoom application bundle is not persisted after running the script. It is temporarily extracted to $HOME/.local/share/zoom-sandbox (or $XDG_CACHE_HOME/zoom-sandbox) when run, but removed after exit to avoid unintentionally launching outside of sandbox-exec. It is possible to write an application wrapper that does the same thing and allows launching from Finder, but would require additional maintenance. Users of the sandbox script can (and should) remove the Zoom app from /Applications/ if previously installed. Not sure if you were troubleshooting an issue with the app failing to launch, but I've added an additional flag to help diagnose:

/path/to/bin/zoom -v

Note that if Zoom had an application available on the Mac App Store this would not be necessary, since that already requires sandboxing. I strongly recommend encouraging organizations to seek alternatives such as Webex or one of the FOSS offerings available that don't have such absymal track records for security and privacy. Other options for users aside from the script include the web client and iOS apps. Unfortunately, at the time of this writing the web client has buggy behavior with camera orientation.

To completely remove previous installations of Zoom, you can use this script or run:

brew rm --cask --zap --force zoom

Note that this will delete preferences as well.

@cielavenir
Copy link
Author

(added license lines)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment