Skip to Content
Security

Security

Brewwery sits between you and a package manager that can change your whole system, so it is conservative by design.

Principles

  • No shell — Homebrew is started directly, never through sh, bash, or zsh
  • No free-form commands — only a fixed set of Homebrew commands can run
  • No surprises — every change to Homebrew needs your confirmation
  • No elevated privileges — no sudo, no helper tools, no admin prompts
  • No telemetry — no analytics, crash reporting, accounts, or cloud sync
  • No background changes — background checks only read state

How a command runs

SwiftUI view → feature model → HomebrewClient → HomebrewCommand validates arguments, produces a fixed argument list → HomebrewRunner → child process posix_spawn, own process group, no shell → brew

HomebrewCommand is a closed Swift enum. Each case produces a fixed argument list, and none accepts arbitrary arguments, so the interface has no way to build a command line. Every value that comes from you — a package name, a search query, a tap, a service, a file path — is validated before the argument list exists, and it is always passed as a single argument, never interpreted by a shell.

Validation rules

InputRule
Formula namesASCII letters, digits, @, -, _, ., +, and / for tap-qualified names such as mongodb/brew/mongodb-community; no leading, trailing, or double /, and no .. segments; up to 120 characters
Cask tokens and service namesASCII letters, digits, @, -, _, ., +; up to 120 characters
Search queriesThe same character set as cask tokens; up to 80 characters
TapsExactly owner/repository, using ASCII letters, digits, -, _, and .
Brewfiles to readAn absolute path to an existing file named Brewfile or ending in .brewfile, up to 1 MB
Brewfiles to installOnly files Brewwery wrote to its own temporary folder, re-checked immediately before running: comments and tap, brew, or cask lines with one validated, quoted name
Custom Homebrew pathAbsolute, an executable file, and must answer brew --version

Package, cask, service, tap, and search identifiers reject spaces, shell metacharacters, quotes, redirection, and newlines. Option-shaped identifiers beginning with - are rejected before spawn. File paths use their own validation rules and are passed as individual arguments, never through a shell.

Changes need confirmation

OperationConfirmation
Install a formula or caskYes
Uninstall a formula or caskYes
Upgrade one package or all packagesYes
brew updateYes
Start, stop, or restart a serviceYes
Add or remove a tapYes
Pin or unpin a formulaYes
Install the missing items of a BrewfileYes, after the comparison
CleanupYes, and only after a preview

Each confirmation shows the exact command that will run.

Operation safety

  • Only one operation that changes Homebrew runs at a time within Brewwery. The coordinator reserves the slot before detection; the runner also rejects overlapping mutations. Metadata updates, taps, and pinning use this guard too. Commands started from Terminal or other apps are outside it.
  • Cancelling an operation signals Homebrew’s whole process group, so the downloads and git processes it started stop too. A process that ignores the request is force-stopped after five seconds.
  • Operations stop automatically after a fixed timeout: 45 minutes for install and upgrade, 90 minutes for a Brewfile install, 15 minutes for uninstall, 30 minutes for cleanup, and 5 minutes for services.
  • Buffered reads are cancellable with a 60-second timeout; short mutations have a 5-minute limit and path probes 10 seconds. Buffered stdout/stderr are capped at 16 MB per pipe with an explicit error. Streaming output and pending UI events are also bounded.

Homebrew environment

Brewwery adds two variables to Homebrew’s environment and nothing else:

HOMEBREW_NO_AUTO_UPDATE=1 HOMEBREW_NO_ANALYTICS=1

Homebrew therefore never updates itself in the middle of an operation, and sends no analytics for commands Brewwery runs.

Commands Brewwery can run

This is the complete list:

# Read-only brew --version brew config brew list --formula --json=v2 brew list --formula --versions --json # fallback for older Homebrew brew list --cask --json=v2 brew list --cask --versions --json # fallback for older Homebrew brew leaves brew list --pinned brew uses --installed <formula> brew search --formula <query> brew search --cask <query> brew info --json=v2 <formula> brew info --cask --json=v2 <cask> brew outdated --json=v2 brew tap brew services list --json brew cleanup -n brew doctor # Changes Homebrew or writes a file — always confirmed or explicitly requested brew update brew install <formula> brew install --cask <cask> brew uninstall <formula> brew uninstall --cask <cask> brew upgrade <formula> brew upgrade --cask <cask> brew upgrade brew tap <owner/repository> brew untap <owner/repository> brew services start|stop|restart <service> brew cleanup brew pin <formula> brew unpin <formula> brew bundle dump --force --file=<path> brew bundle install --no-upgrade --file=<path> # only a list Brewwery wrote itself

The Dock badge runs only brew outdated --json=v2.

App-owned links open in your default browser only when they match an allowlist: www.brewwery.com, brewwery.com, docs.brewwery.com, brew.sh, docs.brew.sh, the Brewwery repository on GitHub, and the exact https://github.com/madeburo author profile. These links must use HTTPS.

Package homepages reported by Homebrew use a separate check for a well-formed HTTP or HTTPS URL with a host. Problem-report email links are limited to [email protected] and open your mail app; you review and send the message yourself.

The app itself

  • Signed with an Apple Developer ID and notarized by Apple
  • Built with the hardened runtime and no entitlements
  • Updates are delivered by Sparkle  over HTTPS and must carry a valid EdDSA signature from the Brewwery release key, in addition to Apple’s code signature

Data on your Mac

DataLocation
Settings~/Library/Preferences/com.brewwery.app.plist
Operation history (last 100, output trimmed)~/Library/Application Support/Brewwery/history.json
Favorites~/Library/Application Support/Brewwery/favorites.json
LogsUnified logging, subsystem com.brewwery.app

Favorites and History encode and save JSON on a serial background queue. Normal quit waits for pending writes; failures show Retry, and unreadable JSON is backed up before replacement. Unsaved changes can still be lost on force-quit or power loss.

These files are not uploaded automatically. Sparkle checks the appcast and downloads app updates; automatic checks can be disabled in Settings. Homebrew fetches metadata and packages. Opening a website, package homepage, GitHub issue, or email is user-initiated. Review diagnostics and problem reports before sharing them. The website’s analytics policy is separate from the desktop app.

Reporting a vulnerability

Please report security issues privately as described in the security policy , not in a public issue.

Last updated on