Security: skill installer leaks GITHUB_TOKEN to any redirect target

Author: Chirag6722Created Aug 12, 2026Updated Aug 12, 2026

Problem

skill-installer/scripts/github_utils.py attaches the user's GitHub token to every request and then hands the request to urllib.request.urlopen, which follows redirects automatically:

python
def github_request(url: str, user_agent: str) -> bytes:
    headers = {"User-Agent": user_agent}
    token = os.environ.get("GITHUB_TOKEN") or os.environ.get("GH_TOKEN")
    if token:
        headers["Authorization"] = f"token {token}"
    req = urllib.request.Request(url, headers=headers)
    with urllib.request.urlopen(req) as resp:   # follows redirects
        return resp.read()

urllib's HTTPRedirectHandler.redirect_request copies every header from the original request onto the redirect request, stripping only Content-Length and Content-Type. Authorization is carried across the redirect, including to a different host and including a downgrade to plain http://.

Reproduction

Two local servers; the first redirects to the second:

$ python repro.py
Header seen by the *redirect target* host: {'auth': 'token ghp_SECRET_TOKEN', 'host': '127.0.0.1:53585'}

The token was minted for github.com and is sent verbatim to an unrelated host.

Why this is reachable

Both installer entry points route through this helper:

  • install-skill-from-github.py fetches https://codeload.github.com/{owner}/{repo}/zip/{ref} and https://api.github.com/repos/{owner}/{repo}, with owner, repo and ref taken from user-supplied --repo/--url/--ref.
  • list-curated-skills.py fetches https://api.github.com/repos/{repo}/contents/{path}?ref={ref} from --repo/--path/--ref.

The README tells users to run these scripts against arbitrary third-party repos, and the catalog itself lists installer one-liners pointing at other people's repositories. A user follows an install command from a README or a skill listing, and any redirect served on that path — an attacker-controlled host, a shortener, a compromised or moved endpoint — receives a token that typically carries repo scope. The token is exfiltrated silently; the install still succeeds, so nothing looks wrong.

There is also no scheme check: github_request will happily send the Authorization header over http://, in cleartext.

Root cause

Authentication is treated as a property of the request object rather than of the destination host. Once the header is set, urllib propagates it wherever the server points, and nothing in the helper re-evaluates whether the new destination is entitled to the credential.

Proposed solution

Scope the credential to trusted GitHub hosts and re-check on every hop:

  1. Send the Authorization header only when the target host is GitHub (github.com, githubusercontent.com, and their subdomains) and the scheme is https. GitHub's archive downloads legitimately redirect to codeload.github.com / objects.githubusercontent.com, so private-repo installs keep working.
  2. Install a custom HTTPRedirectHandler that strips Authorization whenever the redirect target fails that check, and use an opener built with it instead of the module-level urlopen.
  3. Reject non-https URLs outright rather than sending credentials in cleartext.
  4. Add a request timeout — the current call can hang indefinitely on an unresponsive host.
  5. Cover it with tests: a local redirect chain asserting the second host receives no Authorization header, plus unit tests for the host/scheme rules.

Happy to open a PR.

Source: composio-community/awesome-codex-skills