Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions docs/homebrew-publishing.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,11 +192,15 @@ commit, ABI namespace, derived bottle root, and formula matrix, each
Homebrew/brew commit, and exposes the patched temporary Homebrew worktree
through a short-lived launcher under the canonical
`/home/linuxbrew/.linuxbrew` prefix. This preserves the selected prefix and
Cellar so ordinary host build-dependency bottles remain usable. The job then
builds the required Kandelo pieces and executes the Formula build and test
without publisher credentials. Its strict handoff contains only
`manifest.json`, Homebrew's bottle JSON, and one gzip bottle archive. It
contains no Formula source, scripts, environment files, or credentials.
Cellar so ordinary host build-dependency bottles remain usable. Within that
read-only build, Homebrew uses a build-local XDG configuration store and
trusts only the reviewed selected tap before evaluating its dependency
Formulae. The store is removed with the build work directory; the publisher
does not disable tap-trust enforcement or reuse persistent account state.
The job then builds the required Kandelo pieces and executes the Formula
build and test without publisher credentials. Its strict handoff contains
only `manifest.json`, Homebrew's bottle JSON, and one gzip bottle archive.
It contains no Formula source, scripts, environment files, or credentials.
2. `upload-bottle` runs only for a write publication and receives only
`packages: write`. On a fresh runner it validates the strict build handoff
against the plan before exposing the token to an isolated ORAS upload. Its
Expand Down
10 changes: 10 additions & 0 deletions scripts/homebrew-bottle-build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,15 @@ cleanup() {
}
trap cleanup EXIT

# Formula dependencies are evaluated separately from the formula named on the
# command line. Trust the reviewed tap as a whole, but keep every Brew call in
# this build scoped away from user state. The launcher derives
# HOMEBREW_USER_CONFIG_HOME from XDG_CONFIG_HOME, so set the isolated XDG root
# before discovering the repository and prefix.
export XDG_CONFIG_HOME="$WORK_DIR/xdg-config"
mkdir -p "$XDG_CONFIG_HOME/homebrew"
chmod 0700 "$XDG_CONFIG_HOME" "$XDG_CONFIG_HOME/homebrew"

homebrew_patched_launcher_prepare "$BREW_BIN" "$PATCH_FILE" "$WORK_DIR"
BREW_BIN="$HOMEBREW_PATCHED_BREW_BIN"

Expand All @@ -113,6 +122,7 @@ export HOMEBREW_KANDELO_NODE="$(command -v node)"
export HOMEBREW_KANDELO_LLVM_BIN="${LLVM_BIN:-${WASM_POSIX_LLVM_DIR:-}}"

"$BREW_BIN" tap "$TAP_NAME" "$TAP_ROOT"
"$BREW_BIN" trust --tap "$TAP_NAME"
FORMULA_REF="$TAP_NAME/$FORMULA"
TAPPED_TAP_ROOT="$("$BREW_BIN" --repository "$TAP_NAME")"
TAPPED_FORMULA_PATH="$TAPPED_TAP_ROOT/Formula/$FORMULA.rb"
Expand Down
91 changes: 91 additions & 0 deletions scripts/test-homebrew-publish-workflow.sh
Original file line number Diff line number Diff line change
Expand Up @@ -879,6 +879,96 @@ EOF
fi
}

