Version in base suite: 2.8.8-1 Base version: composer_2.8.8-1 Target version: composer_2.8.8-1+deb13u1 Base file: /srv/ftp-master.debian.org/ftp/pool/main/c/composer/composer_2.8.8-1.dsc Target file: /srv/ftp-master.debian.org/policy/pool/main/c/composer/composer_2.8.8-1+deb13u1.dsc changelog | 8 gbp.conf | 2 patches/0017-Merge-commit-from-fork.patch | 355 ++++++++++++++++++++++++++++++ patches/series | 1 4 files changed, 365 insertions(+), 1 deletion(-) dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmpt1dl3mzt/composer_2.8.8-1.dsc: no acceptable signature found dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmpt1dl3mzt/composer_2.8.8-1+deb13u1.dsc: no acceptable signature found diff -Nru composer-2.8.8/debian/changelog composer-2.8.8/debian/changelog --- composer-2.8.8/debian/changelog 2025-04-05 09:50:09.000000000 +0000 +++ composer-2.8.8/debian/changelog 2025-12-30 15:35:23.000000000 +0000 @@ -1,3 +1,11 @@ +composer (2.8.8-1+deb13u1) trixie; urgency=medium + + * Backport fix from composer 2.9.3: + Fixed ANSI sequence injection [CVE-2025-67746] + * Track debian/trixie + + -- David Prévot Tue, 30 Dec 2025 16:35:23 +0100 + composer (2.8.8-1) unstable; urgency=medium [ Jordi Boggiano ] diff -Nru composer-2.8.8/debian/gbp.conf composer-2.8.8/debian/gbp.conf --- composer-2.8.8/debian/gbp.conf 2024-06-25 05:54:44.000000000 +0000 +++ composer-2.8.8/debian/gbp.conf 2025-12-30 15:35:07.000000000 +0000 @@ -1,5 +1,5 @@ [DEFAULT] -debian-branch = debian/latest +debian-branch = debian/trixie filter = [ '.gitattributes' ] pristine-tar = True upstream-vcs-tag = %(version%~%-)s diff -Nru composer-2.8.8/debian/patches/0017-Merge-commit-from-fork.patch composer-2.8.8/debian/patches/0017-Merge-commit-from-fork.patch --- composer-2.8.8/debian/patches/0017-Merge-commit-from-fork.patch 1970-01-01 00:00:00.000000000 +0000 +++ composer-2.8.8/debian/patches/0017-Merge-commit-from-fork.patch 2025-12-30 15:33:32.000000000 +0000 @@ -0,0 +1,355 @@ +From: Jordi Boggiano +Date: Tue, 30 Dec 2025 13:18:16 +0100 +Subject: Merge commit from fork + +Origin: upstream, https://github.com/composer/composer/commit/5db1876a76fdef76d3c4f8a27995c434c7a43e71 +Bug: https://github.com/composer/composer/security/advisories/GHSA-59pp-r3rg-353g +Bug-Debian: https://security-tracker.debian.org/tracker/CVE-2025-67746 +--- + src/Composer/Advisory/Auditor.php | 4 +- + src/Composer/IO/ConsoleIO.php | 46 ++++++- + tests/Composer/Test/IO/ConsoleIOTest.php | 200 +++++++++++++++++++++++++++++++ + 3 files changed, 243 insertions(+), 7 deletions(-) + +diff --git a/src/Composer/Advisory/Auditor.php b/src/Composer/Advisory/Auditor.php +index 485b332..3759787 100644 +--- a/src/Composer/Advisory/Auditor.php ++++ b/src/Composer/Advisory/Auditor.php +@@ -295,7 +295,7 @@ class Auditor + $io->getTable() + ->setHorizontal() + ->setHeaders($headers) +- ->addRow($row) ++ ->addRow(ConsoleIO::sanitize($row)) + ->setColumnWidth(1, 80) + ->setColumnMaxWidth(1, 80) + ->render(); +@@ -368,7 +368,7 @@ class Auditor + + foreach ($packages as $pkg) { + $replacement = $pkg->getReplacementPackage() !== null ? $pkg->getReplacementPackage() : 'none'; +- $table->addRow([$this->getPackageNameWithLink($pkg), $replacement]); ++ $table->addRow(ConsoleIO::sanitize([$this->getPackageNameWithLink($pkg), $replacement])); + } + + $table->render(); +diff --git a/src/Composer/IO/ConsoleIO.php b/src/Composer/IO/ConsoleIO.php +index 8ecea42..9d180ca 100644 +--- a/src/Composer/IO/ConsoleIO.php ++++ b/src/Composer/IO/ConsoleIO.php +@@ -12,6 +12,7 @@ + + namespace Composer\IO; + ++use Composer\Pcre\Preg; + use Composer\Question\StrictConfirmationQuestion; + use Symfony\Component\Console\Helper\HelperSet; + use Symfony\Component\Console\Helper\ProgressBar; +@@ -120,6 +121,8 @@ class ConsoleIO extends BaseIO + */ + public function write($messages, bool $newline = true, int $verbosity = self::NORMAL) + { ++ $messages = self::sanitize($messages); ++ + $this->doWrite($messages, $newline, false, $verbosity); + } + +@@ -128,6 +131,8 @@ class ConsoleIO extends BaseIO + */ + public function writeError($messages, bool $newline = true, int $verbosity = self::NORMAL) + { ++ $messages = self::sanitize($messages); ++ + $this->doWrite($messages, $newline, true, $verbosity); + } + +@@ -252,7 +257,7 @@ class ConsoleIO extends BaseIO + { + /** @var \Symfony\Component\Console\Helper\QuestionHelper $helper */ + $helper = $this->helperSet->get('question'); +- $question = new Question($question, $default); ++ $question = new Question(self::sanitize($question), is_string($default) ? self::sanitize($default) : $default); + + return $helper->ask($this->input, $this->getErrorOutput(), $question); + } +@@ -264,7 +269,7 @@ class ConsoleIO extends BaseIO + { + /** @var \Symfony\Component\Console\Helper\QuestionHelper $helper */ + $helper = $this->helperSet->get('question'); +- $question = new StrictConfirmationQuestion($question, $default); ++ $question = new StrictConfirmationQuestion(self::sanitize($question), is_string($default) ? self::sanitize($default) : $default); + + return $helper->ask($this->input, $this->getErrorOutput(), $question); + } +@@ -276,7 +281,7 @@ class ConsoleIO extends BaseIO + { + /** @var \Symfony\Component\Console\Helper\QuestionHelper $helper */ + $helper = $this->helperSet->get('question'); +- $question = new Question($question, $default); ++ $question = new Question(self::sanitize($question), is_string($default) ? self::sanitize($default) : $default); + $question->setValidator($validator); + $question->setMaxAttempts($attempts); + +@@ -290,7 +295,7 @@ class ConsoleIO extends BaseIO + { + /** @var \Symfony\Component\Console\Helper\QuestionHelper $helper */ + $helper = $this->helperSet->get('question'); +- $question = new Question($question); ++ $question = new Question(self::sanitize($question)); + $question->setHidden(true); + + return $helper->ask($this->input, $this->getErrorOutput(), $question); +@@ -303,7 +308,7 @@ class ConsoleIO extends BaseIO + { + /** @var \Symfony\Component\Console\Helper\QuestionHelper $helper */ + $helper = $this->helperSet->get('question'); +- $question = new ChoiceQuestion($question, $choices, $default); ++ $question = new ChoiceQuestion(self::sanitize($question), self::sanitize($choices), is_string($default) ? self::sanitize($default) : $default); + $question->setMaxAttempts($attempts ?: null); // IOInterface requires false, and Question requires null or int + $question->setErrorMessage($errorMessage); + $question->setMultiselect($multiselect); +@@ -342,4 +347,35 @@ class ConsoleIO extends BaseIO + + return $this->output; + } ++ ++ /** ++ * Sanitize string to remove control characters ++ * ++ * If $allowNewlines is true, \x0A (\n) and \x0D\x0A (\r\n) are let through. Single \r are still sanitized away to prevent overwriting whole lines. ++ * ++ * All other control chars (except NULL bytes) as well as ANSI escape sequences are removed. ++ * ++ * @param string|iterable $messages ++ * @return string|array ++ * @phpstan-return ($messages is string ? string : array) ++ */ ++ public static function sanitize($messages, bool $allowNewlines = true) ++ { ++ // Match ANSI escape sequences: ++ // - CSI (Control Sequence Introducer): ESC [ params intermediate final ++ // - OSC (Operating System Command): ESC ] ... ESC \ or BEL ++ // - Other ESC sequences: ESC followed by any character ++ $escapePattern = '\x1B\[[\x30-\x3F]*[\x20-\x2F]*[\x40-\x7E]|\x1B\].*?(?:\x1B\\\\|\x07)|\x1B.'; ++ $pattern = $allowNewlines ? "{{$escapePattern}|[\x01-\x09\x0B\x0C\x0E-\x1A]|\r(?!\n)}u" : "{{$escapePattern}|[\x01-\x1A]}u"; ++ if (is_string($messages)) { ++ return Preg::replace($pattern, '', $messages); ++ } ++ ++ $sanitized = []; ++ foreach ($messages as $key => $message) { ++ $sanitized[$key] = Preg::replace($pattern, '', $message); ++ } ++ ++ return $sanitized; ++ } + } +diff --git a/tests/Composer/Test/IO/ConsoleIOTest.php b/tests/Composer/Test/IO/ConsoleIOTest.php +index 8e0a829..cf38bf4 100644 +--- a/tests/Composer/Test/IO/ConsoleIOTest.php ++++ b/tests/Composer/Test/IO/ConsoleIOTest.php +@@ -296,4 +296,204 @@ class ConsoleIOTest extends TestCase + self::assertTrue($consoleIO->hasAuthentication('repoName')); + self::assertFalse($consoleIO->hasAuthentication('repoName2')); + } ++ ++ /** ++ * @dataProvider sanitizeProvider ++ * @param string|string[] $input ++ * @param string|string[] $expected ++ */ ++ public function testSanitize($input, bool $allowNewlines, $expected): void ++ { ++ self::assertSame($expected, ConsoleIO::sanitize($input, $allowNewlines)); ++ } ++ ++ /** ++ * @return array ++ */ ++ public static function sanitizeProvider(): array ++ { ++ return [ ++ // String input with allowNewlines=true ++ 'string with \n allowed' => [ ++ 'input' => "Hello\nWorld", ++ 'allowNewlines' => true, ++ 'expected' => "Hello\nWorld", ++ ], ++ 'string with \r\n allowed' => [ ++ 'input' => "Hello\r\nWorld", ++ 'allowNewlines' => true, ++ 'expected' => "Hello\r\nWorld", ++ ], ++ 'string with standalone \r removed' => [ ++ 'input' => "Hello\rWorld", ++ 'allowNewlines' => true, ++ 'expected' => "HelloWorld", ++ ], ++ 'string with escape sequence removed' => [ ++ 'input' => "Hello\x1B[31mWorld", ++ 'allowNewlines' => true, ++ 'expected' => "HelloWorld", ++ ], ++ 'string with control chars removed' => [ ++ 'input' => "Hello\x01\x08\x09World", ++ 'allowNewlines' => true, ++ 'expected' => "HelloWorld", ++ ], ++ 'string with mixed control chars and newlines' => [ ++ 'input' => "Line1\n\x1B[32mLine2\x08\rLine3", ++ 'allowNewlines' => true, ++ 'expected' => "Line1\nLine2Line3", ++ ], ++ 'string with null bytes are allowed' => [ ++ 'input' => "Hello\x00World", ++ 'allowNewlines' => true, ++ 'expected' => "Hello\x00World", ++ ], ++ ++ // String input with allowNewlines=false ++ 'string with \n removed' => [ ++ 'input' => "Hello\nWorld", ++ 'allowNewlines' => false, ++ 'expected' => "HelloWorld", ++ ], ++ 'string with \r\n removed' => [ ++ 'input' => "Hello\r\nWorld", ++ 'allowNewlines' => false, ++ 'expected' => "HelloWorld", ++ ], ++ 'string with escape sequence removed (no newlines)' => [ ++ 'input' => "Hello\x1B[31mWorld", ++ 'allowNewlines' => false, ++ 'expected' => "HelloWorld", ++ ], ++ 'string with all control chars removed' => [ ++ 'input' => "Hello\x01\x08\x09\x0A\x0DWorld", ++ 'allowNewlines' => false, ++ 'expected' => "HelloWorld", ++ ], ++ ++ // Array input with allowNewlines=true ++ 'array with newlines allowed' => [ ++ 'input' => ["Hello\nWorld", "Foo\r\nBar"], ++ 'allowNewlines' => true, ++ 'expected' => ["Hello\nWorld", "Foo\r\nBar"], ++ ], ++ 'array with control chars removed' => [ ++ 'input' => ["Hello\x1B[31mWorld", "Foo\x08Bar\r"], ++ 'allowNewlines' => true, ++ 'expected' => ["HelloWorld", "FooBar"], ++ ], ++ ++ // Array input with allowNewlines=false ++ 'array with newlines removed' => [ ++ 'input' => ["Hello\nWorld", "Foo\r\nBar"], ++ 'allowNewlines' => false, ++ 'expected' => ["HelloWorld", "FooBar"], ++ ], ++ 'array with all control chars removed' => [ ++ 'input' => ["Test\x01\x0A", "Data\x1B[m\x0D"], ++ 'allowNewlines' => false, ++ 'expected' => ["Test", "Data"], ++ ], ++ ++ // Edge cases ++ 'empty string' => [ ++ 'input' => '', ++ 'allowNewlines' => true, ++ 'expected' => '', ++ ], ++ 'empty array' => [ ++ 'input' => [], ++ 'allowNewlines' => true, ++ 'expected' => [], ++ ], ++ 'string with no control chars' => [ ++ 'input' => 'Hello World', ++ 'allowNewlines' => true, ++ 'expected' => 'Hello World', ++ ], ++ 'string with unicode' => [ ++ 'input' => "Hello 世界\nTest", ++ 'allowNewlines' => true, ++ 'expected' => "Hello 世界\nTest", ++ ], ++ ++ // Various ANSI escape sequences ++ 'CSI with multiple parameters' => [ ++ 'input' => "Text\x1B[1;31;40mColored\x1B[0mNormal", ++ 'allowNewlines' => true, ++ 'expected' => "TextColoredNormal", ++ ], ++ 'CSI SGR reset' => [ ++ 'input' => "Before\x1B[mAfter", ++ 'allowNewlines' => true, ++ 'expected' => "BeforeAfter", ++ ], ++ 'CSI cursor positioning' => [ ++ 'input' => "Line\x1B[2J\x1B[H\x1B[10;5HText", ++ 'allowNewlines' => true, ++ 'expected' => "LineText", ++ ], ++ 'OSC with BEL terminator' => [ ++ 'input' => "Text\x1B]0;Window Title\x07More", ++ 'allowNewlines' => true, ++ 'expected' => "TextMore", ++ ], ++ 'OSC with ST terminator' => [ ++ 'input' => "Text\x1B]2;Title\x1B\\More", ++ 'allowNewlines' => true, ++ 'expected' => "TextMore", ++ ], ++ 'Simple ESC sequences' => [ ++ 'input' => "Text\x1B7Saved\x1B8Restored\x1BcReset", ++ 'allowNewlines' => true, ++ 'expected' => "TextSavedRestoredReset", ++ ], ++ 'ESC D (Index)' => [ ++ 'input' => "Line1\x1BDLine2", ++ 'allowNewlines' => true, ++ 'expected' => "Line1Line2", ++ ], ++ 'ESC E (Next Line)' => [ ++ 'input' => "Line1\x1BELine2", ++ 'allowNewlines' => true, ++ 'expected' => "Line1Line2", ++ ], ++ 'ESC M (Reverse Index)' => [ ++ 'input' => "Text\x1BMMore", ++ 'allowNewlines' => true, ++ 'expected' => "TextMore", ++ ], ++ 'ESC N (SS2) and ESC O (SS3)' => [ ++ 'input' => "Text\x1BNchar\x1BOanother", ++ 'allowNewlines' => true, ++ 'expected' => "Textcharanother", ++ ], ++ 'Multiple escape sequences in sequence' => [ ++ 'input' => "\x1B[1m\x1B[31m\x1B[44mBold Red on Blue\x1B[0m", ++ 'allowNewlines' => true, ++ 'expected' => "Bold Red on Blue", ++ ], ++ 'CSI with question mark (private mode)' => [ ++ 'input' => "Text\x1B[?25lHidden\x1B[?25hVisible", ++ 'allowNewlines' => true, ++ 'expected' => "TextHiddenVisible", ++ ], ++ 'CSI erase sequences' => [ ++ 'input' => "Clear\x1B[2J\x1B[K\x1B[1KScreen", ++ 'allowNewlines' => true, ++ 'expected' => "ClearScreen", ++ ], ++ 'Hyperlink OSC 8' => [ ++ 'input' => "Click \x1B]8;;https://example.com\x1B\\here\x1B]8;;\x1B\\ for link", ++ 'allowNewlines' => true, ++ 'expected' => "Click here for link", ++ ], ++ 'Mixed content with complex sequences' => [ ++ 'input' => "\x1B[1;33mWarning:\x1B[0m File\x1B[31m not\x1B[0m found\n\x1B[2KRetrying...", ++ 'allowNewlines' => true, ++ 'expected' => "Warning: File not found\nRetrying...", ++ ], ++ ]; ++ } + } diff -Nru composer-2.8.8/debian/patches/series composer-2.8.8/debian/patches/series --- composer-2.8.8/debian/patches/series 2025-04-05 09:50:09.000000000 +0000 +++ composer-2.8.8/debian/patches/series 2025-12-30 15:33:32.000000000 +0000 @@ -14,3 +14,4 @@ 0014-Revert-Add-workaround-for-InstalledVersion-to-ensure.patch 0015-Revert-Fix-regression-from-12233-in-InstalledVersion.patch 0016-Modernize-PHPUnit-syntax.patch +0017-Merge-commit-from-fork.patch