Skip to content

fix(security): send GITHUB_TOKEN only to GitHub's own hosts - #63

Merged
aledbf merged 1 commit into
mainfrom
github-token-host
Nov 12, 2026
Merged

aledbf merged 1 commit into
mainfrom
github-token-host

Conversation

@aledbf

@aledbf aledbf commented Nov 12, 2026

Copy link
Copy Markdown
Member

Problem. A Feature given as a tarball URL got Authorization: Bearer $GITHUB_TOKEN whenever the URL started with https://github.com or https://api.github.com. So these all received the token:

  • https://github.com.attacker.example/devcontainer-feature-x.tgz
  • https://[email protected]/… (userinfo)
  • https://api.github.com.evil/…

Anyone who can put a Feature URL in a devcontainer.json, or in a Feature's dependsOn, could collect the token.

Fix. isGitHubTarballURI parses the URL and requires https, no userinfo, and a host equal to github.com or api.github.com. Case, one trailing dot and any port are allowed: they name the same host, and TLS binds the token to GitHub's certificate. TS normalizes with new URL(...), which lowercases the host. Before this change https://GitHub.com/... got no token in Go but did in TS; now both send it.

Divergence. TS v0.90.0 getRequestHeaders (containerFeaturesConfiguration.ts:398-419) has the same prefix check and leaks the token the same way. This is a deliberate, documented divergence (docs/DIVERGENCES.md, "Deliberate divergences"). For real GitHub hosts the output is identical to TS: same header, same Using environment GITHUB_TOKEN. / No environment GITHUB_TOKEN available. lines.

Tests (all fail on main)

  • TestIsGitHubTarballURI: look-alikes, userinfo, a backslash trick, mixed case, trailing dot, :443, http://, no scheme.
  • TestFeatureRequestHeadersSendTheTokenOnlyToGitHub: header and log lines for real and look-alike hosts, with and without a token.
  • TestDownloadFeatureTarballKeepsTheTokenFromALookAlikeURL: a TLS server reached through https://[email protected]:<port>/… receives no token.

No parity case can compare this with TS without leaking a token.

Validation (local)

  • task lint, task vuln: pass.
  • task test:race with TMPDIR outside /tmp: pass.
  • task parity:contract: only the 2 read-configuration.features-configuration-local-feature-* cases fail, as on main on this machine (/tmp/.git).

🤖 Generated with Claude Code

A Feature given as a tarball URL got `Authorization: Bearer
$GITHUB_TOKEN` whenever its URL started with "https://github.com" or
"https://api.github.com", so github.com.attacker.example,
[email protected] and api.github.com.evil all received the
token. Anyone who could put a Feature URL in a devcontainer.json, or
in a Feature's dependsOn, could collect it.

The token now goes only to an https URL with no userinfo whose host is
github.com or api.github.com, ignoring case and one trailing dot, on
any port. The reference CLI has the same prefix check, so this is a
deliberate divergence, recorded in docs/DIVERGENCES.md and pinned by
unit tests, including one where a TLS server reached through a
userinfo look-alike receives no token.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@aledbf
aledbf merged commit d258b90 into main Nov 12, 2026
21 of 23 checks passed
@aledbf
aledbf deleted the github-token-host branch November 12, 2026 23:25
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