File php-composer2-CVE-2025-67746.patch of Package php-composer2.42546
From 5db1876a76fdef76d3c4f8a27995c434c7a43e71 Mon Sep 17 00:00:00 2001
From: Jordi Boggiano <j.boggiano@seld.be>
Date: Tue, 30 Dec 2025 13:18:16 +0100
Subject: [PATCH] Merge commit from fork
---
Index: SRC/src/Composer/Advisory/Auditor.php
===================================================================
--- SRC.orig/src/Composer/Advisory/Auditor.php
+++ SRC/src/Composer/Advisory/Auditor.php
@@ -272,7 +272,7 @@ $row[] = $advisory->ignoreReason ?? 'Non
$io->getTable()
->setHorizontal()
->setHeaders($headers)
-->addRow($row)
+->addRow(ConsoleIO::sanitize($row))
->setColumnWidth(1, 80)
->setColumnMaxWidth(1, 80)
->render();
@@ -341,7 +341,7 @@ $table = $io->getTable()
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();
Index: SRC/src/Composer/IO/ConsoleIO.php
===================================================================
--- SRC.orig/src/Composer/IO/ConsoleIO.php
+++ SRC/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 @@ return $this->output->isDebug();
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 @@ $this->doWrite($messages, $newline, fals
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 @@ public function ask($question, $default
{
$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 @@ public function askConfirmation($questio
{
$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 @@ public function askAndValidate($question
{
$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 @@ public function askAndHideAnswer($questi
{
$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 @@ public function select($question, $choic
{
$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);
$question->setErrorMessage($errorMessage);
$question->setMultiselect($multiselect);
@@ -342,4 +347,35 @@ return $this->output->getErrorOutput();
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;
+ }
}