Skip to content

Add Prisma Access Browser as a macOS FMA - #49641

Merged
allenhouchins merged 1 commit into
mainfrom
49328-new-fma-prisma-access-browser
Jul 21, 2026
Merged

Add Prisma Access Browser as a macOS FMA#49641
allenhouchins merged 1 commit into
mainfrom
49328-new-fma-prisma-access-browser

Conversation

@allenhouchins

@allenhouchins allenhouchins commented Jul 21, 2026

Copy link
Copy Markdown
Member

Related issue: Resolves #49328

Adds Prisma Access Browser (Homebrew cask prisma-access-browser) as a macOS Fleet-maintained app.

Notes for reviewers

Naming/slug: This product already ships as a Windows FMA named Prisma Browser (prisma-browser/windows, added in #46409 — "Prisma Browser" is Palo Alto's current branding for what was Prisma Access Browser). The FMA library combines platforms into one row by slug token, so this entry uses name: "Prisma Browser" and slug: "prisma-browser/darwin" while keeping token: "prisma-access-browser" for the cask lookup (same token≠slug pattern as the libreoffice, ollama, and zoom inputs). Both platforms now show as a single "Prisma Browser" library row and share the existing Prisma icon, so no icon changes are needed.

Identity verified against the real installer (downloaded the full pkg; SHA256 matches the cask):

  • CFBundleIdentifier = com.talon-sec.Work (from the pkg Distribution/PackageInfo), so the exists query matches osquery's apps table.
  • CFBundleShortVersionString = 150.49.5.129, exactly the cask version before the comma, so the patched query's bundle_short_version comparison reconciles.
  • Installs Prisma Access Browser.app machine-wide to /Applications as root; requires macOS 12+. Not a bootstrapper.

Install script filename: the cask's pkg artifact (PrismaBrowser-universal.pkg) doesn't match the URL basename, but the vendor serves that name via Content-Disposition, which is what the server stores (FilenameFromResponse) and what orbit names the downloaded installer on both download paths — so the auto-generated install script works without a custom script.

Installer URL is pinned (version + hash in the path), so no latest-redirect SHA-drift risk.

Checklist for submitter

Testing

  • Verified installer identity fields (bundle ID, versions, SHA256) against the real pkg
  • go test ./ee/maintained-apps/... ./cmd/maintained-apps/... passes; generated JSON validated
  • FMA validator run (CI) confirms install/uninstall on a macOS host

Summary by CodeRabbit

  • New Features
    • Added support for Prisma Browser on macOS.
    • Added Prisma Browser version 150.49.5.129 with automated installation, version detection, pinning, and uninstallation.
    • Added safeguards to preserve the app’s running state during installation and restore it afterward.

Ships the Homebrew cask prisma-access-browser as a Fleet-maintained app.
Named "Prisma Browser" with slug prisma-browser/darwin so it combines
with the existing prisma-browser/windows FMA into a single library row
(the UI groups platforms by slug token) and reuses the existing icon.
Copilot AI review requested due to automatic review settings July 21, 2026 12:56
@allenhouchins allenhouchins linked an issue Jul 21, 2026 that may be closed by this pull request

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds Prisma Access Browser as a macOS Fleet-maintained app entry, surfaced in the maintained-apps catalog as “Prisma Browser” (to align with the existing Windows FMA branding/slug grouping).

Changes:

  • Added a new macOS Homebrew input (token: prisma-access-browser) under the shared Prisma Browser slug (prisma-browser/darwin).
  • Generated the macOS maintained-app output manifest/scripts for Prisma Browser.
  • Added the macOS Prisma Browser entry to the aggregated apps catalog output.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
ee/maintained-apps/outputs/prisma-browser/darwin.json New generated macOS FMA manifest (queries, installer URL, install/uninstall scripts).
ee/maintained-apps/outputs/apps.json Adds the darwin Prisma Browser entry to the maintained-apps catalog output.
ee/maintained-apps/inputs/homebrew/prisma-browser.json New Homebrew input mapping slug/name to the prisma-access-browser cask token.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

],
"refs": {
"7381dac4": "#!/bin/bash\n\n# variables\nAPPDIR=\"/Applications/\"\nTMPDIR=$(dirname \"$(realpath \"$INSTALLER_PATH\")\")\n# functions\n\nquit_and_track_application() {\n local bundle_id=\"$1\"\n local var_name=\"APP_WAS_RUNNING_$(echo \"$bundle_id\" | tr '.-' '__')\"\n local timeout_duration=10\n\n # check if the application is running\n local app_running\n app_running=$(osascript -e \"application id \\\"$bundle_id\\\" is running\" 2>/dev/null)\n if [[ \"$app_running\" != \"true\" ]]; then\n eval \"export $var_name=0\"\n return\n fi\n\n local console_user\n console_user=$(stat -f \"%Su\" /dev/console)\n if [[ -z \"$console_user\" || \"$console_user\" == \"root\" || \"$console_user\" == \"loginwindow\" ]]; then\n echo \"Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'.\"\n eval \"export $var_name=0\"\n return\n fi\n\n # App was running, mark it for relaunch\n eval \"export $var_name=1\"\n echo \"Application '$bundle_id' was running; will relaunch after installation.\"\n\n echo \"Quitting application '$bundle_id'...\"\n\n # try to quit the application within the timeout period\n local quit_success=false\n SECONDS=0\n while (( SECONDS < timeout_duration )); do\n if osascript -e \"tell application id \\\"$bundle_id\\\" to quit\" >/dev/null 2>&1; then\n if ! pgrep -f \"$bundle_id\" >/dev/null 2>&1; then\n echo \"Application '$bundle_id' quit successfully.\"\n quit_success=true\n break\n fi\n fi\n sleep 1\n done\n\n if [[ \"$quit_success\" = false ]]; then\n echo \"Application '$bundle_id' did not quit.\"\n fi\n}\n\n\nrelaunch_application() {\n local bundle_id=\"$1\"\n local var_name=\"APP_WAS_RUNNING_$(echo \"$bundle_id\" | tr '.-' '__')\"\n local was_running\n\n # Check if the app was running before installation\n eval \"was_running=\\$$var_name\"\n if [[ \"$was_running\" != \"1\" ]]; then\n return\n fi\n\n local console_user\n console_user=$(stat -f \"%Su\" /dev/console)\n if [[ -z \"$console_user\" || \"$console_user\" == \"root\" || \"$console_user\" == \"loginwindow\" ]]; then\n echo \"Not logged into a non-root GUI; skipping relaunching application ID '$bundle_id'.\"\n return\n fi\n\n echo \"Relaunching application '$bundle_id'...\"\n\n # Launch the app in the logged-in user's GUI session. Apps launched by root\n # won't register with the user's Dock/GUI, so run 'open' as the console user.\n # Use 'launchctl asuser' to bootstrap into the console user's Mach namespace\n # and GUI session — 'sudo -u' alone doesn't do this, which can cause\n # LSOpenURLsWithRole() failures even when 'open' exits 0.\n local open_status=0\n if [[ $EUID -eq 0 ]]; then\n local console_uid\n console_uid=$(id -u \"$console_user\")\n /bin/launchctl asuser \"$console_uid\" sudo -u \"$console_user\" open -b \"$bundle_id\" >/dev/null 2>&1 || open_status=$?\n else\n open -b \"$bundle_id\" >/dev/null 2>&1 || open_status=$?\n fi\n\n if [[ $open_status -eq 0 ]]; then\n echo \"Application '$bundle_id' relaunched successfully.\"\n else\n echo \"Failed to relaunch application '$bundle_id'.\"\n fi\n}\n\n\n# install pkg files\nquit_and_track_application 'com.talon-sec.Work'\nsudo installer -pkg \"$TMPDIR/PrismaBrowser-universal.pkg\" -target /\nrelaunch_application 'com.talon-sec.Work'\n",
"ff947f47": "#!/bin/bash\n\n# variables\nLOGGED_IN_USER=$(scutil <<< \"show State:/Users/ConsoleUser\" | awk '/Name :/ { print $3 }')\n# functions\n\nexpand_pkgid_and_map() {\n local PKGID=\"$1\"\n local FUNC=\"$2\"\n if [[ \"$PKGID\" == *\"*\" ]]; then\n local prefix=\"${PKGID%\\*}\"\n echo \"Expanding wildcard for PKGID: $PKGID\"\n for receipt in $(pkgutil --pkgs | grep \"^${prefix}\"); do\n echo \"Processing $receipt\"\n \"$FUNC\" \"$receipt\"\n done\n else\n \"$FUNC\" \"$PKGID\"\n fi\n}\n\nforget_pkg() {\n local PKGID=\"$1\"\n expand_pkgid_and_map \"$PKGID\" forget_receipt\n}\n\nforget_receipt() {\n local PKGID=\"$1\"\n sudo pkgutil --forget \"$PKGID\"\n}\n\nquit_application() {\n local bundle_id=\"$1\"\n local timeout_duration=10\n\n # check if the application is running\n local app_running\n app_running=$(osascript -e \"application id \\\"$bundle_id\\\" is running\" 2>/dev/null)\n if [[ \"$app_running\" != \"true\" ]]; then\n return\n fi\n\n local console_user\n console_user=$(stat -f \"%Su\" /dev/console)\n if [[ -z \"$console_user\" || \"$console_user\" == \"root\" || \"$console_user\" == \"loginwindow\" ]]; then\n echo \"Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'.\"\n return\n fi\n\n echo \"Quitting application '$bundle_id'...\"\n\n # try to quit the application within the timeout period\n local quit_success=false\n SECONDS=0\n while (( SECONDS < timeout_duration )); do\n if osascript -e \"tell application id \\\"$bundle_id\\\" to quit\" >/dev/null 2>&1; then\n if ! pgrep -f \"$bundle_id\" >/dev/null 2>&1; then\n echo \"Application '$bundle_id' quit successfully.\"\n quit_success=true\n break\n fi\n fi\n sleep 1\n done\n\n if [[ \"$quit_success\" = false ]]; then\n echo \"Application '$bundle_id' did not quit.\"\n fi\n}\n\n\nremove_launchctl_service() {\n local service=\"$1\"\n local booleans=(\"true\" \"false\")\n local plist_status\n local paths\n local should_sudo\n\n echo \"Removing launchctl service ${service}\"\n\n # A wildcard label can't be used with launchctl or as a plist name, so expand\n # it to the labels of currently loaded services that match the pattern.\n local services=(\"$service\")\n if [[ \"$service\" == *\"*\"* ]]; then\n local regex\n # Escape regex metacharacters, turn '*' into '.*', and anchor the pattern so\n # it matches a full label rather than a substring.\n regex=$(printf '%s' \"$service\" | sed -e 's/[][(){}.^$+?|\\\\]/\\\\&/g' -e 's/\\*/.*/g')\n regex=\"^${regex}$\"\n services=()\n local id\n # Match every loaded job by label regardless of PID; launchctl list reports\n # loaded-but-not-running jobs with a \"-\" in the PID column.\n while read -r _ _ id; do\n [[ \"$id\" =~ $regex ]] && services+=(\"$id\")\n done < <(launchctl list 2>/dev/null | tail -n +2)\n if [[ ${#services[@]} -eq 0 ]]; then\n echo \"No loaded launchctl service matches ${service}\"\n return\n fi\n fi\n\n local service_label\n for service_label in \"${services[@]}\"; do\n for should_sudo in \"${booleans[@]}\"; do\n plist_status=$(launchctl list \"${service_label}\" 2>/dev/null)\n\n if [[ $plist_status == \\{* ]]; then\n if [[ $should_sudo == \"true\" ]]; then\n sudo launchctl remove \"${service_label}\"\n else\n launchctl remove \"${service_label}\"\n fi\n sleep 1\n fi\n\n paths=(\n \"/Library/LaunchAgents/${service_label}.plist\"\n \"/Library/LaunchDaemons/${service_label}.plist\"\n )\n\n # if not using sudo, prepend the home directory to the paths\n if [[ $should_sudo == \"false\" ]]; then\n for i in \"${!paths[@]}\"; do\n paths[i]=\"${HOME}${paths[i]}\"\n done\n fi\n\n for path in \"${paths[@]}\"; do\n if [[ -e \"$path\" ]]; then\n if [[ $should_sudo == \"true\" ]]; then\n sudo rm -f -- \"$path\"\n else\n rm -f -- \"$path\"\n fi\n fi\n done\n done\n done\n}\n\nremove_pkg_files() {\n local PKGID=\"$1\"\n expand_pkgid_and_map \"$PKGID\" remove_receipt_files\n}\n\nremove_receipt_files() {\n local PKGID=\"$1\"\n local PKGINFO VOLUME INSTALL_LOCATION FULL_INSTALL_LOCATION\n\n echo \"pkgutil --pkg-info-plist \\\"$PKGID\\\"\"\n PKGINFO=$(pkgutil --pkg-info-plist \"$PKGID\")\n VOLUME=$(echo \"$PKGINFO\" | awk '/<key>volume<\\/key>/ {getline; gsub(/.*<string>|<\\/string>.*/, \"\"); print}')\n INSTALL_LOCATION=$(echo \"$PKGINFO\" | awk '/<key>install-location<\\/key>/ {getline; gsub(/.*<string>|<\\/string>.*/, \"\"); print}')\n\n if [ -z \"$INSTALL_LOCATION\" ] || [ \"$INSTALL_LOCATION\" = \"/\" ]; then\n FULL_INSTALL_LOCATION=\"$VOLUME\"\n else\n FULL_INSTALL_LOCATION=\"$VOLUME/$INSTALL_LOCATION\"\n FULL_INSTALL_LOCATION=$(echo \"$FULL_INSTALL_LOCATION\" | sed 's|//|/|g')\n fi\n\n echo \"sudo pkgutil --only-files --files \\\"$PKGID\\\" | sed \\\"s|^|${FULL_INSTALL_LOCATION}/|\\\" | tr '\\\\\\\\n' '\\\\\\\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\"\n sudo pkgutil --only-files --files \"$PKGID\" | sed \"s|^|/${INSTALL_LOCATION}/|\" | tr '\\n' '\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\n\n echo \"sudo pkgutil --only-dirs --files \\\"$PKGID\\\" | sed \\\"s|^|${FULL_INSTALL_LOCATION}/|\\\" | grep '\\\\.app$' | tr '\\\\\\\\n' '\\\\\\\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\"\n sudo pkgutil --only-dirs --files \"$PKGID\" | sed \"s|^|${FULL_INSTALL_LOCATION}/|\" | grep '\\.app$' | tr '\\n' '\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\n\n root_app_dir=$(\n sudo pkgutil --only-dirs --files \"$PKGID\" \\\n | sed \"s|^|${FULL_INSTALL_LOCATION}/|\" \\\n | grep 'Applications' \\\n | awk '{ print length, $0 }' \\\n | sort -n \\\n | head -n1 \\\n | cut -d' ' -f2-\n )\n if [ -n \"$root_app_dir\" ]; then\n echo \"sudo rmdir -p \\\"$root_app_dir\\\" 2>/dev/null || :\"\n sudo rmdir -p \"$root_app_dir\" 2>/dev/null || :\n fi\n}\n\ntrash() {\n local logged_in_user=\"$1\"\n local target_file=\"$2\"\n local timestamp=\"$(date +%Y-%m-%d-%s)\"\n local rand=\"$(jot -r 1 0 99999)\"\n\n # replace ~ with /Users/$logged_in_user\n if [[ \"$target_file\" == ~* ]]; then\n target_file=\"/Users/$logged_in_user${target_file:1}\"\n fi\n\n local trash=\"/Users/$logged_in_user/.Trash\"\n\n # If the target contains glob characters, expand it and move each match.\n if [[ \"$target_file\" == *[*?[]* ]]; then\n local file file_name\n local matched=false\n local i=0\n # compgen -G expands the (quoted) pattern itself, so paths containing\n # spaces glob correctly; reading line by line keeps each match intact.\n while IFS= read -r file; do\n [[ -n \"$file\" ]] || continue\n [[ -e \"$file\" || -L \"$file\" ]] || continue\n matched=true\n i=$((i + 1))\n file_name=\"$(basename \"$file\")\"\n echo \"removing $file.\"\n # The per-match counter keeps matches that share a basename from\n # overwriting each other in the trash.\n mv -f \"$file\" \"$trash/${file_name}_${timestamp}_${rand}_${i}\"\n done < <(compgen -G \"$target_file\" 2>/dev/null)\n if [[ \"$matched\" == false ]]; then\n echo \"$target_file doesn't exist.\"\n fi\n return\n fi\n\n local file_name=\"$(basename \"${target_file}\")\"\n\n if [[ -e \"$target_file\" ]]; then\n echo \"removing $target_file.\"\n mv -f \"$target_file\" \"$trash/${file_name}_${timestamp}_${rand}\"\n else\n echo \"$target_file doesn't exist.\"\n fi\n}\n\nremove_launchctl_service 'com.paloaltonetworks.prismaaccessbrowserupdater.agent'\nremove_launchctl_service 'com.paloaltonetworks.prismaaccessbrowserupdater.daemon'\nremove_launchctl_service 'com.paloaltonetworks.PrismaAccessBrowserUpdater.wake.system'\nremove_launchctl_service 'com.paloaltonetworks.prismaaccessbrowserupdater.xpcservice'\nquit_application 'com.talon-sec.Work'\nremove_pkg_files 'com.talon-sec.Work'\nforget_pkg 'com.talon-sec.Work'\nsudo rm -rf '/Library/Application Support/PAB'\nsudo rm -rf '/Library/PAB'\ntrash $LOGGED_IN_USER '~/Library/Application Support/PAB'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.paloaltonetworks.PrismaAccessBrowserUpdater'\ntrash $LOGGED_IN_USER '~/Library/Caches/PAB'\ntrash $LOGGED_IN_USER '~/Library/HTTPStorages/com.paloaltonetworks.PrismaAccessBrowserUpdater'\ntrash $LOGGED_IN_USER '~/Library/PAB'\n"
@allenhouchins
allenhouchins marked this pull request as ready for review July 21, 2026 14:23
@allenhouchins
allenhouchins merged commit ebda171 into main Jul 21, 2026
19 checks passed
@allenhouchins
allenhouchins deleted the 49328-new-fma-prisma-access-browser branch July 21, 2026 14:23
@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 435977af-efa0-4095-ba47-88fbdc397c3f

📥 Commits

Reviewing files that changed from the base of the PR and between bd8f1e8 and f6e291d.

📒 Files selected for processing (3)
  • ee/maintained-apps/inputs/homebrew/prisma-browser.json
  • ee/maintained-apps/outputs/apps.json
  • ee/maintained-apps/outputs/prisma-browser/darwin.json

Walkthrough

Adds Prisma Browser as a macOS maintained app with Homebrew input metadata and a darwin catalog entry. Adds version 150.49.5.129 configuration with SQL detection, package URL, checksum, categories, and installer references. Embedded scripts handle quitting and relaunching the app during installation and remove package receipts, launchd services, support directories, caches, storage, and related user files during uninstallation.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 49328-new-fma-prisma-access-browser

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

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.

New FMA: Prisma Access Browser

3 participants