Package: release.debian.org Severity: normal Tags: trixie X-Debbugs-Cc: composer@packages.debian.org Control: affects -1 + src:composer User: release.debian.org@packages.debian.org Usertags: pu Hi, As agreed with the security team, I’d like to fix CVE-2025-67746 in a point release rather than a DSA since it mostly fixes a display issue. [ 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 Regards, taffit
diff -Nru composer-2.8.8/debian/changelog composer-2.8.8/debian/changelog
--- composer-2.8.8/debian/changelog 2025-04-05 11:50:09.000000000 +0200
+++ composer-2.8.8/debian/changelog 2025-12-30 16:35:23.000000000 +0100
@@ -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 <taffit@debian.org> 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 07:54:44.000000000 +0200
+++ composer-2.8.8/debian/gbp.conf 2025-12-30 16:35:07.000000000 +0100
@@ -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 01:00:00.000000000 +0100
+++ composer-2.8.8/debian/patches/0017-Merge-commit-from-fork.patch 2025-12-30 16:33:32.000000000 +0100
@@ -0,0 +1,355 @@
+From: Jordi Boggiano <j.boggiano@seld.be>
+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<string> $messages
++ * @return string|array<string>
++ * @phpstan-return ($messages is string ? string : array<string>)
++ */
++ 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<string, array{input: string|string[], allowNewlines: bool, expected: string|string[]}>
++ */
++ 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 11:50:09.000000000 +0200
+++ composer-2.8.8/debian/patches/series 2025-12-30 16:33:32.000000000 +0100
@@ -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
Attachment:
signature.asc
Description: PGP signature