Developer workstations rot. Packages drift out of date, nobody checks for CVEs until something breaks.
Step 1: Audit Your Taps
Taps go stale. Projects shut down, formulae move to homebrew-core, and you're left with dead weight. Run brew tap and check:
| Tap | Why it's dead | What to do |
|---|---|---|
hashicorp/tap |
All tools are in homebrew-core now. Known "conflicts with itself" bug (Homebrew/brew#22764). | Migrate and untap |
weaveworks/tap |
Company shut down (2024). Abandoned formula_renames.json. |
Untap |
derailed/k9s |
k9s is in homebrew-core | Untap |
To migrate off a tap:
brew uninstall terraform vault packer
brew install terraform vault packer
brew untap hashicorp/tap
If brew info <package> shows it's in homebrew/core, the tap is redundant.
Homebrew 4.x+ also has tap trust. If you see Warning: Skipping <tap> because it is not trusted, either trust it (brew trust <tap>) or remove it. Don't leave dead taps dangling.
Step 2: Vulnerability Scanning
The wrong approach: pointing generic SCA tools at /opt/homebrew/Cellar. Homebrew packages don't leave lockfiles or standard metadata, so osv-scanner and trivy won't find anything useful.
The right approach: brew-vulns, Homebrew's first-party vulnerability scanner (released January 2026):
gem install brew-vulns
brew vulns # scan all installed packages
brew vulns --severity high # filter by severity
brew vulns --cyclonedx > sbom.cdx.json # generate SBOM with vuln data
brew vulns --sarif > results.sarif # for GitHub code scanning
It queries OSV.dev against each formula's source repository and version. Actual CVE data, not guesswork.
For a pre-upgrade gate, brew safe-upgrade checks OSV + GitHub Advisory + NIST NVD before touching your system:
brew tap sharkyger/homebrew-safe-upgrade
brew safe-upgrade # blocks if target version has known CVEs
Step 3: Triage What You Find
brew vulns tells you what's vulnerable. It doesn't tell you whether a fix exists, whether you actually need the package, or what to do when brew upgrade won't help.
Why do you have it
Most vulnerable packages are transitive dependencies. Find out what pulls them in:
brew uses --installed <package>
If nothing depends on it, you installed it directly. If you don't use it, brew uninstall <package> and move on.
Is there an upstream fix
Query the OSV API:
curl -s https://api.osv.dev/v1/vulns/OSV-XXXX-NNN | jq '.affected[].ranges[].events'
You're looking for a "fixed" event:
| Outcome | What it means | What to do |
|---|---|---|
No fixed event |
No upstream fix exists | Wait. Optionally file an issue upstream. |
fixed commit exists, no release |
Fix is on main but not tagged |
Request a release upstream, or brew install --HEAD <package>
|
fixed in a release you already have |
OSV record is stale | Nothing, you're already patched |
Would brew upgrade help
brew info --json=v2 <package> | jq '.formulae[].versions.stable'
If you're already on the latest stable and the CVE is still open, brew upgrade does nothing.
When the fix is on main but not released
Some projects merge security fixes months before cutting a release. Homebrew can only ship what's tagged.
Install from HEAD (builds from source, includes the fix). Not all formulae support this. Check with brew info <package> and look for "HEAD" in the output:
brew uninstall --ignore-dependencies <package>
brew install --HEAD <package>
Pin to prevent regression (a future brew upgrade will revert you to the vulnerable stable bottle):
brew pin <package>
Wait for a release and file an issue upstream asking for one.
Existing upstream issues
Before filing, search the project's issue tracker:
gh search issues --repo <org>/<repo> "<OSV-ID or OSS-Fuzz issue number>"
gh issue list --repo <org>/<repo> --state open --search "<crash function name>"
Not all projects use GitHub Issues (e.g., ICU uses Jira, Chromium uses Monorail). Check the project's README for their bug tracker. OSS-Fuzz auto-notifies project owners, but that notification doesn't always become a public issue.
Verify after remediation
Re-scan after any fix. If the vulnerability still shows, the OSV record may not have been updated yet.
brew vulns --severity medium
Step 4: Update Everything
brew upgrade, npm update -g, gem update, rustup update... nobody runs all of these consistently. Topgrade does:
brew install topgrade
topgrade
It detects what's installed and upgrades all of it. Configure at ~/.config/topgrade/topgrade.toml:
[misc]
cleanup = true
[brew]
greedy_cask = true # upgrade casks without version bumps
Some tools (macOS softwareupdate, RubyGems system) fail in non-interactive mode. Add them to disable if the noise bothers you.
Step 5: Automate It
Daily: Homebrew autoupdate (built-in)
brew autoupdate start 86400 --upgrade --cleanup
This uses launchd behind the scenes. Runs every 24 hours: update, upgrade, cleanup. No cron, no plist, no maintenance.
Check status: brew autoupdate status
Disable: brew autoupdate stop
Weekly: Full topgrade sweep
Add a cron entry or run it manually every Monday:
topgrade --yes
Monthly: Cleanup dead weight
brew autoremove # remove orphan dependencies
brew cleanup --prune=30 # delete old versions
brew tap # check for abandoned taps
Step 6: Hardening Baseline
# Firewall
/usr/libexec/ApplicationFirewall/socketfilterfw --getglobalstate
# FileVault (disk encryption)
fdesetup status
# System Integrity Protection
csrutil status
# Gatekeeper
spctl --status
All four should report enabled. If any don't, fix them:
# Enable firewall
sudo /usr/libexec/ApplicationFirewall/socketfilterfw --setglobalstate on
# FileVault (prompts for restart)
sudo fdesetup enable
For a full CIS Benchmark audit, use Mergen (85 controls with auto-fix) or lynis:
brew install --cask sametsazak/mergen/mergen-app
# or
brew install lynis && sudo lynis audit system
Quick Setup
# 1. Audit and clean up dead taps
brew tap # review the list
brew untap <dead-tap> # remove any you don't need
# 2. Install tooling
gem install brew-vulns
brew install topgrade
# 3. Configure topgrade
mkdir -p ~/.config/topgrade
cat > ~/.config/topgrade/topgrade.toml << 'EOF'
[misc]
cleanup = true
[brew]
greedy_cask = true
EOF
# 4. Set up daily autoupdate
brew autoupdate start 86400 --upgrade --cleanup
# 5. Initial full update
topgrade --yes
# 6. Vulnerability check
brew vulns --severity medium
After this, your workstation maintains itself daily. Run topgrade weekly for the full sweep, and brew vulns whenever you want a CVE status check.
Top comments (0)