Skip to content

Releases: sharkyger/homebrew-safe-upgrade

v0.2.7 — ecosystem-aware NVD matching, package-anchored output, CVE-named age bypass

12 Jun 23:08
v0.2.7
b826f93

Choose a tag to compare

Security-precision and UX release closing all four open user reports — thanks @aleksandrs-ledovskis for the detailed issues.

Fixed

  • Same-named packages from other ecosystems no longer block a Homebrew formula (#74). NVD keyword search matches on text, so a formula could collide with an identically-named package from a language ecosystem — the canonical cmake was permanently blocked by an advisory for the long-dead npm cmake package. The scanner now reads the CPE 2.3 target_sw field and skips advisories whose every applicability statement is pinned to a different language ecosystem. Fail-closed bounds kept: one generic/matching applicability statement — or no CPE data at all — still flags. CPE relevance is also evaluated when no version is supplied; distro- and OS-scoped advisories are filtered on that path too.
  • oracle removed from the distro-vendor CPE filter. Oracle is also the upstream vendor of widely brewed software (MySQL, OpenJDK, VirtualBox), so its application CPEs are now evaluated like any other upstream vendor. Oracle Linux distro CPEs are OS-type and remain filtered.
  • Per-package output lines no longer print above the package they belong to (#73), in brew safe-upgrade and brew safe-install alike.

Added

  • The age-check CVE bypass now names the CVEs it acted on (#72) — severity, CVSS score, and source for up to 5 findings print under the bypass line.

Documentation

  • README documents where the standalone scanner lives per install route (#75): $(brew --prefix safe-upgrade)/libexec/ for tap installs, $(brew --prefix)/bin/ for script installs.

Install / upgrade

  • Tap (recommended): brew install sharkyger/tap/safe-upgrade — formula bump to v0.2.7 follows shortly; then brew update && brew upgrade safe-upgrade
  • Script: re-run install.sh (pinned to this tag, SHA256-verified) or brew safe-update

Full details in the CHANGELOG.

v0.2.6 — brew dispatcher --help fix

12 Jun 10:37
v0.2.6
8f506da

Choose a tag to compare

Patch release. Follow-up to #66 that completes the --help fix for the real-world Homebrew dispatcher path.

Fixed

  • brew safe-upgrade --help (and safe-install / safe-update) now render the tool's own usage instead of Homebrew's generic Example usage: banner (#66 follow-up). Homebrew's dispatcher intercepts --help before exec'ing an external command and renders help only from lines beginning with #: (see External Commands); the scripts carried plain # comments, so brew fell back to its built-in banner and print_help() never ran on that path. Each brew-safe-* script now carries a #: help block mirroring its print_help(), and a regression test drives brew <cmd> --help through the real dispatcher to assert the generic banner is gone. The direct-invocation --help added in 0.2.5 was unaffected and continues to work.

Dogfooded before publish: --help on all three commands renders the tool's own usage via the real brew dispatcher on linuxbrew (Linux) and macOS-arm64; --version reports 0.2.6; no /opt/homebrew vs /usr/local hardcoding.

Full changelog: https://github.com/sharkyger/homebrew-safe-upgrade/blob/v0.2.6/CHANGELOG.md

v0.2.5 — CLI --help, hardened self-updater, static-analysis floor

10 Jun 22:10
v0.2.5
defb750

Choose a tag to compare

brew-safe-upgrade v0.2.5

User-facing CLI help, a hardened self-updater, and a static-analysis floor.

Added

  • --help / -h for all three commands (brew safe-upgrade, brew safe-install, brew safe-update) — a synopsis, flag listing, and examples, printed even on a partially-installed tree. Closes #66 (thanks @aleksandrs-ledovskis for the request).

Changed

  • The self-updater (brew-safe-update) now matches install.sh's supply-chain hardening: it updates to the latest published release tag (never a moving branch) and verifies every file against that release's SHA256SUMS manifest before an atomic, fail-closed swap — including the fresh updater it re-execs, verified before it takes over. Both curl-fetch routes now share one pin-and-verify path.
  • Quieter brew update step — Homebrew's own harmless description-cache backtrace is filtered out; real warnings/errors still show.

Tooling

  • Added mypy, shfmt, markdownlint, an assertive CodeRabbit profile, and a pre-commit config wrapping the lint floor — all enforced in CI.

Docs

  • The README install section now leads with the Homebrew tap one-liner (brew install sharkyger/tap/safe-upgrade); the script install route is the documented secondary path.

Install / upgrade

brew install sharkyger/tap/safe-upgrade      # new install
brew update && brew upgrade safe-upgrade     # tap upgrade

v0.2.4 — harden the script installer

09 Jun 21:45
v0.2.4
2bf07d7

Choose a tag to compare

v0.2.4 — harden the script installer

The curl | bash script installer now pins to an immutable release tag and
verifies every file against a published SHA256 manifest before anything is
installed — bringing the script route in line with the integrity guarantees the
Homebrew tap route already provides.

Changed

  • Pinned, not moving. Downloads come from an immutable release tag, never the
    main branch — a curl | bash run always gets exactly one published release.
  • Verify before install. A published SHA256SUMS manifest drives the release
    file set; every file is staged and checksum-verified before anything lands in
    your Homebrew bin
    . A truncated, tampered, or missing file aborts the whole
    install — no partial state.
  • Atomic, same-filesystem swap and a fail-closed completeness floor so a
    truncated manifest can't yield a "successful" partial install.

Fixed

  • Single-source version. pyproject.toml is realigned with the VERSION
    file, with CI keeping them — and the installer's pinned tag — in lockstep.

Tests / CI

  • A new hermetic end-to-end install smoke runs the real installer over a local
    server in CI on Linux (bash 5) and macOS (bash 3.2) — the script-install route
    is exercised in CI for the first time. Full suite: 151 passing.

Trust model: the manifest and files travel the same TLS channel, so the
checksums are defense-in-depth (transfer integrity + tag immutability) on top of
HTTPS, not a replacement for it. The Homebrew tap route was already immutable
and is unaffected — brew install sharkyger/tap/safe-upgrade.

See CHANGELOG.md for full history.

v0.2.3 — fail-closed freshness hold (Casks, taps, lib*) + single-package upgrade

08 Jun 20:11
v0.2.3
e4a2546

Choose a tag to compare

Security + UX release. The --min-age freshness hold now covers Casks, tap formulae, and lib* formulae, and fails closed when it can't verify a release age — plus single-package upgrades and live progress during the dependency scan.

Fixed

  • Freshness hold now covers Casks, tap formulae, and lib* formulae — and fails closed (#62). The age check only resolved release dates from homebrew-core at Formula/<first-letter>/<name>.rb, so three classes of package came back "age unknown" and silently skipped the --min-age hold: Casks (which live in homebrew-cask), tap formulae (their own tap repo), and lib* formulae (libgit2, libheif, libusb, … — homebrew-core shards these under Formula/lib/, not Formula/l/). Lookups are now routed to the correct repo and path per package type, and a release age that genuinely cannot be verified is now held rather than allowed through. The same routing + fail-closed policy applies to the transitive-dependency age check in both safe-upgrade and safe-install. The CVE-aware bypass (skip the hold when the installed version has known CVEs) is unchanged, so security patches are never withheld.

Added

  • Single-package upgrades (#61): brew safe-upgrade <name> [<name> …] restricts the run to the named outdated package(s) instead of everything brew outdated reports. Matches the full name or its basename; a named package that isn't outdated is reported, not silently ignored.
  • --allow-unknown-age on safe-upgrade and safe-install — the explicit opt-out to permit packages whose release age can't be verified (default: such packages are held).
  • Dependency-scan progress (#60): the transitive-dependency scan now prints [i/N] checking <dep> <version>… before each dependency's network checks, so a large scan shows live progress instead of a silent wait.

Upgrade

  • Homebrew tap: brew update && brew upgrade safe-upgrade
  • Script install: re-run install.sh

Thanks to @aleksandrs-ledovskis for the detailed report in #62.

v0.2.2 — install hardening (atomic self-updater, --version)

06 Jun 15:40
v0.2.2
ef310db

Choose a tag to compare

Reliability release: fixes a recurring install bug where brew safe-upgrade could fail closed with bottle SHA resolver not found after an update.

Fixed

  • Self-updater no longer strands helper files. A brew safe-update from an older updater could fetch only some files yet print "All tools updated", leaving bottle_resolver.py / cask_nvd_map.py missing — so the next brew safe-upgrade failed closed. The updater now re-execs the freshly-fetched updater before the download loop and is atomic + fail-closed: it swaps files into place only if every declared file arrives intact, and never reports success on a partial set.
  • Scripts resolve their own directory through symlinks (portable, no macOS-hostile readlink -f), so helpers resolve for script, Homebrew-formula, and symlinked layouts on both architectures.
  • A missing helper now prints actionable guidance (run 'brew safe-update').

Added

  • --version on all three commands, with self-diagnosis: prints the version, the detected install route (formula vs script), whether every helper file is present, and a warning if both install routes are on PATH.

Verified

  • Real install-and-run dogfood on Intel and Apple Silicon Macs (script route) + macOS arm64 and x86_64-Linux CI.

Update: tap users brew update && brew upgrade safe-upgrade; script users brew safe-update.

Full changelog: v0.2.1...v0.2.2

v0.2.1 — PEP 440-correct pre-release version comparison

05 Jun 19:20
v0.2.1
4cb5ae8

Choose a tag to compare

A correctness patch for the CVE range matcher's version comparison.

Fixed

  • PEP 440-correct pre-release version comparison. The CVE range matcher now uses a small, dependency-free pre-release-aware comparator (dev < alpha < beta < rc < final, with trailing-zero equivalence) in place of the previous tuple parser, so pre-release versions such as 1.0-beta sort correctly relative to their final release when evaluated against advisory ranges. Constraint parsing is tightened and every comparison site is None-safe — unparseable versions or constraints fail closed (treated as affected). The tool stays dependency-free (no packaging runtime dependency). Covered by tests/test_version_validation.py.
  • The test_installed_old_version_is_treated_as_incoming test no longer reads the live Homebrew openssl@3 release date, so it stops failing for ~3 days after every openssl@3 bump.

Install / upgrade

  • Tap users: brew update && brew upgrade safe-upgrade
  • Script install: brew safe-update

Full changelog: v0.2.0...v0.2.1

v0.2.0 — arch-aware bottle-SHA + self-healing updater

03 Jun 21:40
v0.2.0
2b31610

Choose a tag to compare

Security-first wrapper around brew upgrade / brew install — checks every package against NIST NVD, OSV.dev, and GitHub Advisory before the upgrade proceeds. Fail-closed by design.

Highlights since v0.1.1

  • Arch-aware bottle-SHA resolver — fixes a uniform Intel (x86_64) false positive where the SHA check flagged every bottle as [BLOCKED] SHA mismatch (it compared the Intel bottle's SHA against the arm64 bottle's). Now resolves the bottle tag by the host's real arch + OS, never crossing architecture. Confirmed fixed on real Intel hardware.
  • SHA verification is default-on for brew safe-install / safe-upgrade (local bottle SHA vs the canonical SHA at formulae.brew.sh). Tampering blocks and is never overridable by --yes. Opt out with --no-verify-sha.
  • --min-age 3-day freshness hold by default — holds back formulae published in the last N days (supply-chain worm window), with a CVE-aware bypass so security patches still reach you. --min-age 0 to disable.
  • Curated cask → NVD keyword map (cask_nvd_map.py) for better cask CVE coverage.
  • Self-healing brew safe-update — updates itself first, then re-runs, so a single brew safe-update always pulls the full current file set (no more "run it twice" / bottle SHA resolver not found).

Full details in CHANGELOG.md.

Install

curl -fsSL https://raw.githubusercontent.com/sharkyger/homebrew-safe-upgrade/main/install.sh | bash

v0.1.1 — hardening pass

26 Apr 15:34
v0.1.1
916c0fe

Choose a tag to compare

Internal correctness + defense-in-depth fixes following the v0.1.0 review. No flag/env-var surface change.

Highlights

  • brew list --versions parsing now uses awk '\$NF' so multi-keg installations compare against the newest installed version
  • INCOMING_DEPS is a proper bash array; pathological tap dep names with whitespace survive dedup + iteration
  • --min-age for tap-namespaced deps emits a [skip-dep-age] log line instead of relying on a homebrew-core lookup that doesn't apply
  • Dep names validated against ^[a-zA-Z0-9@._/-]+\$ before flowing into any URL or subprocess argument; same regex now also guards the main-package age check in both wrappers
  • Pre-install/upgrade warning split into distinct sections — known CVEs vs. --min-age holds — so two different risk signals are no longer conflated
  • In-code design note explains why transitive deps don't get the CVE-aware --min-age bypass that applies to the user-named package

See CHANGELOG.md for the full breakdown. PR: #25.

Notes

This release tag is GPG-signed — first signed tag on the repo.

v0.1.0 — first tagged release

26 Apr 13:20
cfc0e44

Choose a tag to compare

First tagged release.

Highlights

  • Transitive dependency CVE checking for brew safe-install and brew safe-upgrade — every new dependency version coming in with the operation is checked against OSV.dev, GitHub Advisory, and NIST NVD before brew touches your system. Default-on. Already-installed deps that aren't changing are deliberately skipped (that's brew-vulns' job).
  • --no-deps flag and BREW_SAFE_NO_DEPS={1,true,yes} env var — per-invocation opt-out for power users. No persistent config file.
  • --min-age N — hold packages published less than N days ago, with CVE-aware bypass for the explicitly-named package.
  • --verify-sha — verify bottle SHA against formulae.brew.sh before upgrading.
  • Cask support for both wrappers.
  • macOS bash 3.2 compatible.

Pre-tag history

Several rounds of fixes and hardening landed before this first tag — see CHANGELOG.md for the grouped backfill.

Known follow-ups

Six items from the pre-merge multi-agent review have been filed as issues #19#24. None are blockers; each is a worthwhile improvement.