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, orzsh - 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
→ brewHomebrewCommand 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
| Input | Rule |
|---|---|
| Formula names | ASCII 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 names | ASCII letters, digits, @, -, _, ., +; up to 120 characters |
| Search queries | The same character set as cask tokens; up to 80 characters |
| Taps | Exactly owner/repository, using ASCII letters, digits, -, _, and . |
| Brewfiles to read | An absolute path to an existing file named Brewfile or ending in .brewfile, up to 1 MB |
| Brewfiles to install | Only 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 path | Absolute, 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
| Operation | Confirmation |
|---|---|
| Install a formula or cask | Yes |
| Uninstall a formula or cask | Yes |
| Upgrade one package or all packages | Yes |
brew update | Yes |
| Start, stop, or restart a service | Yes |
| Add or remove a tap | Yes |
| Pin or unpin a formula | Yes |
| Install the missing items of a Brewfile | Yes, after the comparison |
| Cleanup | Yes, 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
gitprocesses 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=1Homebrew 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 itselfThe Dock badge runs only brew outdated --json=v2.
External links
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
| Data | Location |
|---|---|
| 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 |
| Logs | Unified 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.