assert_bottle_build_trusts_selected_tap() {
local tap="$TMPDIR/bottle-trust-tap"
local brew_repo="$TMPDIR/bottle-trust-brew-repo"
local brew_prefix="$TMPDIR/bottle-trust-prefix"
local fake_brew="$TMPDIR/bottle-trust-brew"
local out="$TMPDIR/bottle-trust-out"
local log="$TMPDIR/bottle-trust.log"
local caller_config="$TMPDIR/caller-homebrew-config"
make_tap "$tap"
mkdir -p "$brew_repo" "$brew_prefix" "$caller_config"

cat >"$fake_brew" <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
if [ -n "${XDG_CONFIG_HOME:-}" ]; then
export HOMEBREW_USER_CONFIG_HOME="$XDG_CONFIG_HOME/homebrew"
fi
printf '%s|%s\n' "${HOMEBREW_USER_CONFIG_HOME:-}" "$*" >>"$FAKE_BREW_LOG"
case "${1:-}" in
--prefix)
printf '%s\n' "$FAKE_BREW_PREFIX"
;;
--repository)
if [ "$#" -eq 1 ]; then
printf '%s\n' "$FAKE_BREW_REPOSITORY"
else
printf '%s\n' "$FAKE_TAP_ROOT"
fi
;;
tap)
;;
trust)
[ "${2:-}" = "--tap" ]
[ "${3:-}" = "automattic/kandelo-homebrew" ]
[ -d "${HOMEBREW_USER_CONFIG_HOME:-}" ]
permissions="$(stat -c %a "$HOMEBREW_USER_CONFIG_HOME" 2>/dev/null || stat -f %Lp "$HOMEBREW_USER_CONFIG_HOME")"
[ "$permissions" = "700" ]
case "$HOMEBREW_USER_CONFIG_HOME" in
*/xdg-config/homebrew) ;;
*) exit 43 ;;
esac
;;
install)
exit 42
;;
*)
exit 44
;;
esac
EOF
chmod +x "$fake_brew"

if FAKE_BREW_LOG="$log" \
FAKE_BREW_PREFIX="$brew_prefix" \
FAKE_BREW_REPOSITORY="$brew_repo" \
FAKE_TAP_ROOT="$tap" \
HOMEBREW_BREW_FILE="$fake_brew" \
XDG_CONFIG_HOME="$caller_config" \
bash "$REPO_ROOT/scripts/homebrew-bottle-build.sh" \
--tap-root "$tap" \
--tap-repository Automattic/kandelo-homebrew \
--formula hello \
--arch wasm32 \
--out "$out" \
--bottle-root-url https://example.invalid/bottles \
>/dev/null 2>&1; then
fail "bottle trust fixture unexpectedly completed its sentinel install"
fi

local tap_line trust_line install_line trust_config first_config
tap_line="$(grep -n '|tap automattic/kandelo-homebrew ' "$log" | cut -d: -f1)"
trust_line="$(grep -n '|trust --tap automattic/kandelo-homebrew$' "$log" | cut -d: -f1)"
install_line="$(grep -n '|install --build-bottle --formula automattic/kandelo-homebrew/hello$' "$log" | cut -d: -f1)"
[ -n "$tap_line" ] && [ -n "$trust_line" ] && [ -n "$install_line" ] ||
fail "bottle build did not tap, trust, and install the selected tap"
[ "$tap_line" -lt "$trust_line" ] && [ "$trust_line" -lt "$install_line" ] ||
fail "bottle build did not trust the selected tap before formula evaluation"

trust_config="$(grep '|trust --tap automattic/kandelo-homebrew$' "$log" | cut -d'|' -f1)"
first_config="$(head -n1 "$log" | cut -d'|' -f1)"
[ -n "$trust_config" ] || fail "bottle build trust used no isolated config store"
[ "$first_config" = "$trust_config" ] ||
fail "launcher discovery ran outside the build-local Homebrew config store"
[ "$trust_config" != "$caller_config/homebrew" ] ||
fail "bottle build reused the caller's Homebrew config store"
[ ! -e "$trust_config" ] || fail "build-local Homebrew config survived cleanup"
[ -z "$(find "$caller_config" -mindepth 1 -print -quit)" ] ||
fail "bottle build mutated the caller's Homebrew config store"
}

assert_failure_preserves_metadata() {
local tap="$TMPDIR/failure-tap"
make_tap "$tap"
Expand Down Expand Up @@ -1168,6 +1258,7 @@ assert_matrix
assert_matrix_skips_unchanged_cache_key
assert_upload_dry_run
assert_upload_push_uses_relative_layer_path
assert_bottle_build_trusts_selected_tap
assert_generator_rejects_mismatched_homebrew_commit
assert_build_handoff_is_minimal_and_validated
assert_build_handoff_rejects_untrusted_content
Expand Down
Loading