Skip to content
Merged
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
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ decrypted and parsed live. No Kubernetes. macOS or Linux, amd64 or arm64.
| sh -s -- --claude-code
```

The URL is on `main`, but the script re-runs the copy from the newest
**release** when one carries it, so a `curl | sh` normally does not execute
unreleased changes. Add `--ref=main` to opt into main, or `--ref=vX.Y.Z` to pin.

2. **Open the viewer** in another terminal:

```sh
Expand Down
155 changes: 145 additions & 10 deletions authbridge/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,15 @@
# curl, not sh, so the script runs without it. `sh -s -- --flag` has no such
# failure mode. The env vars below still work.
#
# By default this script re-runs the copy from the newest RELEASE rather than
# executing whatever is currently on main — main is unstable by definition, and a
# `curl | sh` should not be the first thing to run a change nobody has released.
# --ref=main opts back in; --ref=vX.Y.Z pins.
#
# Environment:
# AUTHBRIDGE_VERSION=vX.Y.Z install a specific release tag (default: newest)
# AUTHBRIDGE_REF=REF same as --ref
# AUTHBRIDGE_VERSION=vX.Y.Z install binaries from a specific release
# (default: the release this script came from)
# AUTHBRIDGE_INSTALL_ONLY=1 same as --install-only
# AUTHBRIDGE_SKIP_DOWNLOAD=1 use the already-installed binaries in ~/.local/bin
# instead of downloading (re-run setup offline)
Expand Down Expand Up @@ -77,6 +84,10 @@ Options:
--claude-code after starting, offer to configure Claude Code to use it, so
it runs as plain `claude` with no environment variables
--local the default, spelled out
--ref=REF take THIS SCRIPT from a git ref instead of the newest release
(e.g. --ref=main for unreleased changes, --ref=v0.7.0-alpha.4
to pin). Binaries come from the same release unless
AUTHBRIDGE_VERSION says otherwise.
-h, --help this text

Environment:
Expand All @@ -97,14 +108,15 @@ for arg in "$@"; do
case "$arg" in
--install-only) MODE=install-only ;;
--claude-code) WIRE_CLAUDE_CODE=1 ;;
--ref=*) AUTHBRIDGE_REF="${arg#*=}" ;;
# --local is the default; accepted so writing it out explicitly works, and
# so it mirrors the proxy flag of the same name.
--local) MODE=local ;;
-h | --help)
usage
exit 0
;;
*) die "unknown option: $arg (try --claude-code, --install-only, --local, or no argument)" ;;
*) die "unknown option: $arg (try --claude-code, --install-only, --local, --ref=REF, or no argument)" ;;
esac
done
# Env form kept working; the flag wins if both are given.
Expand All @@ -115,6 +127,109 @@ fi
command -v curl >/dev/null 2>&1 || die "curl is required"
command -v tar >/dev/null 2>&1 || die "tar is required"

# newest_release prints the newest release tag, prereleases included.
# `releases/latest` excludes prereleases and this project ships them, so list
# releases (newest first) and take the first tag_name.
newest_release() {
# Returns non-zero on empty. The pipeline ends in sed, which exits 0 for empty
# input, so `version=$(newest_release) || die` could never fire on a
# rate-limited or offline API — the caller got an empty tag and failed later
# with an unactionable "download failed: abctl__darwin_arm64.tar.gz".
_tag=$(curl -fsSL "https://api.github.com/repos/${REPO}/releases?per_page=1" 2>/dev/null \
| grep -m1 '"tag_name"' | sed -e 's/.*"tag_name": *"//' -e 's/".*//')
[ -n "${_tag}" ] || return 1
printf '%s\n' "${_tag}"
}

# ere_escape quotes the ERE metacharacters in a literal so it matches exactly.
# Archive names contain dots, and an unescaped "." matches any character: the
# pattern for abctl_v0.7.0-alpha.3_..tar.gz also accepted
# abctl_v0X7X0-alpha_3_..Xtar.gz. Nothing exploitable followed — the count check
# or sha_check rejected it — but this script's whole subject is precision here.
ere_escape() {
# shellcheck disable=SC2016 # the sed script is literal on purpose
printf '%s' "$1" | sed 's/[].[^$()*+?{}|\\]/\\&/g'
}

# --- run the released copy of this script, not the one from main ---
#
# The documented command fetches this file from main, which is whatever landed
# last: an unreviewed or half-finished change there runs on someone's laptop
# immediately. Releases are tested, so by default this bootstrap re-runs the copy
# from the newest release and hands it the same arguments.
#
# SCRIPT_REF names the ref this copy came from and doubles as the recursion guard:
# the child sees it set and does not bootstrap again.
SCRIPT_REF="${AUTHBRIDGE_SCRIPT_REF:-}"
if [ -z "${SCRIPT_REF}" ]; then
want_ref="${AUTHBRIDGE_REF:-}"
if [ -z "${want_ref}" ]; then
want_ref="$(newest_release)" || true
fi
if [ -z "${want_ref}" ]; then
warn "could not resolve the newest release; continuing with the copy from main"
SCRIPT_REF="main"
elif [ "${want_ref}" = "main" ]; then
# Explicitly asked for main: this copy already is main.
SCRIPT_REF="main"
else
# Rebuild the argument list without --ref: it is meta, consumed here, and a
# released script from before --ref existed rejects it as an unknown option.
# Rotating the positional parameters keeps arguments with spaces intact,
# which building a string would not.
argc=$#
argi=0
while [ "${argi}" -lt "${argc}" ]; do
a="$1"
shift
argi=$((argi + 1))
case "$a" in
--ref=*) ;;
*) set -- "$@" "$a" ;;
esac
done

