osbytes

Search

Find posts, projects, and members.

← back to blog

Composer and the hyphen that spilled GITHUB_TOKEN into CI logs

2026-05-13by@osbytes6 min read
#composer #github-actions #php #security #ci-cd #credentials #supply-chain

If you run Composer inside GitHub Actions, today’s advisory is the kind of bug that sounds small until you read the failure mode: a format validation path throws an exception that quotes the full token, Symfony Console prints it to stderr, and ordinary CI log capture does the rest. GitHub assigned CVE-2026-45793 and rated the advisory High (GHSA-f9f8-rm49-7jv2).

The token family in the headline is GITHUB_TOKEN, the short-lived credential Actions injects for API access scoped to the workflow job. GitHub has been rolling out a newer structured format whose payloads can include hyphens (the same base64url alphabet that uses - and _ instead of + and /). Composer validates “GitHub OAuth tokens” with a regex that only allows [.A-Za-z0-9_]. A hyphen fails validation. The failure is not a quiet log line; it is an UnexpectedValueException whose message embeds the token string verbatim.

Why this is not “just an edge-case PAT”

The advisory calls out a common precondition: widely used Actions such as shivammathur/setup-php can auto-register GITHUB_TOKEN into Composer’s global auth.json. You do not need a maintainer to paste secrets by hand for the leak to trigger; the platform wires credentials in because that is how PHP ecosystems expect GitHub-hosted packages to authenticate.

In the ordinary case exposure stays narrow enough that advisory text says so plainly. A workflow GITHUB_TOKEN is repository-scoped and typically expires when the job ends (GitHub-hosted runners cap lifetime at hours; self-hosted runners stretch that window). Logs that outlive the job still hurt if they ship to Splunk or a shared triage inbox. The same primitive also applies, the advisory notes, to any future credential that fails validation for any reason, not only the hyphen case that bit ghs_*-shaped tokens today.

Three failures that compound into a secret in Splunk

Upstream’s write-up separates the mechanics without drama:

  1. Interpolation: rejected tokens are included character-for-character in the exception message from Composer\IO\BaseIO::loadConfiguration() (see the published snippet in the advisory).
  2. Rendering path: the exception surfaces through Symfony Console’s default error renderer to stderr, which CI systems treat as first-class telemetry alongside stdout.
  3. Masking gap: GitHub Actions’ built-in secret masker matches registered secrets as exact substrings. When Symfony wraps the message (file and line prefixes, ANSI styling, wrapping), the masker may not recognize the token inside the framing, so the plaintext can land in the log anyway.

That third point is the one worth taking to your platform team. Secret scanning on push is a different animal from redaction under error formatting. If your mental model was “GitHub masks GITHUB_TOKEN everywhere,” this advisory is a useful correction: masking assumes stable string contexts.

What to do

Upgrade Composer to a patched release: 2.9.8, 2.2.28, or 1.10.28, depending on your supported line (GHSA-f9f8-rm49-7jv2). If you cannot bump immediately, treat failing Composer invocations in CI like any other credential exposure: rotate anything that might have been echoed, tighten log retention where you can, and inspect historical runs for the exception shape once you know your formatter.

Today's failure mode is log disclosure after a client-side validation mistake, not rogue registry writes through npm. If you want the latter story (Actions, OIDC, publish tokens), it is spelled out here: /blog/dirty-frag-stable-kernels-google-ai-exploit-tracker-tanstack-npm-supply-chain-compromise.

Sources