I have switched my full OS-level dev env to https://mise.jdx.dev/ from Homebrew+pipx+npm, initially as an experiment but found out that it actually works amazingly well. Many things get installed directly from GitHub releases or a corresponding package manager (uv, pnpm, go get ...), zero glue code to "repackage", zero version lag. You can install any arbitrary version of a package, even multiple ones at once, and dynamically adjust which ones are active per working folder or explicitly through environments.
Funnily Mise does not support dependencies, and I was quite surprised that it mostly doesn't matter, as either pnpm/uv handles that, or it's a static binary that just works. In the past, had the unfortunate experience of packaging a Python application for Homebrew (the ridiculous process involved importing around 50 dependencies as "resources", building every single one from source or manually checking if it's already on Homebrew, declaring build toolchains for 5 different programming languages as dependencies, waiting over an hour for CI to finish on every update, then an upstream update introduced a "build-time dependency loop" and the project suddenly became unpackable for Homebrew) so I totally get why Mise took the "easy way out" and just relies on language-specific package managers directly.
Only thing from my Brewfile that I couldn't replace was the Docker CLI (needed to interact with Colima). And I still use Homebrew for casks. I encourage others to experiment with their dev setups, there are some amazing new tools out there.
Mise installs itself as a static binary actually (but it's of course packaged in many registries), and while there are some third party registries it delegates to for some packages (aqua, asfd), most stuff I have installed is either built-in, or from PyPI, npm or GitHub, i.e. directly published by the upstream maintainers. More info: https://mise.jdx.dev/dev-tools/backends/
Hi Mike, I’m @bfontaine on GitHub (I helped maintain Homebrew in ~2014-2016). I’m always impressed at your longevity as a maintainer; it’s been like what, 16+ years you’ve been maintaining Homebrew and you’re still here, still shipping new features! Thank you for everything!
The concept of a "userspace package manager" is something I would expect Linux to have figured out twenty years ago. It's ridiculous that the usual situation for non-root users is "you can't install XY but feel free to build from source". Homebrew, Mise and Nix are filling that hole now. (Flatpak is more oriented towards GUI apps, and Snap... exists.)
Thanks for the update. Is there any chance we can get some kind of cooldown mechanism in Homebrew?
The only people I want to trust to quickly ship new code to my machine are Apple and my browser (which handles more untrusted input than anything else).
For everything else (vscode and its extensions, npm, homebrew, and all the apps that self-update), I prefer to err on the side of waiting a few days.
Some exceptional 0days might warrant a cooldown bypass, but even in its current form users are vulnerable to 0days until they run brew upgrade.
Also, where we package things from NPM/PyPi/RubyGems that have been subject to these attacks: we already apply cooldowns for you both when packaging and when creating PRs to update to new versions.
That doc is very useful and confidence inspiring in terms of being mainly about people and process, rather than about one single technical solution.
Relevant parts for those who have cool-downs at the top of mind:
> Across Homebrew’s history far more users have been protected by shipping zero-day fixes quickly than have been exposed to npm-style token-theft or crypto-mining attacks, so a global cooldown would be a net negative for most users’ security. The deeper reason Homebrew does not need a general cooldown is that, unlike language package managers, it already separates publishing from distribution: an upstream release does not reach users until it has passed human review, CI and checksum verification, which is the very review window that language-ecosystem cooldowns are trying to recreate.
[...]
> For ecosystems with a track record of fast-moving supply-side attacks, Homebrew applies a download cooldown: a freshly-published upstream version is not adopted immediately, giving the wider community time to detect and report a malicious release before Homebrew users are exposed. Cooldowns have been added for:
Bundler
RubyGems livecheck
npm and pip defaults
PyPI resource resolution
npm and PyPI in bump
Glad to see that Homebrew is taking security seriously. Still, I want to minimize the number of parties who can quickly get new code onto my machine.
Your doc says "Human review of each release." What does that actually entail?
uv had a release at 10:21am yesterday with 7,060 additions and 2,409 deletions. The new release was available in homebrew at 11:46am. What human review happened there?
I don't know of any other OS package manager that ships code this quickly to users. Arch Linux has not pushed the new release of uv yet, for example.
For those who don't know what broxit is talking about, they're referring to something like --minimum-release-age/minimumReleaseAge in many pieces of software and package managers to reduce vulnerability to supply chain attacks. Often times, such attacks are detected within a few days of compromise.
Most handle this by having release channels. You would `brew set-channel stable/edge`.
It annoyed me this week because I only had a few minutes to try elixir 1.20 after the announcement, and brew lagged behind. You can install erl and elixir by other means (I prefer to run my own toolchains) but it wasn’t worth doing in that moment.
Brew has or used to have a source option for some recipes and that basicallllly solves it too, if you squint.
That's only for upstream packages, i.e. what the CI pulls in when building bottles. Homebrew itself is a rolling package manager, essentially only supporting the "latest" version for each package, which doesn't work well with the usual "only install packages older than X" concept.
I assume this trust issue is related to the not-infrequent MacOS notifications asking for permission to run Ruby in the background or when the machine starts. It says nothing about Homebrew though.
Thanks for producing such an amazing piece of software. Most of my Mac installations are based on Homebrew, but I have to rely on version management tools like Pyenv or nvm for Python and Node. Wish there was some standard 'Homebrew' way to install multiple versions of node, php and Python
You need to set HOMEBREW_NO_UPGRADE_AUTO_UPDATES_CASKS to 1, as alluded to by a hint when it (first?) occurs. This means if you have hints off (via HOMEBREW_NO_ENV_HINTS) then I suspect you can start getting this behavior without warning which is a bummer.
Ah, I suppose I did word that poorly—I more mean that a significant breaking change (Casks that previously were documented as being excluded from auto-updating suddenly being auto-updated) which can occur silently is a rough end-user experience, even if the user explicitly opted into hiding hints.
Yes this is intended. We skip those that seem to have already auto-updated underneath. Our code for this is not yet rock solid so please file issues for those you notice are not doing the right thing here.
Interesting that the `brew-rs` experiment has concluded and didn't find much of a performance increase. I suppose that is expected though with a lot of the bottleneck being network IO?
Personally I stopped using Homebrew after I got screwed too many times on mandatory upgrades that I couldn't pin. I use a combination of Mise and MacPorts now so I don't get any more surprise breakage and forced obsolescence. Plus Mise allows me to upgrade to any new version, whereas with Homebrew you have to wait for whenever the tap feels like upgrading (llama.cpp tap skips every 10 releases)
Nix is also worth checking out, even if the Darwin packaging is a bit flaky. I really appreciate having cross-platform devshells when I have to alternate between Mac and Linux on a regular basis.
Mise is also cross-platform, we actually use it at work for projects we develop locally on macOS, then build in CI on Linux -- it even supports multiplatform lockfiles. I had a few tries with Nix but it's a lot to wrap your head around, Mise is simple to "just try".
I have switched my full OS-level dev env to https://mise.jdx.dev/ from Homebrew+pipx+npm, initially as an experiment but found out that it actually works amazingly well. Many things get installed directly from GitHub releases or a corresponding package manager (uv, pnpm, go get ...), zero glue code to "repackage", zero version lag. You can install any arbitrary version of a package, even multiple ones at once, and dynamically adjust which ones are active per working folder or explicitly through environments.
Funnily Mise does not support dependencies, and I was quite surprised that it mostly doesn't matter, as either pnpm/uv handles that, or it's a static binary that just works. In the past, had the unfortunate experience of packaging a Python application for Homebrew (the ridiculous process involved importing around 50 dependencies as "resources", building every single one from source or manually checking if it's already on Homebrew, declaring build toolchains for 5 different programming languages as dependencies, waiting over an hour for CI to finish on every update, then an upstream update introduced a "build-time dependency loop" and the project suddenly became unpackable for Homebrew) so I totally get why Mise took the "easy way out" and just relies on language-specific package managers directly.
Only thing from my Brewfile that I couldn't replace was the Docker CLI (needed to interact with Colima). And I still use Homebrew for casks. I encourage others to experiment with their dev setups, there are some amazing new tools out there.
Do you have an example? Sounds interesting.
Don't forget that mise depends on package registries, to install itself as well as its tools.
Mise installs itself as a static binary actually (but it's of course packaged in many registries), and while there are some third party registries it delegates to for some packages (aqua, asfd), most stuff I have installed is either built-in, or from PyPI, npm or GitHub, i.e. directly published by the upstream maintainers. More info: https://mise.jdx.dev/dev-tools/backends/
You'll see that mise recommends installing itself exclusively through package registries: https://mise.jdx.dev/installing-mise.html
pypi, npm, and even github (through releases) are registries.
Hi Mike, I’m @bfontaine on GitHub (I helped maintain Homebrew in ~2014-2016). I’m always impressed at your longevity as a maintainer; it’s been like what, 16+ years you’ve been maintaining Homebrew and you’re still here, still shipping new features! Thank you for everything!
17 in September. Thanks for all your great work at the time! Hope you’re well <3
Thanks for all the hard work.
We are not many [1], but Homebrew has been a great way to quickly bootstrap an environment in immutable Linux distributions.
Note that certain operating systems such as Universal Blue's Bazzite (1.28%), Bluefin (0.49%) and Aurora (0.28%) default to bundling Homebrew [2].
[1] https://formulae.brew.sh/analytics/os-version/365d/
[2] https://github.com/ublue-os/brew
The concept of a "userspace package manager" is something I would expect Linux to have figured out twenty years ago. It's ridiculous that the usual situation for non-root users is "you can't install XY but feel free to build from source". Homebrew, Mise and Nix are filling that hole now. (Flatpak is more oriented towards GUI apps, and Snap... exists.)
I'm using nix on Bazzite, with home-manager
Thanks for the update. Is there any chance we can get some kind of cooldown mechanism in Homebrew?
The only people I want to trust to quickly ship new code to my machine are Apple and my browser (which handles more untrusted input than anything else).
For everything else (vscode and its extensions, npm, homebrew, and all the apps that self-update), I prefer to err on the side of waiting a few days.
Some exceptional 0days might warrant a cooldown bypass, but even in its current form users are vulnerable to 0days until they run brew upgrade.
https://docs.brew.sh/Supply-Chain-Security details how we’re handling cooldowns and why we have a very different risk profile to e.g. NPM.
Also, where we package things from NPM/PyPi/RubyGems that have been subject to these attacks: we already apply cooldowns for you both when packaging and when creating PRs to update to new versions.
That doc is very useful and confidence inspiring in terms of being mainly about people and process, rather than about one single technical solution.
Relevant parts for those who have cool-downs at the top of mind:
> Across Homebrew’s history far more users have been protected by shipping zero-day fixes quickly than have been exposed to npm-style token-theft or crypto-mining attacks, so a global cooldown would be a net negative for most users’ security. The deeper reason Homebrew does not need a general cooldown is that, unlike language package managers, it already separates publishing from distribution: an upstream release does not reach users until it has passed human review, CI and checksum verification, which is the very review window that language-ecosystem cooldowns are trying to recreate.
[...]
> For ecosystems with a track record of fast-moving supply-side attacks, Homebrew applies a download cooldown: a freshly-published upstream version is not adopted immediately, giving the wider community time to detect and report a malicious release before Homebrew users are exposed. Cooldowns have been added for:
Glad to see that Homebrew is taking security seriously. Still, I want to minimize the number of parties who can quickly get new code onto my machine.
Your doc says "Human review of each release." What does that actually entail?
uv had a release at 10:21am yesterday with 7,060 additions and 2,409 deletions. The new release was available in homebrew at 11:46am. What human review happened there?
I don't know of any other OS package manager that ships code this quickly to users. Arch Linux has not pushed the new release of uv yet, for example.
+1
For those who don't know what broxit is talking about, they're referring to something like --minimum-release-age/minimumReleaseAge in many pieces of software and package managers to reduce vulnerability to supply chain attacks. Often times, such attacks are detected within a few days of compromise.
Here's Bun's, as an example: https://bun.com/docs/pm/cli/install#minimum-release-age
Most handle this by having release channels. You would `brew set-channel stable/edge`.
It annoyed me this week because I only had a few minutes to try elixir 1.20 after the announcement, and brew lagged behind. You can install erl and elixir by other means (I prefer to run my own toolchains) but it wasn’t worth doing in that moment.
Brew has or used to have a source option for some recipes and that basicallllly solves it too, if you squint.
It's in this release, see this section:
> Cooldowns, livecheck and bumping
That's only for upstream packages, i.e. what the CI pulls in when building bottles. Homebrew itself is a rolling package manager, essentially only supporting the "latest" version for each package, which doesn't work well with the usual "only install packages older than X" concept.
100% need this.
I assume this trust issue is related to the not-infrequent MacOS notifications asking for permission to run Ruby in the background or when the machine starts. It says nothing about Homebrew though.
Thanks for producing such an amazing piece of software. Most of my Mac installations are based on Homebrew, but I have to rely on version management tools like Pyenv or nvm for Python and Node. Wish there was some standard 'Homebrew' way to install multiple versions of node, php and Python
Have a look at https://mise.jdx.dev/, it's exactly what you're looking for!
Congrats on the performance improvements. That's the most pleasant `brew upgrade` session I've had in years
Awesome! Thank you for the update.
I noticed that homebrew updated _all_ my casks when running 'brew upgrade' (even those with "auto_updates: true" in their Cask JSON API).
Is this intended, new default behavior? This did not use to happen...
You need to set HOMEBREW_NO_UPGRADE_AUTO_UPDATES_CASKS to 1, as alluded to by a hint when it (first?) occurs. This means if you have hints off (via HOMEBREW_NO_ENV_HINTS) then I suspect you can start getting this behavior without warning which is a bummer.
See also: https://docs.brew.sh/FAQ#why-arent-some-apps-included-during...
> This means if you have hints off (via HOMEBREW_NO_ENV_HINTS) then I suspect you can start getting this behavior without warning which is a bummer.
I read this as "This means if you close your eyes you don’t see things, which is a bummer."
This means if you have hints off (via HOMEBREW_NO_ENV_HINTS) then I suspect you can start getting this behavior without warning which is a bummer.
When you instruct the system not to tell you things, the system not telling you those things is a bummer?
If I could get more of the tech I interact with to stop doing things I didn't ask it to, it would reduce a lot of stress and wasted time.
Ah, I suppose I did word that poorly—I more mean that a significant breaking change (Casks that previously were documented as being excluded from auto-updating suddenly being auto-updated) which can occur silently is a rough end-user experience, even if the user explicitly opted into hiding hints.
Yes this is intended. We skip those that seem to have already auto-updated underneath. Our code for this is not yet rock solid so please file issues for those you notice are not doing the right thing here.
Is there a way to `brew trust` inside my Brewfile? That'd be nice for the handful of formulas I install from github repos via `brew bundle --global`.
This is described here (https://docs.brew.sh/Tap-Trust) if you scroll down a bit.
`brew tap/recipe, trusted: true`
Interesting that the `brew-rs` experiment has concluded and didn't find much of a performance increase. I suppose that is expected though with a lot of the bottleneck being network IO?
Personally I stopped using Homebrew after I got screwed too many times on mandatory upgrades that I couldn't pin. I use a combination of Mise and MacPorts now so I don't get any more surprise breakage and forced obsolescence. Plus Mise allows me to upgrade to any new version, whereas with Homebrew you have to wait for whenever the tap feels like upgrading (llama.cpp tap skips every 10 releases)
I'm in the "switched most to Mise" stage, might look into MacPorts for the remaining stuff, thanks for the tip!
Nix is also worth checking out, even if the Darwin packaging is a bit flaky. I really appreciate having cross-platform devshells when I have to alternate between Mac and Linux on a regular basis.
Mise is also cross-platform, we actually use it at work for projects we develop locally on macOS, then build in CI on Linux -- it even supports multiplatform lockfiles. I had a few tries with Nix but it's a lot to wrap your head around, Mise is simple to "just try".
Does Homebrew have good support for exact (and older) versions of packages now?
Nope, still rolling. Have a look at https://mise.jdx.dev/ if you need exact versions
I don't think that's a part of its goals at all.
Thanks for the hardwork.
Is the eventual goal to move most formula/cask behavior into declarative install steps and treat Ruby as an escape hatch?
Yes, exactly. The goal is you can install all official packages without needing custom postinstall/preflight/postflight blocks.
homebrew is so nice, thank you for all your effort
Hell yeah, tap trust!!!