Package: release.debian.org Severity: normal Tags: trixie X-Debbugs-Cc: [email protected] Control: affects -1 + src:composer User: [email protected] Usertags: pu
Hi, As agreed with the security team, I’d like to address a GitHub token leak [CVE-2026-45793] via p-u. The change is just a regex match on code that may not be used outside of GitHub infrastructure, and the testsuite is updated to check for it. [ Checklist ] [x] *all* changes are documented in the d/changelog [x] I reviewed all changes and I approve them [x] attach debdiff against the package in (old)stable [x] the issue is verified as fixed in unstable Cheers, taffit
diff -Nru composer-2.8.8/debian/changelog composer-2.8.8/debian/changelog --- composer-2.8.8/debian/changelog 2026-04-15 10:50:08.000000000 +0200 +++ composer-2.8.8/debian/changelog 2026-05-14 10:37:40.000000000 +0200 @@ -1,3 +1,10 @@ +composer (2.8.8-1+deb13u3) trixie; urgency=medium + + * Fix regexp to support new GitHub installation tokens format + (GHSA-f9f8-rm49-7jv2) [CVE-2026-45793] + + -- David Prévot <[email protected]> Thu, 14 May 2026 10:37:40 +0200 + composer (2.8.8-1+deb13u2) trixie; urgency=medium * Fix command injection via malicious Perforce repository definition diff -Nru composer-2.8.8/debian/patches/0020-ProcessExecutor-mask-GitHub-fine-grained-access-toke.patch composer-2.8.8/debian/patches/0020-ProcessExecutor-mask-GitHub-fine-grained-access-toke.patch --- composer-2.8.8/debian/patches/0020-ProcessExecutor-mask-GitHub-fine-grained-access-toke.patch 1970-01-01 01:00:00.000000000 +0100 +++ composer-2.8.8/debian/patches/0020-ProcessExecutor-mask-GitHub-fine-grained-access-toke.patch 2026-05-14 10:37:03.000000000 +0200 @@ -0,0 +1,68 @@ +From: Stephan <[email protected]> +Date: Thu, 21 Aug 2025 10:07:24 +0100 +Subject: ProcessExecutor: mask GitHub fine grained access tokens similar to + URL (#12487) + +Origin: upstream, https://github.com/composer/composer/commit/af81eac1816effad4fa959eb79b73ba214254540 +--- + src/Composer/Util/GitHub.php | 2 ++ + src/Composer/Util/ProcessExecutor.php | 4 ++-- + src/Composer/Util/Url.php | 4 ++-- + tests/Composer/Test/Util/ProcessExecutorTest.php | 1 + + 4 files changed, 7 insertions(+), 4 deletions(-) + +diff --git a/src/Composer/Util/GitHub.php b/src/Composer/Util/GitHub.php +index 64ee4f5..c6ad90f 100644 +--- a/src/Composer/Util/GitHub.php ++++ b/src/Composer/Util/GitHub.php +@@ -23,6 +23,8 @@ use Composer\Pcre\Preg; + */ + class GitHub + { ++ public const GITHUB_TOKEN_REGEX = '{^([a-f0-9]{12,}|gh[a-z]_[a-zA-Z0-9_]+|github_pat_[a-zA-Z0-9_]+)$}'; ++ + /** @var IOInterface */ + protected $io; + /** @var Config */ +diff --git a/src/Composer/Util/ProcessExecutor.php b/src/Composer/Util/ProcessExecutor.php +index 11db709..91a5c40 100644 +--- a/src/Composer/Util/ProcessExecutor.php ++++ b/src/Composer/Util/ProcessExecutor.php +@@ -483,8 +483,8 @@ class ProcessExecutor + + $commandString = is_string($command) ? $command : implode(' ', array_map(self::class.'::escape', $command)); + $safeCommand = Preg::replaceCallback('{://(?P<user>[^:/\s]+):(?P<password>[^@\s/]+)@}i', static function ($m): string { +- // if the username looks like a long (12char+) hex string, or a modern github token (e.g. ghp_xxx) we obfuscate that +- if (Preg::isMatch('{^([a-f0-9]{12,}|gh[a-z]_[a-zA-Z0-9_]+)$}', $m['user'])) { ++ // if the username looks like a long (12char+) hex string, or a modern github token (e.g. ghp_xxx, github_pat_xxx) we obfuscate that ++ if (Preg::isMatch(GitHub::GITHUB_TOKEN_REGEX, $m['user'])) { + return '://***:***@'; + } + if (Preg::isMatch('{^[a-f0-9]{12,}$}', $m['user'])) { +diff --git a/src/Composer/Util/Url.php b/src/Composer/Util/Url.php +index 32f6134..7e615fe 100644 +--- a/src/Composer/Util/Url.php ++++ b/src/Composer/Util/Url.php +@@ -110,8 +110,8 @@ class Url + $url = Preg::replace('{([&?]access_token=)[^&]+}', '$1***', $url); + + $url = Preg::replaceCallback('{^(?P<prefix>[a-z0-9]+://)?(?P<user>[^:/\s@]+):(?P<password>[^@\s/]+)@}i', static function ($m): string { +- // if the username looks like a long (12char+) hex string, or a modern github token (e.g. ghp_xxx) we obfuscate that +- if (Preg::isMatch('{^([a-f0-9]{12,}|gh[a-z]_[a-zA-Z0-9_]+|github_pat_[a-zA-Z0-9_]+)$}', $m['user'])) { ++ // if the username looks like a long (12char+) hex string, or a modern github token (e.g. ghp_xxx, github_pat_xxx) we obfuscate that ++ if (Preg::isMatch(GitHub::GITHUB_TOKEN_REGEX, $m['user'])) { + return $m['prefix'].'***:***@'; + } + +diff --git a/tests/Composer/Test/Util/ProcessExecutorTest.php b/tests/Composer/Test/Util/ProcessExecutorTest.php +index 28aca39..da27847 100644 +--- a/tests/Composer/Test/Util/ProcessExecutorTest.php ++++ b/tests/Composer/Test/Util/ProcessExecutorTest.php +@@ -82,6 +82,7 @@ class ProcessExecutorTest extends TestCase + ['echo https://foo:[email protected]/', 'echo https://foo:***@example.org/'], + ['echo http://[email protected]', 'echo http://[email protected]'], + ['echo http://abcdef1234567890234578:[email protected]/', 'echo http://***:***@github.com/'], ++ ['echo http://github_pat_1234567890abcdefghijkl_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVW:[email protected]/', 'echo http://***:***@github.com/'], + ["svn ls --verbose --non-interactive --username 'foo' --password 'bar' 'https://foo.example.org/svn/'", "svn ls --verbose --non-interactive --username 'foo' --password '***' 'https://foo.example.org/svn/'"], + ["svn ls --verbose --non-interactive --username 'foo' --password 'bar \'bar' 'https://foo.example.org/svn/'", "svn ls --verbose --non-interactive --username 'foo' --password '***' 'https://foo.example.org/svn/'"], + ]; diff -Nru composer-2.8.8/debian/patches/0021-Fix-regexp-to-support-new-GitHub-installation-tokens.patch composer-2.8.8/debian/patches/0021-Fix-regexp-to-support-new-GitHub-installation-tokens.patch --- composer-2.8.8/debian/patches/0021-Fix-regexp-to-support-new-GitHub-installation-tokens.patch 1970-01-01 01:00:00.000000000 +0100 +++ composer-2.8.8/debian/patches/0021-Fix-regexp-to-support-new-GitHub-installation-tokens.patch 2026-05-14 10:37:03.000000000 +0200 @@ -0,0 +1,148 @@ +From: Philipp Scheit <[email protected]> +Date: Wed, 13 May 2026 09:00:52 +0200 +Subject: Fix regexp to support new GitHub installation tokens format (#12853) + +Origin: upstream, https://github.com/composer/composer/commit/37872360dc9c0f8befc12f741e98db2aa9b1827c +Bug: https://github.com/composer/composer/security/advisories/GHSA-f9f8-rm49-7jv2 +--- + src/Composer/IO/BaseIO.php | 5 +- + src/Composer/Util/GitHub.php | 2 +- + tests/Composer/Test/IO/BaseIOTest.php | 99 +++++++++++++++++++++++++++++++++++ + 3 files changed, 103 insertions(+), 3 deletions(-) + create mode 100644 tests/Composer/Test/IO/BaseIOTest.php + +diff --git a/src/Composer/IO/BaseIO.php b/src/Composer/IO/BaseIO.php +index 55ac166..9650c96 100644 +--- a/src/Composer/IO/BaseIO.php ++++ b/src/Composer/IO/BaseIO.php +@@ -136,9 +136,10 @@ abstract class BaseIO implements IOInterface + + // 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.'"'); ++ if (!Preg::isMatch('{^[.A-Za-z0-9_-]+$}', $token)) { ++ throw new \UnexpectedValueException('Your github oauth token for '.$domain.' contains invalid characters.'); + } ++ + $this->checkAndSetAuthentication($domain, $token, 'x-oauth-basic'); + } + +diff --git a/src/Composer/Util/GitHub.php b/src/Composer/Util/GitHub.php +index c6ad90f..c1cfdfe 100644 +--- a/src/Composer/Util/GitHub.php ++++ b/src/Composer/Util/GitHub.php +@@ -23,7 +23,7 @@ use Composer\Pcre\Preg; + */ + class GitHub + { +- public const GITHUB_TOKEN_REGEX = '{^([a-f0-9]{12,}|gh[a-z]_[a-zA-Z0-9_]+|github_pat_[a-zA-Z0-9_]+)$}'; ++ public const GITHUB_TOKEN_REGEX = '{^([a-f0-9]{12,}|gh[a-z]_[a-zA-Z0-9_.-]+|github_pat_[a-zA-Z0-9_]+)$}'; + + /** @var IOInterface */ + protected $io; +diff --git a/tests/Composer/Test/IO/BaseIOTest.php b/tests/Composer/Test/IO/BaseIOTest.php +new file mode 100644 +index 0000000..b899d86 +--- /dev/null ++++ b/tests/Composer/Test/IO/BaseIOTest.php +@@ -0,0 +1,99 @@ ++<?php declare(strict_types=1); ++ ++/* ++ * This file is part of Composer. ++ * ++ * (c) Nils Adermann <[email protected]> ++ * Jordi Boggiano <[email protected]> ++ * ++ * For the full copyright and license information, please view the LICENSE ++ * file that was distributed with this source code. ++ */ ++ ++namespace Composer\Test\IO; ++ ++use Composer\Config; ++use Composer\IO\BufferIO; ++use Composer\Test\TestCase; ++ ++class BaseIOTest extends TestCase ++{ ++ /** ++ * @dataProvider provideValidGithubTokens ++ */ ++ public function testLoadConfigurationAcceptsValidGithubToken(string $token): void ++ { ++ $io = new BufferIO(); ++ $config = new Config(false); ++ $config->merge(['config' => ['github-oauth' => ['github.com' => $token]]]); ++ ++ $io->loadConfiguration($config); ++ ++ $auth = $io->getAuthentication('github.com'); ++ self::assertSame($token, $auth['username']); ++ self::assertSame('x-oauth-basic', $auth['password']); ++ } ++ ++ /** @return array<string, array{string}> */ ++ public static function provideValidGithubTokens(): array ++ { ++ return [ ++ 'legacy 40-hex PAT' => ['8a7f2c1bdc4e9f06a3b7c2e9d4f1a8b6c5d7e0f2'], ++ 'ghp_ flat token' => ['ghp_n3K9wQ2eL5bV8mY1pX4cZ7aR0fT6sH3uJ8oI'], ++ 'gho_ flat token' => ['gho_M2pQ7vR4xL9eK6bN1cT8aZ0sJ3wY5fH7uG2d'], ++ 'ghu_ flat token' => ['ghu_R5tY8wA1xC4eK7bN0pV3mL6sH9uJ2gD5fQ8z'], ++ 'ghs_ flat token' => ['ghs_K7bN3pV5mL8eR2tY9wA1xC4sH6uJ0gD3fQ8z'], ++ 'ghr_ flat token' => ['ghr_X9aZ2sJ5wY8fH1uG4dR7bN0pV3mL6eK2tQ5c'], ++ // shivammathur/setup-php style: ghs_<id>_<base64url>.<base64url>.<base64url> ++ // base64url alphabet includes '-' which the old regex rejected ++ 'ghs_ structured installation token (jwt body)' => [ ++ 'ghs_1234567890_eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJjb21wb3NlciJ9.aB-cDef_GHIjkl-mnoPQR0123456', ++ ], ++ 'github_pat_ fine-grained PAT' => [ ++ 'github_pat_11ABCDEFG0aB1cD2eF3gH4_n3K9wQ2eL5bV8mY1pX4cZ7aR0fT6sH3uJ8oI2pQ7vR4xL9eK6bN', ++ ], ++ ]; ++ } ++ ++ /** ++ * @dataProvider provideUrlBreakingGithubTokens ++ */ ++ public function testLoadConfigurationRejectsTokenWithUrlBreakingCharacters(string $token, string $offending): void ++ { ++ $io = new BufferIO(); ++ $config = new Config(false); ++ $config->merge(['config' => ['github-oauth' => ['github.com' => $token]]]); ++ ++ try { ++ $io->loadConfiguration($config); ++ self::fail('Expected loadConfiguration to reject token containing '.$offending); ++ } catch (\UnexpectedValueException $e) { ++ // Defect #1: the rejected token must not be echoed back into the ++ // exception message — Symfony Console renders it to stderr and CI ++ // log shippers / GitHub Actions secret masking do not reliably ++ // strip it from the framed error block. ++ self::assertStringNotContainsString( ++ $token, ++ $e->getMessage(), ++ 'Exception message must not leak the rejected token value.' ++ ); ++ } ++ } ++ ++ /** @return array<string, array{string, string}> */ ++ public static function provideUrlBreakingGithubTokens(): array ++ { ++ return [ ++ 'contains @ (userinfo separator)' => ['[email protected]', '@'], ++ 'contains : (basic-auth user:pass split)' => ['ghp_AAAA:extra', ':'], ++ 'contains / (path separator)' => ['ghp_AAA/BBB', '/'], ++ 'contains backslash' => ['ghp_AAA\\BBB', '\\'], ++ 'contains ? (query separator)' => ['ghp_AAA?x=1', '?'], ++ 'contains # (fragment)' => ['ghp_AAA#frag', '#'], ++ 'contains space' => ['ghp_AAA BBB', 'space'], ++ 'contains tab' => ["ghp_AAA\tBBB", 'tab'], ++ 'contains CR' => ["ghp_AAA\rBBB", 'CR'], ++ 'contains LF (header injection)' => ["ghp_AAA\nX-Evil: 1", 'LF'], ++ ]; ++ } ++} diff -Nru composer-2.8.8/debian/patches/0022-Modernize-PHPUnit-syntax.patch composer-2.8.8/debian/patches/0022-Modernize-PHPUnit-syntax.patch --- composer-2.8.8/debian/patches/0022-Modernize-PHPUnit-syntax.patch 1970-01-01 01:00:00.000000000 +0100 +++ composer-2.8.8/debian/patches/0022-Modernize-PHPUnit-syntax.patch 2026-05-14 10:37:03.000000000 +0200 @@ -0,0 +1,38 @@ +From: =?utf-8?q?David_Pr=C3=A9vot?= <[email protected]> +Date: Fri, 29 Aug 2025 08:31:05 +0200 +Subject: Modernize PHPUnit syntax + +--- + tests/Composer/Test/IO/BaseIOTest.php | 9 +++------ + 1 file changed, 3 insertions(+), 6 deletions(-) + +diff --git a/tests/Composer/Test/IO/BaseIOTest.php b/tests/Composer/Test/IO/BaseIOTest.php +index b899d86..e22d8ea 100644 +--- a/tests/Composer/Test/IO/BaseIOTest.php ++++ b/tests/Composer/Test/IO/BaseIOTest.php +@@ -15,12 +15,11 @@ namespace Composer\Test\IO; + use Composer\Config; + use Composer\IO\BufferIO; + use Composer\Test\TestCase; ++use PHPUnit\Framework\Attributes\DataProvider; + + class BaseIOTest extends TestCase + { +- /** +- * @dataProvider provideValidGithubTokens +- */ ++ #[DataProvider('provideValidGithubTokens')] + public function testLoadConfigurationAcceptsValidGithubToken(string $token): void + { + $io = new BufferIO(); +@@ -55,9 +54,7 @@ class BaseIOTest extends TestCase + ]; + } + +- /** +- * @dataProvider provideUrlBreakingGithubTokens +- */ ++ #[DataProvider('provideUrlBreakingGithubTokens')] + public function testLoadConfigurationRejectsTokenWithUrlBreakingCharacters(string $token, string $offending): void + { + $io = new BufferIO(); diff -Nru composer-2.8.8/debian/patches/series composer-2.8.8/debian/patches/series --- composer-2.8.8/debian/patches/series 2026-04-15 10:50:08.000000000 +0200 +++ composer-2.8.8/debian/patches/series 2026-05-14 10:37:03.000000000 +0200 @@ -17,3 +17,6 @@ 0017-Merge-commit-from-fork.patch 0018-Merge-commit-from-fork.patch 0019-Merge-commit-from-fork.patch +0020-ProcessExecutor-mask-GitHub-fine-grained-access-toke.patch +0021-Fix-regexp-to-support-new-GitHub-installation-tokens.patch +0022-Modernize-PHPUnit-syntax.patch
signature.asc
Description: PGP signature

