Sansec logo

Composer vulnerability leaks GitHub tokens, threatens PHP supply chain

Sansec

by Sansec Forensics Team

Published in Threat Research − May 13, 2026

A patched Composer flaw (GHSA-f9f8-rm49-7jv2) prints raw GitHub Actions tokens into CI logs whenever a modern token format reaches the package manager. The damage depends on the workflow's trigger and the repository's default token permissions: write-scoped tokens can be raced while the job is still running and used to publish malicious code through Packagist.

Composer vulnerability leaks GitHub tokens, threatens PHP supply chain
Update May 13th: GitHub has temporarily rolled back the new token format rollout. According to the Composer maintainers, that leaves a few days to update Composer in CI before the rollout resumes next week.

Composer maintainers released emergency patches today for a vulnerability that prints raw GitHub tokens into CI job logs. The fix ships in 2.9.8, 2.2.28 (LTS), and 1.10.28 (legacy). Every PHP CI pipeline running an older Composer with shivammathur/setup-php (or any action that auto-registers GITHUB_TOKEN) is exposed.

The leaked credential is the GITHUB_TOKEN from whatever workflow ran Composer. For workflows triggered by push, schedule, or pull_request_target, that token may have write access to the source repository, depending on the repository's default permissions.

How the leak works

Composer validates GitHub OAuth tokens loaded from auth.json against a regex. The regex was added in 2021 and accepts only the characters [.A-Za-z0-9_]. GitHub's token formats at the time contained no hyphens, so every valid token passed.

GitHub later rolled out a new structured token format for GitHub Actions: ghs_<id>_<JWT>. The announcement stated:

This should not impact your existing Actions workflows.

The JWT segment is base64url-encoded, which often includes hyphens. The new tokens fail Composer's regex on the first hyphen they contain.

When validation fails, Composer interpolates the raw token directly into the exception message:

if (!Preg::isMatch('{^[.A-Za-z0-9_]+$}', $token)) {
    throw new \UnexpectedValueException(
        'Your github oauth token for '.$domain.
        ' contains invalid characters: "'.$token.'"'
    );
}

Symfony Console renders the unhandled exception to stderr, dropping the full token into the CI job log.

In CI, the common entry point is shivammathur/setup-php, used by tens of thousands of public workflows. The action writes GITHUB_TOKEN into Composer's global auth.json with no opt-in required, so the next composer invocation triggers the failed validation and dumps the token.

Why GitHub's secret masking failed

GitHub Actions scans each log line for the registered secret bytes and replaces matches with ***. The matcher only catches contiguous copies of the token.

Symfony Console's exception renderer interleaves ANSI color escape sequences through the output and wraps long lines at the terminal width, so the token never appears contiguously in a single write. In archived runs the boxed ##[error] annotation shows ***, while the raw stderr line directly below it prints the token in full.

What the token can do

The leaked token's permissions depend on the repository's GITHUB_TOKEN default. GitHub flipped that default to read-only on February 2, 2023, but only for newly created organizations and personal-account repositories. The announcement is explicit:

This change will not impact any existing enterprises, organizations or repositories.

Any project predating the change continues to issue read/write tokens unless an admin has manually tightened the setting.

Exposure window

GitHub's documentation states the GITHUB_TOKEN "expires when the job finishes or after its effective maximum lifetime." The Composer security advisory repeats the claim, describing tokens that "expire when the job ends."

The leaked credential is a JWT with a 1-hour signed expiry. Once the job completes, GitHub revokes write-scoped tokens within seconds. Sansec and the original researcher independently verified that this revocation can often be raced: a token harvested while the job is still in_progress is accepted by GitHub's API. In Sansec's testing, a window of under 2 seconds was enough to capture the token and push a commit. For workflows that do other work after Composer (test, build, deploy), that window can be minutes.

Why this matters for the PHP supply chain

Composer pulls dependencies from Packagist, which mirrors releases from GitHub repositories. A maintainer who publishes a popular library typically has a CI workflow that runs Composer on every push, and a scheduled workflow that runs nightly. If those workflows use shivammathur/setup-php, the maintainer's GITHUB_TOKEN lands in the job log.

For a public repository, the log is public. An attacker who harvests a write-scoped token from a push or schedule run on a widely depended-on library can push a malicious commit and tag a release. Downstream CI pipelines running composer update pull the compromised code within minutes.

Recommendations

  1. Update Composer: do not pin a specific Composer version in your CI workflows. shivammathur/setup-php installs the latest stable Composer by default, which now contains the fix and will pick up future patches automatically.

  2. Drop default token permissions to read-only: at the organization or repository level, set the default GITHUB_TOKEN permission to read. Grant write only where the workflow needs it. This neutralizes the leak for every workflow that does not need to push.

  3. Freeze dependencies: avoid pulling new package versions until the broader impact is assessed.

Timeline

DateEvent
2021Token validation regex added to Composer (excludes hyphen)
May 12, 2026Damien Retzinger discovers the leak during incident triage
May 12, 2026Reported privately to Composer maintainers and GitHub PSIRT
May 12, 2026Reported as a public issue by GitHub user kesselb
May 13, 2026Composer 2.9.8, 2.2.28, and 1.10.28 released. GHSA-f9f8-rm49-7jv2 published
May 13, 2026Sansec publishes this advisory
May 13, 2026GitHub temporarily rolls back the new token format

Read more

Scan your store now
for malware & vulnerabilities

$ curl ecomscan.com | sh

eComscan is the most thorough security scanner for Magento, Adobe Commerce, Shopware, WooCommerce and many more.

Stay up to date with the latest eCommerce attacks

Sansec logo

experts in eCommerce security

Terms & Conditions
Privacy & Cookie Policy