DEV Community

Sonu Preetam
Sonu Preetam

Posted on

Keeping Your Mac Dev Environment From Rotting

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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'
Enter fullscreen mode Exit fullscreen mode

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'
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

Pin to prevent regression (a future brew upgrade will revert you to the vulnerable stable bottle):

brew pin <package>
Enter fullscreen mode Exit fullscreen mode

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>"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Monthly: Cleanup dead weight

brew autoremove          # remove orphan dependencies
brew cleanup --prune=30  # delete old versions
brew tap                 # check for abandoned taps
Enter fullscreen mode Exit fullscreen mode

Step 6: Hardening Baseline

# Firewall
/usr/libexec/ApplicationFirewall/socketfilterfw --getglobalstate

# FileVault (disk encryption)
fdesetup status

# System Integrity Protection
csrutil status

# Gatekeeper
spctl --status
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)