Homebrew Vulnerability Scanning and Patch Management
Use brew vulns to check Homebrew formulae for known vulnerabilities, then use brew upgrade to install available fixes.
Your scheduler or MDM can run them across devices and retain the results for a central patch-management workflow.
The scanner checks formulae; it does not scan casks or every package installed by other tools.
Run these commands as the Homebrew owner on macOS or Linux, with brew and any required tools on PATH.
For root management jobs, use the absolute path with as-brew-user, for example /opt/homebrew/bin/brew as-brew-user in place of brew on Apple Silicon.
- Scan installed formulae
- Capture findings without losing errors
- Prioritise without hiding unresolved risk
- Select vulnerable upgrade candidates
- Apply and verify updates
- Automate patching through your management system
- Understand the limits of a clean scan
Scan installed formulae
- Refresh metadata and run an initial scan.
brew update
brew vulns --list-skipped
With no formula arguments, brew vulns checks installed formulae.
It queries the Open Source Vulnerabilities database using supported source identities and versions.
It can account for fixes supplied by formula patches, rather than assuming that every unchanged upstream version remains vulnerable.
Use --no-ignore-patches when investigating findings that those patches resolve.
- If investigating a single package, scan it and its dependencies.
brew vulns --deps git
- For a baseline review, scan formula entries in a reviewed Brewfile.
brew vulns --brewfile=Brewfile --deps
A Brewfile scan covers its formula entries, not its casks, language-package entries or every installed package omitted from the file. For uninstalled formulae, the scan assesses the available formula definition rather than an installed artefact.
Capture findings without losing errors
brew vulns returns a non-zero status for open findings and for some incomplete-scan conditions.
A non-zero status alone does not distinguish a vulnerability from an operational failure.
Conversely, a zero status does not mean every package was scanned: unsupported-source skips can remain in the report.
- Create a protected report directory.
- Run this capture step from that directory.
#!/bin/bash
set -euo pipefail
scan_status=0
brew vulns --json > homebrew-vulns.json 2> homebrew-vulns.stderr || scan_status=$?
printf '%s\n' "${scan_status}" > homebrew-vulns.status
exit "${scan_status}"
- Retain the files even when the job exits unsuccessfully.
- Validate the JSON before importing it and retain standard error even when JSON parsing succeeds.
Each findings entry identifies the formula, scanned version, source repository, open vulnerabilities and vulnerabilities resolved by formula patches.
The report also describes scan coverage:
| Field | Meaning |
|---|---|
checked |
Number of formula targets queried, including targets without findings |
complete |
Whether all selected targets were assessed without the coverage gaps listed below |
skipped_formulae |
Formulae without a supported source identity |
outdated_without_sbom |
Older installations assessed using current-recipe data because evidence is missing |
unscanned_formulae |
Installed formula names mapped to untrusted_tap or resolution_error |
complete describes the selected formula scope; severity and fix filters can still exclude findings.
Network failures can prevent JSON output entirely.
An old installation without usable source evidence may be assessed using current-recipe data, with a warning and failing status; that result does not establish the installed version’s vulnerability status.
Prioritise without hiding unresolved risk
- Retain the unfiltered report and its coverage fields.
- Filter a remediation queue for the same installation:
brew vulns --json --severity=high --fix-available
--severity=high includes high and critical findings.
Unknown severity does not meet that threshold, so retain an unfiltered scan as well.
--fix-available means an upstream released fix is known; it does not guarantee that Homebrew already provides that version or that the device can install it.
Use --no-fix-available to investigate findings without an upstream released fix, including those with only an unreleased patch.
See the command reference for all fix-type filters.
Select vulnerable upgrade candidates
brew outdated --vulnerable uses the same OSV scanner to filter installed formulae that have an update available.
- Export the upgrade candidates, then preview the plan.
brew outdated --vulnerable --severity=high --json=v2
brew upgrade --vulnerable --severity=high --dry-run
Both commands imply --formula and reject --cask.
Omit --severity to include findings of every severity, including unknown severity.
Findings resolved by a formula patch are excluded using the same installed-version rules as brew vulns.
Pinned packages remain subject to normal pin behaviour: outdated can report them and upgrade leaves them pinned.
If any outdated candidate has an unsupported source or lacks reliable installed-source evidence, selection fails before upgrades begin. Resolve the coverage gap or review specific named candidates instead; a network failure also stops selection. This check covers the candidates being selected, not every installed package or retained keg. An empty selection upgrades nothing.
An available update is not proof that a vulnerability is fixed in that release.
--vulnerable selects affected installed formulae; it does not promise a vulnerability-free target version or restrict dependency upgrades to security fixes.
Keep a full brew vulns --json report to track findings with no available Homebrew update.
Apply and verify updates
- Apply the reviewed severity-based plan, then assess the result even if the upgrade fails. Run these as separate management steps and retain both results.
brew upgrade --vulnerable --severity=high --yes
brew vulns --json
Use explicit formula arguments when a rollout approves only a subset of candidates. The scan is repeated when the upgrade command runs, so review changing advisory data and package metadata as part of the rollout.
Homebrew may also upgrade dependencies and dependants, so review the complete plan.
- If the requirement is an approved minimum version, use the threshold instead.
brew outdated --minimum-version=2.50.1 git
brew upgrade --minimum-version=2.50.1 --dry-run git
brew upgrade --yes --minimum-version=2.50.1 git
brew list --versions git
The version above is an example policy threshold, not a current security recommendation. The command upgrades an installed package only when it is below the threshold and installs the available version, not necessarily that exact version. It does not install a missing package, override pins or guarantee that a sufficiently new release is available. Verify the resulting installed version and rescan after upgrading.
Automate patching through your management system
Use the MDM deployment and scheduling guides to assign these commands to a pilot group, then expand after reviewing the results. Retain the pre-upgrade scan, upgrade status and post-upgrade scan separately, including after failed upgrades; close findings only after verification and report offline devices, pins, missing coverage and unavailable fixes separately. Restart affected applications or services when needed and review retained older kegs; removing a device from a job’s scope does not undo an upgrade.
Understand the limits of a clean scan
An empty findings array means no matches for the assessed targets and filters, not that the device is free of vulnerabilities or malware. Review the coverage fields above and reconcile unresolved installations with inventory. Untrusted taps are not automatically evaluated; advisory matching can also miss bundled components or downstream changes.
For casks, combine brew outdated --cask --greedy, application inventory and upstream security advisories.
Project dependencies and extensions need ecosystem-specific scanners.
See package policies for complementary controls.