How to Point Homebrew at Chainsaw
Route Homebrew bottles through your Chainsaw proxy, enforce org policy on the device with brew.env, and keep an inventory of what is installed.
Overview
By the end of this page, brew install on your Mac or Linux machine
downloads its bottles through Chainsaw, a bottle Chainsaw refuses fails
the install instead of falling back to ghcr.io, and your org’s policy is
enforced by brew itself for the parts the proxy cannot see.
Homebrew has two halves, and Chainsaw covers them differently:
| What brew downloads | Where from | How Chainsaw covers it |
|---|---|---|
| Bottles (prebuilt packages) | ghcr.io | Proxied. A bottle whose digest no homebrew-core formula declares is refused (CHW-2308). |
| The formula API (signed JSON) | formulae.brew.sh | Passed through unchanged. brew verifies its signature, so it is never rewritten. |
| Source builds, casks, third-party taps | each project’s own host | Not proxied. brew enforces the brew.env deny-lists Chainsaw compiles from your policy (step 4). |
chain305.com with your own Chainsaw host and CLIENT_ID /
CLIENT_SECRET with credentials from the Access page. The @default
segment is your org slug (Settings → Organization → Slug).Prerequisites
- A running Chainsaw instance (see Tutorial 01) with a
homebrewrepository. Every org has one; check Repositories. - Client credentials (Client ID and Secret).
- Homebrew on macOS or Linux.
- For steps 4 and 5, the Chainsaw CLI v0.22.152 or later (
chainsaw version).
1. Route bottles through Chainsaw
brew reads /etc/homebrew/brew.env before every command. Write the
routing lines there as root:
sudo mkdir -p /etc/homebrew
sudo tee /etc/homebrew/brew.env >/dev/null <<'EOF'
HOMEBREW_SYSTEM_ENV_TAKES_PRIORITY=1
HOMEBREW_API_DOMAIN=https://chain305.com/chainproxy/repository/@default/homebrew/api
HOMEBREW_ARTIFACT_DOMAIN=https://chain305.com/chainproxy/repository/@default/homebrew
HOMEBREW_ARTIFACT_DOMAIN_NO_FALLBACK=1
EOF
HOMEBREW_ARTIFACT_DOMAIN_NO_FALLBACK=1is the line that makes a refusal stick. Without it, brew retries a refused bottle against ghcr.io.HOMEBREW_SYSTEM_ENV_TAKES_PRIORITY=1stops a user’s ownbrew.envfrom overriding the system file.
2. Add your credential
Every user can read /etc/homebrew/brew.env, so the credential never goes
there. Put it in your shell profile (~/.zshrc or ~/.bashrc):
export HOMEBREW_DOCKER_REGISTRY_BASIC_AUTH_TOKEN="$(printf '%s:%s' CLIENT_ID CLIENT_SECRET | base64 | tr -d '\n')"
brew sends this as Basic auth to the artifact domain. Without it, every
bottle request gets 401 and, with NO_FALLBACK set, the install fails.
Or let the CLI write both files for you: chainsaw install-hook brew
writes the routing lines and the credential to ~/.homebrew/brew.env for
your user only.
3. Verify
Open a new shell, then:
brew fetch --force-bottle jq
You should see the bottle manifest and the bottle download succeed. In
the dashboard, Report → Audit log shows the jq bottle request. To
confirm brew picked up the file, run brew config and look for the
HOMEBREW_* lines from step 1.
If the fetch fails with curl: (22) The requested URL returned error: 401,
the credential from step 2 is missing in this shell. A 403 with
CHW-2308 means Chainsaw refused the bottle; see
CHW-2308.
4. Enforce your policy on the device
The proxy only sees bottles. To cover source builds, casks and taps, have Chainsaw compile your org policy into brew’s own settings:
chainsaw brew env --repo homebrew # print it
sudo chainsaw brew env --system --repo homebrew # write /etc/homebrew/brew.env
The file adds what your policies block or quarantine by name
(HOMEBREW_FORBIDDEN_FORMULAE, HOMEBREW_FORBIDDEN_CASKS,
HOMEBREW_FORBIDDEN_TAPS), licence blocks (HOMEBREW_FORBIDDEN_LICENSES)
and HOMEBREW_CASK_OPTS=--require-sha. brew refuses those itself, for
bottles and source builds alike. Conditional rules (CVSS, cooldown,
malware) are judged per download by the proxy and do not appear in the
file.
For a fleet, have your MDM fetch the file on a schedule and install it root-owned:
curl -fsS -H "Authorization: Bearer $CHAINSAW_TOKEN" \
"https://chain305.com/chainproxy/api/v1/homebrew/brew.env?repo=homebrew" \
-o /etc/homebrew/brew.env.new && mv /etc/homebrew/brew.env.new /etc/homebrew/brew.env
The token needs policies:read (an admin or MDM API key; a client
credential gets 401). Writing to a temporary file first means a failed
download never leaves a truncated brew.env. For a hard guarantee on
managed machines, also block ghcr.io, pkg-containers.githubusercontent.com
and formulae.brew.sh at the egress firewall.
5. Check installs before they happen (optional)
chainsaw brew wraps brew and checks each formula, cask and tap against
your org policy before brew runs:
chainsaw brew install jq
chainsaw brew bundle --file ~/Brewfile
To make that automatic for plain brew install, add the shell shims:
eval "$(chainsaw guard init)".
6. Keep an inventory of what is installed (optional)
chainsaw brew inventory --target my-laptop --dry-run # count only
chainsaw brew inventory --target my-laptop
This uploads the installed homebrew/core formulae as a declared target. Chainsaw rescans them and alerts on new CVEs and deprecations. Casks and third-party-tap formulae are listed as skipped.
What Chainsaw checks for Homebrew
- CVEs from Homebrew’s own advisory feed, with CVSS, EPSS and CISA KEV.
- Licence, deprecated and disabled formulae.
- Bottle checksums and build attestations.
- Release age (cooldown) and install popularity.
Not covered: there is no malware feed for Homebrew, no maintainer or typosquat signal, and no CVE data for casks.