Split tool pinning: keep Dockerfile tier pinned, unpin optional tools#51
Conversation
The Dockerfile tier (delta, yq, xh, glow, gum, starship) stays pinned with SHA256 checksums so docker build remains reproducible. Everything installed by setup.sh or updated by sqrbx-update (opencode, editors, TUIs, zellij, Go, nvm) now tracks the latest upstream release at install time, matching the posture the npm-based AI tools already had. This removes a chunk of maintenance churn (no more version/checksum bumps across half the tool registry), gets users new features without waiting for a squarebox release, and makes the README security story match reality instead of overselling "all tools pinned". Library changes (scripts/lib/tool-lib.sh): - sb_gh_latest_tag: query GitHub releases API - sb_latest_version: strip version_prefix from latest tag - sb_resolve_asset_version: for tools where asset version differs from tag (microsoft/edit), populate SB_ASSET_VERSION from the latest asset - sb_install now accepts "latest" and resolves both on demand Runtime changes: - setup.sh: drop all *_VERSION vars + verify_checksum helper, call sb_install <tool> latest; nvm and Go also fetch latest - squarebox-update.sh: only verify checksums for dockerfile-group tools (via SB_CURRENT_TOOL), drop setup-checksums fetch, remove the now redundant edit_prepare_asset_version helper Build/tooling: - Dockerfile: drop COPY setup-checksums.txt - scripts/update-versions.sh: only touches checksums.txt and Dockerfile ARGs for the dockerfile group - setup-checksums.txt: deleted Docs: - README, SECURITY, CLAUDE, CONTRIBUTING rewritten to clearly describe the two tiers and their respective guarantees https://claude.ai/code/session_01EGUSPwfTKFoiXfjd7LLtm2
There was a problem hiding this comment.
Pull request overview
This PR splits Squarebox tool installation into two trust tiers: a reproducible, checksum-verified Dockerfile tier, and an “optional tools” tier that installs the latest upstream releases at runtime (setup/update), reducing ongoing version/checksum maintenance.
Changes:
- Removed setup-time version pinning + checksum verification for optional tools;
setup.shnow installs most tools viasb_install <tool> latest(and fetches latest for Go/nvm). - Updated
sqrbx-updateto only enforce repo checksums for Dockerfile-tier tools; optional-tier updates track upstream latest without checksum gating. - Refactored
tool-lib.shto supportlatestinstalls (GitHub Releases API) and dynamic asset-version resolution; updated docs to describe the two-tier model.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| setup.sh | Switches optional tool installs to upstream-latest; removes setup-checksums integration. |
| setup-checksums.txt | Deleted (no longer used for optional tier). |
| scripts/lib/tool-lib.sh | Adds latest-version resolution + asset-version resolution; sb_install accepts latest. |
| scripts/squarebox-update.sh | Enforces checksums only for Dockerfile-tier tools; optional tier installs latest without verification. |
| scripts/update-versions.sh | Now only updates Dockerfile-tier versions/checksums + Dockerfile ARGs. |
| Dockerfile | Stops copying setup-checksums.txt into the image. |
| SECURITY.md | Updates trust model to reflect new two-tier guarantees. |
| README.md | Updates security section to describe pinned base-image tier vs latest optional tier. |
| CONTRIBUTING.md | Updates contributor guidance for pinned vs optional tools. |
| CLAUDE.md | Updates tooling/version update instructions to match the new tiers. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| # Some tools publish assets whose embedded version differs from the tag | ||
| # (e.g. microsoft/edit). For tools marked asset_version_from_api: true in | ||
| # tools.yaml, query the latest release, locate an asset matching this | ||
| # architecture, and extract a semver from its filename into SB_ASSET_VERSION. | ||
| sb_resolve_asset_version() { | ||
| local tool="$1" | ||
| [ "$(sb_get "$tool" asset_version_from_api)" = "true" ] || return 0 | ||
| local repo body name ver | ||
| repo=$(sb_get "$tool" repo) | ||
| body=$(curl -fsSL "https://api.github.com/repos/${repo}/releases/latest" 2>/dev/null) || return 1 | ||
| name=$(echo "$body" | jq -r '.assets[].name' | grep -F "${SB_ZARCH}" | head -1) | ||
| [ -z "$name" ] && { echo "Error: no ${SB_ZARCH} asset for ${repo}" >&2; return 1; } | ||
| ver=$(echo "$name" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1) | ||
| [ -z "$ver" ] && { echo "Error: no semver in asset name ${name}" >&2; return 1; } | ||
| SB_ASSET_VERSION="$ver" | ||
| export SB_ASSET_VERSION | ||
| } | ||
|
|
||
| # ── Verification hook (override before calling sb_install) ──────────── | ||
|
|
||
| sb_verify() { :; } | ||
|
|
||
| # ── Install pipeline ───────────────────────────────────────────────── | ||
|
|
||
| sb_install() { | ||
| local tool="$1" version="$2" | ||
| local tool="$1" version="${2:-latest}" | ||
| if [ "$version" = "latest" ]; then | ||
| version=$(sb_latest_version "$tool") || return 1 | ||
| fi | ||
| # Some tools (e.g. microsoft/edit) have an asset version that differs | ||
| # from the tag; sb_resolve_asset_version is a no-op unless the tool | ||
| # is marked asset_version_from_api: true in tools.yaml. | ||
| sb_resolve_asset_version "$tool" || return 1 |
There was a problem hiding this comment.
sb_resolve_asset_version always queries .../releases/latest and sets SB_ASSET_VERSION from the latest assets, regardless of the version passed to sb_install. This makes sb_install edit <specific-version> unreliable: if a newer edit release exists, the tag/version and asset version can diverge and the download URL will 404. Consider passing the requested version (or full tag) into sb_resolve_asset_version and querying the matching release (e.g., by tag) so explicit version installs remain correct.
| local repo body name ver | ||
| repo=$(sb_get "$tool" repo) | ||
| body=$(curl -fsSL "https://api.github.com/repos/${repo}/releases/latest" 2>/dev/null) || return 1 |
There was a problem hiding this comment.
sb_resolve_asset_version returns failure with no diagnostic if the GitHub API request fails (e.g., 403 rate limit / transient network error) because the curl ... || return 1 path emits no message. It would be much easier to troubleshoot setup/update failures if this mirrored sb_gh_latest_tag by capturing HTTP status (and/or curl exit) and printing a specific error before returning non-zero.
| local repo body name ver | |
| repo=$(sb_get "$tool" repo) | |
| body=$(curl -fsSL "https://api.github.com/repos/${repo}/releases/latest" 2>/dev/null) || return 1 | |
| local repo response body status curl_rc name ver | |
| repo=$(sb_get "$tool" repo) | |
| response=$(curl -sSL -w $'\n%{http_code}' "https://api.github.com/repos/${repo}/releases/latest" 2>/dev/null) | |
| curl_rc=$? | |
| if [ "$curl_rc" -ne 0 ]; then | |
| echo "Error: failed to query latest release for ${repo} (curl exit ${curl_rc})" >&2 | |
| return 1 | |
| fi | |
| status=${response##*$'\n'} | |
| body=${response%$'\n'*} | |
| if [ "$status" != "200" ]; then | |
| echo "Error: GitHub API request for ${repo} failed with HTTP ${status}" >&2 | |
| return 1 | |
| fi |
| local response http_code body tag | ||
| response=$(curl -fsSL -w '\n%{http_code}' \ | ||
| "https://api.github.com/repos/${repo}/releases/latest" 2>/dev/null) || true | ||
| http_code=$(echo "$response" | tail -1) | ||
| body=$(echo "$response" | sed '$d') | ||
| if [ "$http_code" != "200" ]; then | ||
| echo "Error: GitHub API returned HTTP ${http_code} for ${repo}" >&2 |
There was a problem hiding this comment.
sb_gh_latest_tag uses curl -f ... || true and then only checks the captured %{http_code}. If curl fails before an HTTP response is produced (DNS/timeout/TLS), http_code can be empty and the resulting error (HTTP for <repo>) is not actionable. Consider capturing curl’s exit status and/or avoiding -f so you can reliably surface the HTTP code/body and provide a clearer message (including a dedicated 403/rate-limit case, which the other scripts currently handle explicitly).
| local response http_code body tag | |
| response=$(curl -fsSL -w '\n%{http_code}' \ | |
| "https://api.github.com/repos/${repo}/releases/latest" 2>/dev/null) || true | |
| http_code=$(echo "$response" | tail -1) | |
| body=$(echo "$response" | sed '$d') | |
| if [ "$http_code" != "200" ]; then | |
| echo "Error: GitHub API returned HTTP ${http_code} for ${repo}" >&2 | |
| local response curl_status http_code body tag message | |
| response=$(curl -sSL -w '\n%{http_code}' \ | |
| "https://api.github.com/repos/${repo}/releases/latest" 2>/dev/null) | |
| curl_status=$? | |
| http_code=$(echo "$response" | tail -1) | |
| body=$(echo "$response" | sed '$d') | |
| if [ "$curl_status" -ne 0 ]; then | |
| echo "Error: failed to fetch latest release for ${repo} (curl exit ${curl_status})" >&2 | |
| return 1 | |
| fi | |
| if [ -z "$http_code" ]; then | |
| echo "Error: GitHub API did not return an HTTP status for ${repo}" >&2 | |
| return 1 | |
| fi | |
| if [ "$http_code" = "403" ]; then | |
| message=$(echo "$body" | jq -r '.message // empty' 2>/dev/null) | |
| if [ -n "$message" ]; then | |
| echo "Error: GitHub API returned HTTP 403 for ${repo}: ${message} (possible rate limit)" >&2 | |
| else | |
| echo "Error: GitHub API returned HTTP 403 for ${repo} (possible rate limit)" >&2 | |
| fi | |
| return 1 | |
| fi | |
| if [ "$http_code" != "200" ]; then | |
| message=$(echo "$body" | jq -r '.message // empty' 2>/dev/null) | |
| if [ -n "$message" ]; then | |
| echo "Error: GitHub API returned HTTP ${http_code} for ${repo}: ${message}" >&2 | |
| else | |
| echo "Error: GitHub API returned HTTP ${http_code} for ${repo}" >&2 | |
| fi |
Three related fixes to the GitHub API call paths in tool-lib.sh: 1. sb_resolve_asset_version now takes the version being installed and queries /releases/tags/<tag> for it, instead of always hitting /releases/latest. Previously, sb_install edit <specific-version> would download by the requested tag but resolve SB_ASSET_VERSION from whatever the latest release happened to be, so a 404 was possible if the latest release was newer than the requested one. None of our current callers hit this (setup.sh uses 'latest' and sqrbx-update passes the just-resolved latest version), but it's a latent bug in a reusable library. 2. Consolidated the GitHub API call logic into a private _sb_gh_api_get helper that reports curl exit codes, missing HTTP status, 403/rate-limit specifically, and surfaces the API's .message field in the error output. sb_gh_latest_tag previously produced "HTTP for <repo>" when curl failed before receiving a response (DNS/timeout/TLS), which was not actionable. 3. sb_resolve_asset_version used to return silently on curl failure. It now prints a diagnostic via the shared helper. Verified: library still loads, error path on a bogus repo now prints a full contextual message including the API's rate-limit text. https://claude.ai/code/session_01EGUSPwfTKFoiXfjd7LLtm2
The Dockerfile tier (delta, yq, xh, glow, gum, starship) stays pinned
with SHA256 checksums so docker build remains reproducible. Everything
installed by setup.sh or updated by sqrbx-update (opencode, editors,
TUIs, zellij, Go, nvm) now tracks the latest upstream release at install
time, matching the posture the npm-based AI tools already had.
This removes a chunk of maintenance churn (no more version/checksum
bumps across half the tool registry), gets users new features without
waiting for a squarebox release, and makes the README security story
match reality instead of overselling "all tools pinned".
Library changes (scripts/lib/tool-lib.sh):
tag (microsoft/edit), populate SB_ASSET_VERSION from the latest asset
Runtime changes:
sb_install latest; nvm and Go also fetch latest
(via SB_CURRENT_TOOL), drop setup-checksums fetch, remove the now
redundant edit_prepare_asset_version helper
Build/tooling:
ARGs for the dockerfile group
Docs:
the two tiers and their respective guarantees
https://claude.ai/code/session_01EGUSPwfTKFoiXfjd7LLtm2