Skip to content

Authenticate GitHub API metadata requests when a token is available - #173

Merged
BrettKinny merged 3 commits into
mainfrom
feat/github-api-token
Oct 1, 2026
Merged

BrettKinny merged 3 commits into
mainfrom
feat/github-api-token

Conversation

@BrettKinny

Copy link
Copy Markdown
Collaborator

Summary

Unauthenticated GitHub REST calls are limited to 60 requests/hour/IP, so a full setup.sh or sqrbx-update behind a shared NAT or CI can fail with HTTP 403. All in-Box API calls (tool releases, LazyVim / Oh My Zsh / PowerShell lookups in setup.sh) go through _sb_gh_api_get in scripts/lib/tool-lib.sh, which now optionally authenticates.

  • Token source precedence: GH_TOKEN, then GITHUB_TOKEN, then gh auth token. The gh step runs only when gh is installed and already logged in, and only for the default https://api.github.com base. It runs lazily, at most once per shell, with GH_PROMPT_DISABLED=1 and stdin from /dev/null, so it never prompts.
  • Scope: Authorization: Bearer is sent only when the URL is under the configured SB_GITHUB_API_BASE and that base is https://. Artifact downloads never get the header. curl does not forward a custom Authorization header to a different host when following a redirect, and we don't pass --location-trusted.
  • No argv exposure: the header reaches curl as a config here-string on stdin (curl -K - ... <<<'header = "..."'). Tokens must match ^[A-Za-z0-9_.-]+$, so the value can't inject curl config. A malformed token is ignored with a warning that does not print it.
  • No persistence: the token is never logged, never put in error messages, and never written to the metadata cache (the cache still stores only response bodies keyed by URL).
  • HTTP 401: warns, drops the token for the rest of the shell, and retries once without authentication.
  • HTTP 403/429 without a token: the error now suggests GH_TOKEN or gh auth login.
  • Fail-closed metadata, digest and caching behaviour is unchanged.
  • install.sh and install.ps1 are unchanged.

README (Update section) and SECURITY.md (Download trust) are updated.

Tests

New cases in tests/test-tool-lib.sh use a mock curl that records argv and stdin config. They cover:

  • the header is sent when GH_TOKEN is set
  • GITHUB_TOKEN is used when GH_TOKEN is unset
  • no header when no token is set, and gh is not consulted for a custom base
  • the token is absent from curl argv, from output, and from the metadata cache
  • artifact downloads never carry the token
  • no token is sent to a non-HTTPS base
  • HTTP 401 retries once unauthenticated, warns, and drops the token
  • an unauthenticated 403 fails closed and shows the hint
  • the gh auth token fallback is resolved once
  • a malformed token is ignored without being printed

The full host suite (19 files) passes, both with GH_TOKEN unset and with it set.

🤖 Generated with Claude Code

BrettKinny and others added 3 commits October 1, 2026 12:14
Unauthenticated GitHub REST calls are limited to 60 requests per hour per
IP, so a full setup or sqrbx-update on a shared NAT or CI runner can fail
with HTTP 403. _sb_gh_api_get now optionally authenticates using GH_TOKEN,
then GITHUB_TOKEN, then an already-authenticated gh CLI (resolved lazily
once per shell, default API base only, never prompting).

The Authorization header is sent only to the configured HTTPS
SB_GITHUB_API_BASE, passed to curl through a stdin config so the token
never appears in argv, and is never logged or cached. HTTP 401 drops the
token and retries once unauthenticated; an unauthenticated 403/429 now
suggests GH_TOKEN or gh auth login. Fail-closed metadata handling is
unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@BrettKinny
BrettKinny merged commit 695c006 into main Oct 1, 2026
4 checks passed
@BrettKinny
BrettKinny deleted the feat/github-api-token branch October 1, 2026 04:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant