How to Point Homebrew at Chainsaw

Beginner 20 minutes Developers / Platform Engineers Getting Started Updated

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 downloadsWhere fromHow Chainsaw covers it
Bottles (prebuilt packages)ghcr.ioProxied. A bottle whose digest no homebrew-core formula declares is refused (CHW-2308).
The formula API (signed JSON)formulae.brew.shPassed through unchanged. brew verifies its signature, so it is never rewritten.
Source builds, casks, third-party tapseach project’s own hostNot proxied. brew enforces the brew.env deny-lists Chainsaw compiles from your policy (step 4).
Replace 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 homebrew repository. 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=1 is the line that makes a refusal stick. Without it, brew retries a refused bottle against ghcr.io.
  • HOMEBREW_SYSTEM_ENV_TAKES_PRIORITY=1 stops a user’s own brew.env from 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.

Next Steps