Skip to content

Instantly share code, notes, and snippets.

@kristovatlas
Last active September 8, 2026 16:23
Show Gist options
  • Select an option

  • Save kristovatlas/544b2d7a8314cf21c67e33b5a081695a to your computer and use it in GitHub Desktop.

Select an option

Save kristovatlas/544b2d7a8314cf21c67e33b5a081695a to your computer and use it in GitHub Desktop.
Install Claude Code based on known-good hash instead of curl-bashing; Install Codex with Socket.dev Firewall
#!/usr/bin/env bash
# install-claude-verified.sh — download and install Claude Code with full
# signature verification, no unauthenticated bootstrap script.
#
# Usage: ./install-claude-verified.sh [VERSION]
# VERSION defaults to whatever $REPO/latest reports.
# Requires: curl, gpg, jq, sha256sum
# Trust anchor: the pinned fingerprint below (from code.claude.com/docs/en/setup).
#
# Template tip: vendor the key file into your image at $VENDORED_KEY and this
# script will use it instead of fetching the key from the same origin as the
# binaries.
set -euo pipefail
REPO="https://downloads.claude.ai/claude-code-releases"
KEY_URL="https://downloads.claude.ai/keys/claude-code.asc"
PINNED_FPR="31DDDE24DDFAB679F42D7BD2BAA929FF1A7ECACE"
VENDORED_KEY="${VENDORED_KEY:-/opt/provision/claude-code.asc}"
for cmd in curl gpg jq sha256sum; do
command -v "$cmd" >/dev/null || { echo "missing dependency: $cmd" >&2; exit 1; }
done
WORK=$(mktemp -d)
export GNUPGHOME="$WORK/gnupg" # isolated keyring; never touches the user's
mkdir -p -m 700 "$GNUPGHOME"
trap 'rm -rf "$WORK"' EXIT
# --- 1. Obtain the release signing key ---------------------------------------
if [ -f "$VENDORED_KEY" ]; then
cp "$VENDORED_KEY" "$WORK/key.asc"
echo "using vendored key: $VENDORED_KEY"
else
echo "vendored key not found; fetching from $KEY_URL"
curl -fsSL "$KEY_URL" -o "$WORK/key.asc"
fi
# --- 2. Pin the fingerprint BEFORE trusting the key --------------------------
ACTUAL_FPR=$(gpg --with-colons --import-options show-only --import "$WORK/key.asc" \
| awk -F: '/^fpr:/ {print $10; exit}')
if [ "$ACTUAL_FPR" != "$PINNED_FPR" ]; then
echo "FATAL: key fingerprint mismatch" >&2
echo " expected: $PINNED_FPR" >&2
echo " got: $ACTUAL_FPR" >&2
exit 1
fi
gpg --quiet --import "$WORK/key.asc"
# --- 3. Resolve version -------------------------------------------------------
VERSION="${1:-$(curl -fsSL "$REPO/latest")}"
[[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+ ]] || {
echo "FATAL: '$VERSION' is not a valid version string" >&2; exit 1; }
echo "version: $VERSION (manifest signatures exist for >= 2.1.89)"
# --- 4. Fetch and verify the signed manifest ----------------------------------
curl -fsSL "$REPO/$VERSION/manifest.json" -o "$WORK/manifest.json"
curl -fsSL "$REPO/$VERSION/manifest.json.sig" -o "$WORK/manifest.json.sig"
# Require not just "a good signature" but a good signature FROM THE PINNED KEY.
gpg --status-fd 1 --verify "$WORK/manifest.json.sig" "$WORK/manifest.json" 2>/dev/null \
| grep -q "VALIDSIG $PINNED_FPR" \
|| { echo "FATAL: manifest signature invalid or from wrong key" >&2; exit 1; }
echo "manifest signature: OK ($PINNED_FPR)"
# --- 5. Detect platform (matches install.sh's logic) --------------------------
case "$(uname -m)" in
x86_64|amd64) arch="x64" ;;
arm64|aarch64) arch="arm64" ;;
*) echo "unsupported arch: $(uname -m)" >&2; exit 1 ;;
esac
if ldd /bin/ls 2>&1 | grep -q musl; then
platform="linux-$arch-musl"
else
platform="linux-$arch"
fi
# --- 6. Download binary and verify against the signed manifest ----------------
CHECKSUM=$(jq -re ".platforms[\"$platform\"].checksum" "$WORK/manifest.json")
[[ "$CHECKSUM" =~ ^[a-f0-9]{64}$ ]] || {
echo "FATAL: no checksum for $platform in manifest" >&2; exit 1; }
BIN="$WORK/claude-$VERSION-$platform"
curl -fsSL "$REPO/$VERSION/$platform/claude" -o "$BIN"
ACTUAL=$(sha256sum "$BIN" | cut -d' ' -f1)
if [ "$ACTUAL" != "$CHECKSUM" ]; then
echo "FATAL: binary checksum mismatch" >&2
echo " manifest: $CHECKSUM" >&2
echo " actual: $ACTUAL" >&2
exit 1
fi
echo "binary checksum: OK ($platform)"
# --- 7. Install ----------------------------------------------------------------
chmod +x "$BIN"
"$BIN" install "$VERSION"
echo "installed Claude Code $VERSION (verified against signed manifest)"
#!/usr/bin/env bash
set -euo pipefail
# ── Helpers ──────────────────────────────────────────────────────
info() { printf '\033[1;34m[INFO]\033[0m %s\n' "$*"; }
ok() { printf '\033[1;32m[OK]\033[0m %s\n' "$*"; }
err() { printf '\033[1;31m[ERROR]\033[0m %s\n' "$*" >&2; }
# ── Pre-flight: require npm ──────────────────────────────────────
if ! command -v npm &>/dev/null; then
err "npm is not installed. Please install Node.js/npm first."
exit 1
fi
info "Node $(node -v) / npm $(npm -v)"
# ── Check for Socket Firewall (sfw) ─────────────────────────────
if command -v sfw &>/dev/null; then
ok "Socket Firewall is already installed: $(sfw --version 2>/dev/null || echo 'version unknown')"
else
info "Socket Firewall not found. Installing globally via npm..."
npm i -g sfw
# Verify the install succeeded
if command -v sfw &>/dev/null; then
ok "Socket Firewall installed successfully."
else
err "sfw command not found after install. Check that npm's global bin directory is in your PATH."
err "Hint: npm config get prefix → add <prefix>/bin to your PATH"
exit 1
fi
fi
# ── Clear npm cache (recommended by Socket before first use) ─────
info "Clearing npm cache so sfw can intercept all network requests..."
npm cache clean --force
# ── Run the guarded install ──────────────────────────────────────
info "Running: sfw npm i -g @openai/codex"
sfw npm i -g @openai/codex
ok "Done! @openai/codex installed with Socket Firewall protection."
curl -fsSL https://code.kimi.com/kimi-code/install.sh -o install.sh && checksum="$(sha256sum install.sh | awk '{print $1}')"; if [ "$checksum" = "638927825e96825edbb563de5e0cb06f8a0551c53e026ade8b717b0f25cb83d2" ]; then echo "Checksum matched."; echo "Proceed with installation? (Y/n)"; read response; if [ "$response" = "Y" ] || [ "$response" = "y" ] || [ -z "$response" ]; then bash install.sh && rm -f install.sh; else echo "Installation aborted."; rm -f install.sh; exit 0; fi; else echo "Error: Checksum failed"; exit 1; fi
#!/usr/bin/env bash
set -euo pipefail
OPENCODE_PKG="opencode-ai" # npm package for SST's opencode CLI — verify this matches what you intend before relying on it
ZAI_PKG="@z_ai/coding-helper"
# ── Helpers ──────────────────────────────────────────────────────
info() { printf '\033[1;34m[INFO]\033[0m %s\n' "$*"; }
ok() { printf '\033[1;32m[OK]\033[0m %s\n' "$*"; }
err() { printf '\033[1;31m[ERROR]\033[0m %s\n' "$*" >&2; }
# ── Pre-flight: require npm ──────────────────────────────────────
if ! command -v npm &>/dev/null; then
err "npm is not installed. Please install Node.js/npm first."
exit 1
fi
info "Node $(node -v) / npm $(npm -v)"
# ── Check for Socket Firewall (sfw) ─────────────────────────────
if command -v sfw &>/dev/null; then
ok "Socket Firewall is already installed: $(sfw --version 2>/dev/null || echo 'version unknown')"
else
info "Socket Firewall not found. Installing globally via npm..."
npm i -g sfw
if command -v sfw &>/dev/null; then
ok "Socket Firewall installed successfully."
else
err "sfw command not found after install. Check that npm's global bin directory is in your PATH."
err "Hint: npm config get prefix → add <prefix>/bin to your PATH"
exit 1
fi
fi
# ── Clear npm cache (recommended by Socket before first use) ─────
# npx resolves through the same cache, so a stale cache means sfw
# never sees the fetch.
info "Clearing npm cache so sfw can intercept all network requests..."
npm cache clean --force
# ── Install a package globally under the firewall ────────────────
install_with_sfw() {
local pkg="$1"
info "Running: sfw npm i -g $pkg"
sfw npm i -g "$pkg"
ok "$pkg installed with Socket Firewall protection."
}
# ── Fetch opencode first, under the firewall ──────────────────────
install_with_sfw "$OPENCODE_PKG"
# ── Fetch zai under the firewall ──────────────────────────────────
install_with_sfw "$ZAI_PKG"
# ── Install shell aliases so future launches stay gated ───────────
# Without these, typing `npx <pkg>` out of habit runs the TUI with
# unrestricted network access — the firewall only ever covers the
# command it directly wraps. The aliases make the safe form the default.
declare -A ALIASES=(
[opencode]="sfw npx --yes ${OPENCODE_PKG}"
[zai-helper]="sfw npx --yes ${ZAI_PKG}"
)
# Write to whichever rc files actually exist; fall back to ~/.bashrc.
RC_FILES=()
for rc in "$HOME/.bashrc" "$HOME/.zshrc"; do
[[ -f "$rc" ]] && RC_FILES+=("$rc")
done
[[ ${#RC_FILES[@]} -eq 0 ]] && RC_FILES=("$HOME/.bashrc")
for rc in "${RC_FILES[@]}"; do
for alias_name in "${!ALIASES[@]}"; do
alias_line="alias ${alias_name}='${ALIASES[$alias_name]}'"
# Match on the alias name, not the whole line, so an older version
# of the alias gets reported instead of silently duplicated.
if grep -q "alias ${alias_name}=" "$rc" 2>/dev/null; then
info "An alias named '${alias_name}' already exists in ${rc} — leaving it alone."
else
{
printf '\n# Added by install-opencode-zai-sfw.sh — run the CLI behind Socket Firewall\n'
printf '%s\n' "$alias_line"
} >> "$rc"
ok "Added '${alias_name}' alias to ${rc}"
fi
done
done
# Aliases are read at shell startup, so these are not live yet in the
# caller's session.
info "Run 'source ${RC_FILES[0]}' or open a new shell to use: opencode, zai-helper"
# ── Launch zai, still gated ───────────────────────────────────────
info "Launching: sfw npx $ZAI_PKG"
exec sfw npx --yes "$ZAI_PKG" "$@"
curl -fsSL https://claude.ai/install.sh -o install.sh && checksum="$(sha256sum install.sh | awk '{print $1}')"
if [ "$checksum" = "3a68d3406cf674e17bed1733a4dcf37805e2e47d87417700007d7e1aa766a944" ]; then
echo "Checksum matched."
echo "Proceed with installation? (Y/n)"
read response
if [ "$response" = "Y" ] || [ "$response" = "y" ] || [ -z "$response" ]; then bash install.sh && rm -f install.sh; else
echo "Installation aborted."
rm -f install.sh
exit 0
fi
else
echo "Error: Checksum failed"
exit 1
fi
curl -fsSL https://claude.ai/install.sh -o install.sh && checksum="$(sha256sum install.sh | awk '{print $1}')"; if [ "$checksum" = "3a68d3406cf674e17bed1733a4dcf37805e2e47d87417700007d7e1aa766a944" ]; then echo "Checksum matched."; echo "Proceed with installation? (Y/n)"; read response; if [ "$response" = "Y" ] || [ "$response" = "y" ] || [ -z "$response" ]; then bash install.sh && rm -f install.sh; else echo "Installation aborted."; rm -f install.sh; exit 0; fi; else echo "Error: Checksum failed"; exit 1; fi
#!/usr/bin/env bash
set -euo pipefail
OPENCODE_PKG="opencode-ai" # npm package for SST's opencode CLI
# ── Helpers ──────────────────────────────────────────────────────
info() { printf '\033[1;34m[INFO]\033[0m %s\n' "$*"; }
ok() { printf '\033[1;32m[OK]\033[0m %s\n' "$*"; }
err() { printf '\033[1;31m[ERROR]\033[0m %s\n' "$*" >&2; }
# ── Pre-flight: require npm ──────────────────────────────────────
if ! command -v npm &>/dev/null; then
err "npm is not installed. Please install Node.js/npm first."
exit 1
fi
info "Node $(node -v) / npm $(npm -v)"
# ── Check for Socket Firewall (sfw) ─────────────────────────────
if command -v sfw &>/dev/null; then
ok "Socket Firewall is already installed: $(sfw --version 2>/dev/null || echo 'version unknown')"
else
info "Socket Firewall not found. Installing globally via npm..."
npm i -g sfw
if command -v sfw &>/dev/null; then
ok "Socket Firewall installed successfully."
else
err "sfw command not found after install. Check that npm's global bin directory is in your PATH."
err "Hint: npm config get prefix → add <prefix>/bin to your PATH"
exit 1
fi
fi
# ── Clear npm cache (recommended by Socket before first use) ─────
# npm resolves through the same cache, so a stale cache means sfw
# never sees the fetch.
info "Clearing npm cache so sfw can intercept all network requests..."
npm cache clean --force
# ── Install opencode globally, under the firewall ─────────────────
# Note: sfw gates package-manager traffic (installs, updates). It does
# not sandbox the runtime network activity of the installed app, so a
# global install gated here is just as protected as launching via
# `sfw npx` every time — and starts faster.
info "Running: sfw npm i -g $OPENCODE_PKG"
sfw npm i -g "$OPENCODE_PKG"
ok "$OPENCODE_PKG installed with Socket Firewall protection."
if ! command -v opencode &>/dev/null; then
err "opencode command not found after install. Check your PATH (npm config get prefix → <prefix>/bin)."
exit 1
fi
# ── Gate future updates too ───────────────────────────────────────
# Updating is the moment new package code enters your machine, so it
# should go through sfw just like the install did.
UPDATE_ALIAS="alias opencode-update='sfw npm update -g ${OPENCODE_PKG}'"
RC_FILES=()
for rc in "$HOME/.bashrc" "$HOME/.zshrc"; do
[[ -f "$rc" ]] && RC_FILES+=("$rc")
done
[[ ${#RC_FILES[@]} -eq 0 ]] && RC_FILES=("$HOME/.bashrc")
for rc in "${RC_FILES[@]}"; do
if grep -q "alias opencode-update=" "$rc" 2>/dev/null; then
info "An 'opencode-update' alias already exists in ${rc} — leaving it alone."
else
{
printf '\n# Added by install-opencode-sfw.sh — update opencode behind Socket Firewall\n'
printf '%s\n' "$UPDATE_ALIAS"
} >> "$rc"
ok "Added 'opencode-update' alias to ${rc}"
fi
done
printf '\n'
ok "opencode is installed."
printf '\n'
info "NEXT STEP — connect a model provider (both are built into opencode):"
info " opencode auth login"
info " • pick 'Z.AI Coding Plan' to use your GLM Coding Plan subscription"
info " • run it again and pick 'OpenRouter' to add your OpenRouter API key"
info " Both can be connected at once; switch models anytime with /models in the TUI."
printf '\n'
info "Other commands:"
info " opencode launch the TUI"
info " opencode-update update opencode behind Socket Firewall (open a new shell first)"
@kristovatlas

Copy link
Copy Markdown
Author

Today I'm adding the Kimi Code CLI install.sh.
Reviews: Opus 4.8 Max for malicious intent / modification:

Verdict: I found no malicious intent and no signs of tampering. This is a standard trust-on-first-use installer, and actually a more careful-than-average one. The single most sensitive behavior — that it will rename/delete an existing kimi command during migration — is legitimate but worth knowing about (details below).

What the script actually does: detects your OS/arch, resolves a version (pinned or "latest"), downloads a manifest.json and the matching binary from code.kimi.com, verifies the binary's SHA-256 against the manifest, installs it to ~/.kimi-code/bin/kimi, adds that to your PATH, and migrates any old Python kimi-cli shims. That flow matches the official docs exactly, and code.kimi.com/kimi-code/install.sh is the real documented install command from Moonshot AI's own docs and GitHub org — not a look-alike domain.

The things I specifically checked for and didn't find:

No hidden or secondary endpoints. Every network call goes to the one code.kimi.com base URL. No IP addresses, no pastebin/gist/webhook, no base64 -d, no eval, no obfuscation of any kind.
No data exfiltration. Nothing reads SSH keys, cloud credentials, env vars, or browser data. Every network operation is an outbound GET to download; nothing is POSTed anywhere.
Integrity check present. It fetches a checksum, validates its format (^[a-f0-9]{64}$), and refuses to install on mismatch. Many sketchy installers skip this entirely.
No silent privilege escalation. The script never calls sudo itself; it defaults to writing under $HOME. sudo only appears in the optional usage examples.
Good hygiene: set -euo pipefail, a mktemp working dir, and a cleanup trap on exit.

The riskiest part of any installer is anything that deletes or renames files, so I scrutinized the legacy-shim migration (lines ~227–469) closely. It's tightly fenced: it only touches files literally named kimi, only if they're executable, only if the first 4 KB contains the marker kimi_cli, and it explicitly refuses to classify anything inside its own install directory. It resolves symlinks only to sniff the marker but acts on the shim path itself (so it renames/removes the symlink, never the target), checks directory writability before acting, uses mv -- / rm -- to prevent option injection, and always preserves a kimi-legacy fallback. The blast radius is effectively just leftover copies of the old Python CLI. This matches the product's real history — Kimi Code CLI used to be a Python tool installed via uv, and it's now shipping as a single native binary, so cleaning up the old shims is exactly what you'd expect.

Two honest caveats — these are limitations of the approach, not evidence of malice:

Trust-on-first-use. The checksum is served from the same origin as the binary, so it protects against CDN/transport corruption but not against a compromised origin server (an attacker who controlled code.kimi.com would control both). This is inherent to every curl | bash installer, not something specific to this one. You did the right thing saving it and reviewing it rather than piping straight to bash.
This covers the installer, not the binary. The script fetches and installs a closed-source binary; the actual agent code lives there and is out of scope for a shell-script review.

One behavior to be aware of before you run it: the migration will rename your first existing kimi (old Python CLI) to kimi-legacy and delete any duplicate old shims further down your PATH. That's intentional and it keeps a fallback, but if you rely on the old kimi command, know that it's moving.

If you want byte-level certainty that your copy matches what's currently served, you can diff it yourself:

curl -fsSL https://code.kimi.com/kimi-code/install.sh | diff - kimi_install.sh

Two notes on that: a difference isn't necessarily sinister (the hosted script gets updated, so version drift is expected), and I couldn't run this diff from here — my sandbox can't reach kimi.com, and the fetch returns the script as a binary file type rather than text. If you'd prefer to route around direct execution entirely, the documented npm install -g @moonshot-ai/kimi-code path is an alternative that goes through the npm registry's own integrity checks.

virustotal.com: 0/61, but "Matches rule Usage Of Web Request Commands And Cmdlets" and "Matches rule Curl Usage on Linux by Nasreddine Bencherchali (Nextron Systems) at Sigma Integrated Rule Set (GitHub)"
638927825e96825edbb563de5e0cb06f8a0551c53e026ade8b717b0f25cb83d2

@kristovatlas

Copy link
Copy Markdown
Author

Latest CC review:

Review the change to this script for malicious intent:

Nothing in this fragment looks malicious. Walking through the parts that would matter:

The two curl lines are inert. Both sit inside echo string arguments going to stderr — they're guidance text, never executed. The URL is https://claude.ai/install.sh: correct domain spelling, no typosquat, no lookalike TLD, HTTPS. The # pinned-dep-allow comments accurately describe what they're suppressing, which is a good sign rather than a red flag. One thing to confirm in review: that the lint rule those comments suppress is line-scoped, so a suppression can't accidentally cover a genuinely executed fetch elsewhere in the file.

The guard's logic is sound and its direction is privilege-reducing. It fires only when euid is 0 and SUDO_USER is set to a non-root user, so plain root (containers, CI, root-only boxes) is untouched, as the comment claims. ${SUDO_USER:-} and ${CLAUDE_INSTALL_ALLOW_SUDO:-} are properly defaulted for set -u. Nothing here writes files, reads credentials, or touches the network. A change that refuses root is the opposite shape from what an attacker inserts.

Three things worth flagging as ordinary review comments:

  • The suggested override command probably doesn't work on default sudoers. sudo CLAUDE_INSTALL_ALLOW_SUDO=1 bash sets the variable via sudo, which most default configs reject ("you are not allowed to set the following environment variables") unless the sudoers rule carries SETENV or the name is in env_keep. sudo env CLAUDE_INSTALL_ALLOW_SUDO=1 bash is the portable form. Users hitting a hard exit 1 and then a second failure on the documented escape hatch is a bad path.
  • The detection has a known hole. sudo -i or sudo su - resets the environment, dropping SUDO_USER, so a user who takes a root login shell first sails past the guard and installs into /root — exactly the footgun being defended against. Probably acceptable, but it means the check catches the common form, not the class.
  • It's a breaking change for anyone whose automation currently does curl … | sudo bash. Those pipelines will start failing at this new exit 1. Worth a changelog note.

The snippet is cut off at both ends, so I can't see the check that precedes it or confirm the exit 1 that presumably follows these messages. If you want a verdict on the whole change, paste the full diff — in particular anything downstream that resolves $HOME or does the actual download and chmod, since that's where an installer's real risk lives.

Full script confirms it: no malicious intent, and the change is defensive rather than the reverse. Summary of the review, plus a correction to something I said earlier.

The sudo guard. Placement is good — it sits after argument validation and before any network activity or filesystem writes, so it fails early and cheaply. The logic matches the comment: it fires only on euid 0 with a non-root SUDO_USER, leaving plain root untouched. Nothing in the block touches the network, credentials, or disk. One correction to my earlier note: I said the ${SUDO_USER:-} defaults were "properly defaulted for set -u" — the script only sets set -e, so those defaults aren't load-bearing. Harmless either way, but I overstated it. The exit 1 I couldn't see is present and correct.

The rest of the script checks out. Both curl invocations in the error text remain inert (echo to stderr). The real fetches go to downloads.claude.ai over HTTPS, no IP literals or lookalike hosts. The trust chain is sound: binary verified against a SHA256 from manifest.json on the same origin, checksum format anchored to ^[a-f0-9]{64}$, mismatch deletes the file and exits nonzero, chmod +x happens only after verification, and the download is removed afterward. No eval, no base64 blobs, no shell-rc writes, no persistence, no env exfiltration. TARGET reaches the binary as a single quoted argv element and its regex forbids a leading -, so no option or shell injection.

Three defense-in-depth notes, none of them about the change itself:

  • The version regex is anchored only at the front (^[0-9]+\.[0-9]+\.[0-9]+), unlike the fully anchored TARGET check. $version then flows into binary_path="$DOWNLOAD_DIR/claude-$version-$platform", so server-controlled content like 1.2.3/../../../../tmp/x would place and execute the file outside $DOWNLOAD_DIR. It only matters if downloads.claude.ai itself is serving bad content — in which case you have worse problems — but adding (-[^[:space:]]+)?$ costs nothing.
  • No set -o pipefail. It happens to be safe here: every pipeline that could swallow a failure (shasum | cut, jq) yields an empty string that then fails a subsequent check. That's fail-safe by accident rather than by construction.
  • The jq-less manifest parser is brittle but not exploitable. [^}]* can't cross the closing brace of the platform's object, so it can't bind a different platform's checksum, and $platform interpolates from a fixed set of uname-derived literals. It does silently depend on checksum appearing after the platform key.

My two substantive comments on the change stand: sudo VAR=1 bash is rejected by default sudoers without SETENV, so the documented escape hatch will likely fail for the people who need it (sudo env CLAUDE_INSTALL_ALLOW_SUDO=1 bash is portable), and sudo -i/sudo su - drops SUDO_USER and walks straight past the guard into /root.

@kristovatlas

Copy link
Copy Markdown
Author

updated zai script to install opencod first since zai has no TUI

@kristovatlas

Copy link
Copy Markdown
Author

Fable 5 review of latest claude code install.sh:

Verdict: clean. I diffed it against the live official installer — https://claude.ai/install.sh currently redirects to downloads.claude.ai/claude-code-releases/bootstrap.sh, and your uploaded file matches that content line for line, including the comments and the internal pinned-dep-allow lint annotations. This is Anthropic's own bootstrap installer, not a modified copy.

What I checked and why it passes:

Network behavior. The only endpoints contacted are https://downloads.claude.ai/claude-code-releases/... (version file, manifests, binary). Nothing is sent outbound beyond the GET requests themselves — no telemetry, no exfiltration of env vars or files, no eval, no base64/obfuscated payloads, no writes outside $HOME/.claude (plus whatever claude install itself does).

Input handling. The one user input, $TARGET, is validated against a strict regex (stable|latest|semver) before ever being used, and it's quoted when passed to the binary. No injection surface.

Integrity checks — including the two spots that look sketchy but aren't. Both download paths (zstd-compressed and plain) require a SHA-256 match against the manifest before the binary runs.

The zstd -d ... | head -c "$size" > "$binary_path" || true line looks like a suppressed failure, but it fails open into the fallback path, not into execution: if the decompressed output doesn't match the manifest checksum, verified stays false and the script re-downloads the plain binary with a hard checksum check that exits on mismatch. A tampered file can never reach chmod +x + execution.
The head -c "$size" truncation is similarly bounded by the checksum verification right after it, so it can't be used to smuggle or trim content undetected.

Execution. Yes, it downloads and runs an arbitrary binary ("$binary_path" install) — that's the entire purpose of a bootstrap installer, and it's gated behind the checksum. The sudo guard, Rosetta/musl detection, stty sane recovery, and OOM-137 messaging are all benign housekeeping.

One real limitation worth knowing, though it's a design property of the official installer rather than something surreptitious in this copy: the checksums come from the same server as the binaries, so the verification protects against corruption, truncation, and CDN-level tampering of a single file — not against a fully compromised origin or a TLS MITM with a trusted cert. There's no independent signature (GPG/sigstore) verification. If your threat model includes a compromised downloads.claude.ai, no amount of reading this script helps; short of that, it's safe to run.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment