Skip to content

Github Actions issued GITHUB_TOKEN disclosure in GitHub Actions logs

High severity GitHub Reviewed Published May 13, 2026 in composer/composer • Updated May 19, 2026

Package

composer composer/composer (Composer)

Affected versions

>= 2.3.0, < 2.9.8
>= 2.0.0, < 2.2.28
>= 1.0, < 1.10.28

Patched versions

2.9.8
2.2.28
1.10.28

Description

Summary

Composer leaks the full contents of tokens configured as GitHub OAuth tokens if they do not match Composer's expected format for such tokens to stderr. GitHub has introduced a new format for GitHub Actions GITHUB_TOKEN values. These tokens are validated in the same way by Composer on GitHub Actions. The new format including a - (hyphen) fails Composer's validation and leads to disclosure of the GITHUB_TOKEN in logs.

Many widely-used Actions (e.g. shivammathur/setup-php) auto-register GITHUB_TOKEN into composer's global auth.json, so the leak triggers without any unusual user configuration.

GitHub Actions tokens expire when the associated job finishes, and they are scoped to the respective repository only. So in most regular cases the Composer validation, which errors while leaking the token, also immediately ends the job, expiring the token immediately. Tokens expire at the very latest after 6 hours on GitHub-hosted runners. If you use self-hosted runner, expiration is at most 24 hours after creation. The new token format is being rolled out gradually, so not all repositories are affected yet, but will be soon.

Classic ghp_ PATs are not affected by the regex bug per se, but the same leak primitive applies to any future credential that fails validation for any reason.

Details

When a GitHub token fails regular expression validation of the character set, the rejected token is interpolated verbatim into the UnexpectedValueException message thrown by Composer\IO\BaseIO::loadConfiguration(), which Symfony Console then prints. Validation reliably fails for any token containing a - (hyphen), which includes the modern ghs_<id>_<base64url-JWT> GitHub App installation token format, the same format used by GitHub Actions' built-in GITHUB_TOKEN and by actions/create-github-app-token.

Severity: medium. Pre-conditions are common in real-world CI. Practical blast radius is bounded by the leaked credential's scope and TTL (short for a workflow GITHUB_TOKEN, longer for App-minted tokens or user-issued credentials that happen to contain -).

Vulnerable code, src/Composer/IO/BaseIO.php (line 139 on main, line 143 on 2.8.x), inside loadConfiguration():

// allowed chars for GH tokens are from https://github.blog/changelog/2021-03-04-authentication-token-format-updates/
// plus dots which were at some point used for GH app integration tokens
if (!Preg::isMatch('{^[.A-Za-z0-9_]+$}', $token)) {
    throw new \UnexpectedValueException(
        'Your github oauth token for '.$domain.' contains invalid characters: "'.$token.'"'
    );
}

Three issues combine to produce the leak:

  1. The rejected token is interpolated into the exception message. The exception bubbles up to Symfony Console's default error renderer, which writes it to stderr. Any environment that captures stderr (CI logs, log shippers, monitoring, support transcripts) now has the raw token.

  2. The validation regex ^[.A-Za-z0-9_]+$ does not permit -. GitHub's current ghs_<numeric-id>_<base64url-JWT> structured installation tokens routinely contain -, because base64url (RFC 4648 §5) uses - and _ as URL-safe replacements for + and /. The regex was chosen in 2021 on the understanding that GitHub tokens use only [A-Za-z0-9_] plus ..

  3. Detection / mitigation in upstream platforms is unreliable. GitHub Actions' built-in secret masker matches registered values as exact substrings. When the exception message is rendered by Symfony Console it may wrap, embed in In BaseIO.php line N: framing, or interleave with ANSI control sequences. So the masker does not redact, and the plaintext token reaches the log.

References

@naderman naderman published to composer/composer May 13, 2026
Published to the GitHub Advisory Database May 19, 2026
Reviewed May 19, 2026
Last updated May 19, 2026

Severity

High

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Network
Attack complexity
Low
Privileges required
None
User interaction
None
Scope
Unchanged
Confidentiality
High
Integrity
None
Availability
None

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N

EPSS score

Exploit Prediction Scoring System (EPSS)

This score estimates the probability of this vulnerability being exploited within the next 30 days. Data provided by FIRST.
(19th percentile)

Weaknesses

Exposure of Sensitive Information to an Unauthorized Actor

The product exposes sensitive information to an actor that is not explicitly authorized to have access to that information. Learn more on MITRE.

CVE ID

CVE-2026-45793

GHSA ID

GHSA-f9f8-rm49-7jv2

Source code

Credits

Loading Checking history
See something to contribute? Suggest improvements for this vulnerability.