boot=$(mktemp)
url="https://raw.githubusercontent.com/${REPO}/${want_ref}/authbridge/install.sh"
# Capture the status code rather than collapsing every failure into one
# branch. A 404 means that ref genuinely predates this script — fall back.
# A transport error means we could not ask, and silently dropping to main
# there would break the exact guarantee this bootstrap exists to give.
# On a transport failure curl still prints "000" via -w AND exits non-zero,
# so appending our own default produced "HTTP 000000". Overwrite instead.
http=$(curl -sSL -o "${boot}" -w '%{http_code}' "${url}" 2>/dev/null) || http="000"
[ -n "${http}" ] || http="000"
if [ "${http}" = "200" ] && [ -s "${boot}" ]; then
info "Using the installer from ${want_ref}."
# set -e would abort the parent on a non-zero child before any of the
# lines below ran, leaking the downloaded script on every failed
# install. The if/else keeps the status and still cleans up.
if AUTHBRIDGE_SCRIPT_REF="${want_ref}" sh "${boot}" "$@"; then
status=0
else
status=$?
fi
rm -f "${boot}"
exit "${status}"
fi
rm -f "${boot}"
if [ "${http}" = "404" ]; then
# A release from before this script existed under that name. Falling
# back beats refusing to install, but name the copy that is running so
# a surprise is attributable.
warn "${want_ref} has no authbridge/install.sh (HTTP 404); continuing with the copy from main"
SCRIPT_REF="main"
else
# Blocked, offline, rate-limited, proxied, 5xx. We cannot tell whether a
# released installer exists, so do not quietly run main instead.
die "could not fetch the installer for ${want_ref} (HTTP ${http}) from ${url}.
Check the network, or choose explicitly:
--ref=main run the copy from main (unreleased changes)
--ref=vX.Y.Z use a specific release"
fi
fi
fi

# Verify the checklist file passed as $1 (run from the directory holding the
# files). shasum is preferred: it's always present on macOS and its -c reads the
# GNU-style checksums.txt reliably, whereas some non-GNU sha256sum builds reject
Expand Down Expand Up @@ -241,14 +356,18 @@ if [ "${AUTHBRIDGE_SKIP_DOWNLOAD:-}" = "1" ]; then
else

# --- resolve the release tag ---
# `releases/latest` excludes prereleases, and the project ships prereleases, so
# list releases (newest first) and take the first tag_name instead.
version="${AUTHBRIDGE_VERSION:-}"
if [ -z "$version" ]; then
info "Resolving newest release..."
version=$(curl -fsSL "https://api.github.com/repos/${REPO}/releases?per_page=1" \
| grep -m1 '"tag_name"' | sed -e 's/.*"tag_name": *"//' -e 's/".*//')
[ -n "$version" ] || die "could not resolve the newest release (set AUTHBRIDGE_VERSION=vX.Y.Z)"
# Default the binaries to the same release this script came from, so the
# script and the binaries it installs are one tested set rather than two
# independently-moving things.
case "${SCRIPT_REF}" in

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (non-blocking): ${archive} is interpolated unescaped into grep -E, so the . characters in the version and .tar.gz match any char. It's low-risk here — the real verification is shasum -a 256 -c below, the exact line-count guard bounds it, and an over-match tends to fail closed (a wrong-named line makes shasum -c look for a file that isn't there). But for a security-sensitive path it'd be a touch tidier to match on literals — e.g. escape the dots, or grep -F -- "$archive" combined with an end-of-line check. Take it or leave it; the layered verification already covers the real threat.

v*) version="${SCRIPT_REF}" ;;
*)
info "Resolving newest release..."
version=$(newest_release) || die "could not resolve the newest release (set AUTHBRIDGE_VERSION=vX.Y.Z)"
;;
esac
fi
info "Release: $version"

Expand Down Expand Up @@ -276,8 +395,24 @@ info "Verifying checksums..."
# checksums.txt can't make verification fail on a file we never fetched.
: > "${tmp}/checksums.filtered"
for archive in "${abctl_tgz}" "${proxy_tgz}"; do
grep -E "[[:space:]]\*?${archive}\$" "${tmp}/checksums.txt" >> "${tmp}/checksums.filtered" \
|| die "checksums.txt has no entry for ${archive} — refusing to install it unverified"
# The name may be preceded by whitespace, sha256sum's binary-mode "*", or a
# path component: the release workflow runs `sha256sum ./*.tar.gz`, so every
# real line reads "HASH ./abctl_....tar.gz". An earlier version of this
# pattern required the name immediately after whitespace or "*", which matched
# nothing against an actual release and refused every install.
# Anchored to the whole line and to the exact shape our own workflow emits:
# "HASH ./name" (from `cd dist && sha256sum ./*.tar.gz`), with a bare name and
# binary-mode "*" also accepted.
#
# Deliberately NOT any path. sha_check runs from ${tmp}, so a permissive class
# let a crafted entry like "HASH ../name" or "HASH /etc/name" match and be
# verified against a file outside the download directory — passing verification
# for something other than the archive we then extract. Only ./ and a bare name
# are ours, so nothing else is accepted.
archive_re=$(ere_escape "${archive}")
grep -E "^[0-9a-fA-F]+[[:space:]]+\*?(\./)?${archive_re}\$" "${tmp}/checksums.txt" \
>> "${tmp}/checksums.filtered" \
|| die "checksums.txt has no usable entry for ${archive} — refusing to install it unverified"
done
# Both entries present, and exactly the two we asked for.
lines=$(wc -l < "${tmp}/checksums.filtered" | tr -d '[:space:]')
Expand Down
Loading