How to check Homebrew packages for CVEs before you upgrade

It is Monday morning, coffee in hand, and you run brew upgrade to catch your tools up — gh, ImageMagick, a database client, whatever drifted out of date over the weekend. A list scrolls past, you press enter, and Homebrew cheerfully downloads and installs the newest version of each one. It is probably the most trusted command in your day: Homebrew is where your Mac’s developer tools come from, so of course you keep it current. Updating is the responsible thing to do, right?

Here is the assumption hiding inside that habit. brew upgrade never asks whether the new version it is about to install carries a known security flaw — or whether the recipe describing it was quietly changed. It simply fetches the latest and runs it. Homebrew’s packages are not screened for security: they are formulae maintained by volunteers in public taps, and a single compromised formula, or a hijacked tap, reaches every machine that upgrades from it. Most mornings that is completely fine. The one morning it is not, you have handed a fresh, hostile binary the run of your laptop — and you did it deliberately, with the command you trust most.

It is also rarely just the one tool you meant to update. Bumping a single formula can pull in or upgrade a handful of dependencies, each its own download from its own maintainer, and brew upgrade checks none of them against any vulnerability database — that is simply not what it is for. homebrew-safe-upgrade makes it what it is for: before anything is installed or upgraded, it checks the target version of every package, and the dependencies arriving with it, and only lets the clean ones through.

The problem, in plain terms

There are really three separate ways a brew upgrade can hurt you, and stock Homebrew guards against none of them. The obvious one is a known vulnerability: the new version has a published CVE, and you install it anyway because nothing stopped to look. The second is freshness: the most dangerous release is often the one published an hour ago, in the gap between an attacker stealing a maintainer’s credentials and the world noticing — far too new for any CVE database to list. The third is tampering: a tap that has been hijacked can serve a different binary under a version number you already trust, so the version string looks normal while the bytes are not.

Who runs into this

Every developer on macOS or Linux who uses Homebrew — which is most of them — and every CI pipeline that brew installs its toolchain. None of them reads the changelog of every transitive dependency before hitting enter; the whole point of brew upgrade is that you do not have to think about it. That is exactly why the check belongs in the command itself.

What homebrew-safe-upgrade does — and how it works

homebrew-safe-upgrade adds two commands, brew safe-upgrade and brew safe-install, that wrap the real thing. Each one runs the same gate before handing off to Homebrew.

How homebrew-safe-upgrade screens an upgradeA brew upgrade is intercepted by the wrapper, which checks each package for CVEs, a freshness hold and a bottle-SHA tamper check before installing, letting only clean packages through.brew upgradeno security checksafe-upgradeCVE + freshness+ SHA tamper-checkClean onlyrisky ones held back
Every package and its dependencies are checked before brew installs — vulnerable, too-fresh or tampered ones are held.

It checks the target version against three databases

Every outdated package is checked against OSV.dev, the GitHub Advisory Database and NIST NVD, with version-aware filtering so an old CVE that does not affect the version you are actually installing does not raise a false alarm. Clean packages are offered for upgrade; vulnerable ones are blocked and listed separately. (--yes runs it unattended for CI.)

It checks the dependencies coming in with the upgrade

The same gate is applied to the transitive dependencies the operation would bring onto your system — both brand-new ones and existing ones whose version is being bumped — so a clean-looking tool cannot smuggle a vulnerable dependency in behind it.

A freshness hold for brand-new releases

By default, any formula or cask published less than three days old is held back — the window in which a compromised release is typically live but not yet flagged anywhere. The hold is fail-closed: if a package’s age cannot be verified (the source repo is unreachable, GitHub is rate-limiting), it is held, not waved through. It is also CVE-aware — if the version you currently have installed has a known flaw, the hold on its fix is skipped, so a freshness rule never keeps a security patch from you. --min-age 0 turns it off.

A tamper check for hijacked taps

This is the defence against the version-string-looks-fine-but-the-bytes-changed attack. homebrew-safe-upgrade compares each bottle’s SHA against the canonical hash published at formulae.brew.sh, and blocks a tap that ships a different binary under the same version number before Homebrew ever installs it.

Install & use it

Install

Add the 5bats tap once, then install the wrapper:

brew install sharkyger/tap/safe-upgrade

Use it

brew safe-upgrade               # check every outdated package, upgrade only the clean ones
brew safe-install wget curl     # check these (and their deps) before installing
brew safe-upgrade --yes         # unattended, for CI
brew safe-upgrade --min-age 7   # stricter freshness hold

Full flag reference, cask coverage notes, and source are on GitHub: github.com/sharkyger/homebrew-safe-upgrade (MIT).

FAQ

Are Homebrew packages vetted for security?

No. Homebrew formulae and casks are community-maintained recipes in public taps; nobody screens each release for known vulnerabilities, and brew upgrade does not check. homebrew-safe-upgrade adds that check before anything installs.

Does brew upgrade check for vulnerabilities?

It does not — brew upgrade fetches and installs the newest version regardless of known CVEs. brew safe-upgrade checks each target version, and the dependencies coming in with it, against three vulnerability databases first, and only upgrades the clean ones.

Can a Homebrew tap be tampered with?

Yes — a compromised tap could serve a different binary under a version number you already trust. homebrew-safe-upgrade compares each bottle’s SHA against Homebrew’s canonical record and blocks a mismatch before brew installs it.


homebrew-safe-upgrade is one of the 5bats supply-chain gates — the same check-before-it-runs idea applied to Python, PHP, and the packages an AI assistant installs.

See the source and full options on GitHub →