Package: release.debian.org
Severity: normal
Tags: trixie
X-Debbugs-Cc: [email protected]
Control: affects -1 + src:php-guzzlehttp-psr7
User: [email protected]
Usertags: pu

Hi,

As agreed with the security team, I’d like to fix CVE-2026-55766 in the
point release since it doesn’t deserve a DSA.

[ 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

Thanks in advance for considering.

Regards,

taffit
diff -Nru php-guzzlehttp-psr7-2.7.1/debian/changelog php-guzzlehttp-psr7-2.7.1/debian/changelog
--- php-guzzlehttp-psr7-2.7.1/debian/changelog	2026-05-30 13:39:01.000000000 +0200
+++ php-guzzlehttp-psr7-2.7.1/debian/changelog	2026-06-19 08:13:30.000000000 +0200
@@ -1,3 +1,10 @@
+php-guzzlehttp-psr7 (2.7.1-1+deb13u2) trixie; urgency=medium
+
+  * Reject CR/LF in HTTP method, protocol version, and reason phrase
+    (GHSA-vm85-hxw5-5432) [CVE-2026-55766]
+
+ -- David Prévot <[email protected]>  Fri, 19 Jun 2026 08:13:30 +0200
+
 php-guzzlehttp-psr7 (2.7.1-1+deb13u1) trixie; urgency=medium
 
   * Backport fixes from upstream
diff -Nru php-guzzlehttp-psr7-2.7.1/debian/patches/0004-Modernize-PHPUnit-syntax.patch php-guzzlehttp-psr7-2.7.1/debian/patches/0004-Modernize-PHPUnit-syntax.patch
--- php-guzzlehttp-psr7-2.7.1/debian/patches/0004-Modernize-PHPUnit-syntax.patch	2026-05-30 13:39:01.000000000 +0200
+++ php-guzzlehttp-psr7-2.7.1/debian/patches/0004-Modernize-PHPUnit-syntax.patch	2026-06-19 08:13:30.000000000 +0200
@@ -9,8 +9,8 @@
  tests/MessageTest.php              |  9 +++------
  tests/PumpStreamTest.php           |  5 ++---
  tests/QueryTest.php                |  9 +++------
- tests/RequestTest.php              | 21 ++++++---------------
- tests/ResponseTest.php             | 21 +++++++--------------
+ tests/RequestTest.php              | 37 ++++++++++---------------------------
+ tests/ResponseTest.php             | 37 +++++++++++--------------------------
  tests/ServerRequestTest.php        | 13 ++++---------
  tests/StreamDecoratorTraitTest.php |  5 ++---
  tests/StreamTest.php               | 12 ++++--------
@@ -20,7 +20,7 @@
  tests/UriResolverTest.php          | 13 ++++---------
  tests/UriTest.php                  | 25 +++++++------------------
  tests/UtilsTest.php                |  9 ++++-----
- 17 files changed, 66 insertions(+), 138 deletions(-)
+ 17 files changed, 74 insertions(+), 162 deletions(-)
 
 diff --git a/tests/AppendStreamTest.php b/tests/AppendStreamTest.php
 index 302470a..b0c6a2e 100644
@@ -113,7 +113,7 @@
      {
          self::assertSame($result, Psr7\Header::splitList($header));
 diff --git a/tests/MessageTest.php b/tests/MessageTest.php
-index 42909be..4df397a 100644
+index 1862a95..31f3bb1 100644
 --- a/tests/MessageTest.php
 +++ b/tests/MessageTest.php
 @@ -6,6 +6,7 @@ namespace GuzzleHttp\Tests\Psr7;
@@ -204,7 +204,7 @@
      {
          $result = Psr7\Query::parse($input, false);
 diff --git a/tests/RequestTest.php b/tests/RequestTest.php
-index 3a62c57..0ea5069 100644
+index 9acc022..ec5c44b 100644
 --- a/tests/RequestTest.php
 +++ b/tests/RequestTest.php
 @@ -7,6 +7,7 @@ namespace GuzzleHttp\Tests\Psr7;
@@ -236,7 +236,51 @@
      public function testWithInvalidMethods($method): void
      {
          $r = new Request('get', '/');
-@@ -198,9 +195,7 @@ class RequestTest extends TestCase
+@@ -125,9 +122,7 @@ class RequestTest extends TestCase
+         ];
+     }
+ 
+-    /**
+-     * @dataProvider startLineSeparatorProvider
+-     */
++    #[DataProvider('startLineSeparatorProvider')]
+     public function testConstructWithLineSeparatorsInMethod(string $lineSeparator): void
+     {
+         $this->expectException(\InvalidArgumentException::class);
+@@ -135,9 +130,7 @@ class RequestTest extends TestCase
+         new Request('GET'.$lineSeparator.'X-Injected: yes', '/');
+     }
+ 
+-    /**
+-     * @dataProvider startLineSeparatorProvider
+-     */
++    #[DataProvider('startLineSeparatorProvider')]
+     public function testWithMethodRejectsLineSeparators(string $lineSeparator): void
+     {
+         $r = new Request('GET', '/');
+@@ -147,9 +140,7 @@ class RequestTest extends TestCase
+         $r->withMethod('GET'.$lineSeparator.'X-Injected: yes');
+     }
+ 
+-    /**
+-     * @dataProvider startLineSeparatorProvider
+-     */
++    #[DataProvider('startLineSeparatorProvider')]
+     public function testConstructWithLineSeparatorsInProtocolVersion(string $lineSeparator): void
+     {
+         $this->expectException(\InvalidArgumentException::class);
+@@ -157,9 +148,7 @@ class RequestTest extends TestCase
+         new Request('GET', '/', [], null, '1.1'.$lineSeparator.'X-Injected: yes');
+     }
+ 
+-    /**
+-     * @dataProvider startLineSeparatorProvider
+-     */
++    #[DataProvider('startLineSeparatorProvider')]
+     public function testWithProtocolVersionRejectsLineSeparators(string $lineSeparator): void
+     {
+         $r = new Request('GET', '/');
+@@ -249,9 +238,7 @@ class RequestTest extends TestCase
          self::assertSame('', $r->getHeaderLine('Bar'));
      }
  
@@ -247,7 +291,7 @@
      public function testContainsNotAllowedCharsOnHeaderField($header): void
      {
          $this->expectExceptionMessage(
-@@ -223,9 +218,7 @@ class RequestTest extends TestCase
+@@ -274,9 +261,7 @@ class RequestTest extends TestCase
          return [[' key '], ['key '], [' key'], ['key/'], ['key('], ['key\\'], [' ']];
      }
  
@@ -258,7 +302,7 @@
      public function testContainsAllowedCharsOnHeaderField($header): void
      {
          $r = new Request(
-@@ -329,9 +322,7 @@ class RequestTest extends TestCase
+@@ -380,9 +365,7 @@ class RequestTest extends TestCase
          self::assertSame('example.com:8080', $request->getHeaderLine('Host'));
      }
  
@@ -270,7 +314,7 @@
      {
          $this->expectException(\InvalidArgumentException::class);
 diff --git a/tests/ResponseTest.php b/tests/ResponseTest.php
-index 70d3ecc..2ce74e4 100644
+index d15d726..c05eb9e 100644
 --- a/tests/ResponseTest.php
 +++ b/tests/ResponseTest.php
 @@ -6,6 +6,7 @@ namespace GuzzleHttp\Tests\Psr7;
@@ -281,7 +325,51 @@
  use PHPUnit\Framework\TestCase;
  use Psr\Http\Message\StreamInterface;
  
-@@ -253,9 +254,7 @@ class ResponseTest extends TestCase
+@@ -135,9 +136,7 @@ class ResponseTest extends TestCase
+         self::assertSame('1000', $r->getProtocolVersion());
+     }
+ 
+-    /**
+-     * @dataProvider startLineSeparatorProvider
+-     */
++    #[DataProvider('startLineSeparatorProvider')]
+     public function testConstructWithLineSeparatorsInProtocolVersion(string $lineSeparator): void
+     {
+         $this->expectException(\InvalidArgumentException::class);
+@@ -145,9 +144,7 @@ class ResponseTest extends TestCase
+         new Response(200, [], null, '1.1'.$lineSeparator.'X-Injected: yes');
+     }
+ 
+-    /**
+-     * @dataProvider startLineSeparatorProvider
+-     */
++    #[DataProvider('startLineSeparatorProvider')]
+     public function testWithProtocolVersionRejectsLineSeparators(string $lineSeparator): void
+     {
+         $r = new Response();
+@@ -157,9 +154,7 @@ class ResponseTest extends TestCase
+         $r->withProtocolVersion('1.1'.$lineSeparator.'X-Injected: yes');
+     }
+ 
+-    /**
+-     * @dataProvider startLineSeparatorProvider
+-     */
++    #[DataProvider('startLineSeparatorProvider')]
+     public function testConstructWithLineSeparatorsInReasonPhrase(string $lineSeparator): void
+     {
+         $this->expectException(\InvalidArgumentException::class);
+@@ -167,9 +162,7 @@ class ResponseTest extends TestCase
+         new Response(200, [], null, '1.1', 'OK'.$lineSeparator.'X-Injected: yes');
+     }
+ 
+-    /**
+-     * @dataProvider startLineSeparatorProvider
+-     */
++    #[DataProvider('startLineSeparatorProvider')]
+     public function testWithStatusRejectsLineSeparatorsInReasonPhrase(string $lineSeparator): void
+     {
+         $r = new Response();
+@@ -304,9 +297,7 @@ class ResponseTest extends TestCase
          self::assertSame('bar', $r->getHeaderLine('123'));
      }
  
@@ -292,7 +380,7 @@
      public function testConstructResponseInvalidHeader($header, $headerValue, $expectedMessage): void
      {
          $this->expectException(\InvalidArgumentException::class);
-@@ -272,9 +271,7 @@ class ResponseTest extends TestCase
+@@ -323,9 +314,7 @@ class ResponseTest extends TestCase
          ];
      }
  
@@ -303,7 +391,7 @@
      public function testWithInvalidHeader($header, $headerValue, $expectedMessage): void
      {
          $r = new Response();
-@@ -323,10 +320,9 @@ class ResponseTest extends TestCase
+@@ -374,10 +363,9 @@ class ResponseTest extends TestCase
      }
  
      /**
@@ -315,7 +403,7 @@
      public function testConstructResponseWithNonIntegerStatusCode($invalidValues): void
      {
          $this->expectException(\TypeError::class);
-@@ -334,10 +330,9 @@ class ResponseTest extends TestCase
+@@ -385,10 +373,9 @@ class ResponseTest extends TestCase
      }
  
      /**
@@ -327,7 +415,7 @@
      public function testResponseChangeStatusCodeWithNonInteger($invalidValues): void
      {
          $response = new Response();
-@@ -357,10 +352,9 @@ class ResponseTest extends TestCase
+@@ -408,10 +395,9 @@ class ResponseTest extends TestCase
      }
  
      /**
@@ -339,7 +427,7 @@
      public function testConstructResponseWithInvalidRangeStatusCode($invalidValues): void
      {
          $this->expectException(\InvalidArgumentException::class);
-@@ -369,10 +363,9 @@ class ResponseTest extends TestCase
+@@ -420,10 +406,9 @@ class ResponseTest extends TestCase
      }
  
      /**
diff -Nru php-guzzlehttp-psr7-2.7.1/debian/patches/0009-Mitigate-CRLF-Injection-in-HTTP-Start-Line-Serializa.patch php-guzzlehttp-psr7-2.7.1/debian/patches/0009-Mitigate-CRLF-Injection-in-HTTP-Start-Line-Serializa.patch
--- php-guzzlehttp-psr7-2.7.1/debian/patches/0009-Mitigate-CRLF-Injection-in-HTTP-Start-Line-Serializa.patch	1970-01-01 01:00:00.000000000 +0100
+++ php-guzzlehttp-psr7-2.7.1/debian/patches/0009-Mitigate-CRLF-Injection-in-HTTP-Start-Line-Serializa.patch	2026-06-19 08:13:30.000000000 +0200
@@ -0,0 +1,297 @@
+From: Graham Campbell <[email protected]>
+Date: Thu, 18 Jun 2026 10:47:13 +0100
+Subject: Mitigate CRLF Injection in HTTP Start-Line Serialization (#798)
+
+Origin: backport, https://github.com/guzzle/psr7/commit/f3f94b4d344fa9788afb9558ac032923a9767380
+Bug: https://github.com/guzzle/psr7/security/advisories/GHSA-vm85-hxw5-5432
+Bug-Debian: https://security-tracker.debian.org/tracker/CVE-2026-55766
+---
+ src/Message.php        |  8 ++++++++
+ src/MessageTrait.php   | 19 +++++++++++++++++++
+ src/Request.php        |  4 ++++
+ src/Response.php       | 12 +++++++++---
+ tests/MessageTest.php  | 14 ++++++++++++++
+ tests/RequestTest.php  | 51 ++++++++++++++++++++++++++++++++++++++++++++++++++
+ tests/ResponseTest.php | 51 ++++++++++++++++++++++++++++++++++++++++++++++++++
+ 7 files changed, 156 insertions(+), 3 deletions(-)
+
+diff --git a/src/Message.php b/src/Message.php
+index d3aafca..3bd88eb 100644
+--- a/src/Message.php
++++ b/src/Message.php
+@@ -219,6 +219,10 @@ final class Message
+     public static function parseRequest(string $message): RequestInterface
+     {
+         $data = self::parseMessage($message);
++        if (strpbrk($data['start-line'], "\r\n") !== false) {
++            throw new \InvalidArgumentException('Invalid request string');
++        }
++
+         $matches = [];
+         if (!preg_match('/^[\S]+\s+([a-zA-Z]+:\/\/|\/).*/', $data['start-line'], $matches)) {
+             throw new \InvalidArgumentException('Invalid request string');
+@@ -245,6 +249,10 @@ final class Message
+     public static function parseResponse(string $message): ResponseInterface
+     {
+         $data = self::parseMessage($message);
++        if (strpbrk($data['start-line'], "\r\n") !== false) {
++            throw new \InvalidArgumentException('Invalid response string');
++        }
++
+         // According to https://datatracker.ietf.org/doc/html/rfc7230#section-3.1.2
+         // the space between status-code and reason-phrase is required. But
+         // browsers accept responses without space and reason as well.
+diff --git a/src/MessageTrait.php b/src/MessageTrait.php
+index 65dbc4b..8341ddb 100644
+--- a/src/MessageTrait.php
++++ b/src/MessageTrait.php
+@@ -31,6 +31,8 @@ trait MessageTrait
+ 
+     public function withProtocolVersion($version): MessageInterface
+     {
++        $this->assertProtocolVersion($version);
++
+         if ($this->protocol === $version) {
+             return $this;
+         }
+@@ -233,6 +235,23 @@ trait MessageTrait
+         }
+     }
+ 
++    /**
++     * @param mixed $version
++     */
++    private function assertProtocolVersion($version): void
++    {
++        if (is_string($version)) {
++            $this->assertNoLineSeparators($version, 'Protocol version');
++        }
++    }
++
++    private function assertNoLineSeparators(string $value, string $field): void
++    {
++        if (strpbrk($value, "\r\n") !== false) {
++            throw new \InvalidArgumentException($field.' must not contain CR or LF characters.');
++        }
++    }
++
+     /**
+      * @see https://datatracker.ietf.org/doc/html/rfc7230#section-3.2
+      *
+diff --git a/src/Request.php b/src/Request.php
+index 743c5d2..bae3349 100644
+--- a/src/Request.php
++++ b/src/Request.php
+@@ -40,6 +40,8 @@ class Request implements RequestInterface
+         string $version = '1.1'
+     ) {
+         $this->assertMethod($method);
++        $this->assertProtocolVersion($version);
++
+         if (!($uri instanceof UriInterface)) {
+             $uri = new Uri($uri);
+         }
+@@ -159,5 +161,7 @@ class Request implements RequestInterface
+         if (!is_string($method) || $method === '') {
+             throw new InvalidArgumentException('Method must be a non-empty string.');
+         }
++
++        $this->assertNoLineSeparators($method, 'Method');
+     }
+ }
+diff --git a/src/Response.php b/src/Response.php
+index 34e612f..1f10b67 100644
+--- a/src/Response.php
++++ b/src/Response.php
+@@ -99,6 +99,7 @@ class Response implements ResponseInterface
+         ?string $reason = null
+     ) {
+         $this->assertStatusCodeRange($status);
++        $this->assertProtocolVersion($version);
+ 
+         $this->statusCode = $status;
+ 
+@@ -108,11 +109,14 @@ class Response implements ResponseInterface
+ 
+         $this->setHeaders($headers);
+         if ($reason == '' && isset(self::PHRASES[$this->statusCode])) {
+-            $this->reasonPhrase = self::PHRASES[$this->statusCode];
++            $reasonPhrase = self::PHRASES[$this->statusCode];
+         } else {
+-            $this->reasonPhrase = (string) $reason;
++            $reasonPhrase = (string) $reason;
+         }
+ 
++        $this->assertNoLineSeparators($reasonPhrase, 'Reason phrase');
++        $this->reasonPhrase = $reasonPhrase;
++
+         $this->protocol = $version;
+     }
+ 
+@@ -137,7 +141,9 @@ class Response implements ResponseInterface
+         if ($reasonPhrase == '' && isset(self::PHRASES[$new->statusCode])) {
+             $reasonPhrase = self::PHRASES[$new->statusCode];
+         }
+-        $new->reasonPhrase = (string) $reasonPhrase;
++        $reasonPhrase = (string) $reasonPhrase;
++        $this->assertNoLineSeparators($reasonPhrase, 'Reason phrase');
++        $new->reasonPhrase = $reasonPhrase;
+ 
+         return $new;
+     }
+diff --git a/tests/MessageTest.php b/tests/MessageTest.php
+index 42909be..1862a95 100644
+--- a/tests/MessageTest.php
++++ b/tests/MessageTest.php
+@@ -231,6 +231,13 @@ class MessageTest extends TestCase
+         Psr7\Message::parseRequest("HTTP/1.1 200 OK\r\n\r\n");
+     }
+ 
++    public function testParseRequestRejectsStartLineWithBareCarriageReturn(): void
++    {
++        $this->expectException(\InvalidArgumentException::class);
++
++        Psr7\Message::parseRequest("GET / HTTP/1.1\rX-Injected: yes\nHost: foo.com\n\n");
++    }
++
+     public function testParsesResponseMessages(): void
+     {
+         $res = "HTTP/1.0 200 OK\r\nFoo: Bar\r\nBaz: Bam\r\nBaz: Qux\r\n\r\nTest";
+@@ -309,6 +316,13 @@ class MessageTest extends TestCase
+         Psr7\Message::parseResponse("GET / HTTP/1.1\r\n\r\n");
+     }
+ 
++    public function testParseResponseRejectsStartLineWithBareCarriageReturn(): void
++    {
++        $this->expectException(\InvalidArgumentException::class);
++
++        Psr7\Message::parseResponse("HTTP/1.1 200 OK\rX-Injected: yes\n\n");
++    }
++
+     public function testMessageBodySummaryWithSmallBody(): void
+     {
+         $message = new Psr7\Response(200, [], 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.');
+diff --git a/tests/RequestTest.php b/tests/RequestTest.php
+index 3a62c57..9acc022 100644
+--- a/tests/RequestTest.php
++++ b/tests/RequestTest.php
+@@ -125,6 +125,57 @@ class RequestTest extends TestCase
+         ];
+     }
+ 
++    /**
++     * @dataProvider startLineSeparatorProvider
++     */
++    public function testConstructWithLineSeparatorsInMethod(string $lineSeparator): void
++    {
++        $this->expectException(\InvalidArgumentException::class);
++
++        new Request('GET'.$lineSeparator.'X-Injected: yes', '/');
++    }
++
++    /**
++     * @dataProvider startLineSeparatorProvider
++     */
++    public function testWithMethodRejectsLineSeparators(string $lineSeparator): void
++    {
++        $r = new Request('GET', '/');
++
++        $this->expectException(\InvalidArgumentException::class);
++
++        $r->withMethod('GET'.$lineSeparator.'X-Injected: yes');
++    }
++
++    /**
++     * @dataProvider startLineSeparatorProvider
++     */
++    public function testConstructWithLineSeparatorsInProtocolVersion(string $lineSeparator): void
++    {
++        $this->expectException(\InvalidArgumentException::class);
++
++        new Request('GET', '/', [], null, '1.1'.$lineSeparator.'X-Injected: yes');
++    }
++
++    /**
++     * @dataProvider startLineSeparatorProvider
++     */
++    public function testWithProtocolVersionRejectsLineSeparators(string $lineSeparator): void
++    {
++        $r = new Request('GET', '/');
++
++        $this->expectException(\InvalidArgumentException::class);
++
++        $r->withProtocolVersion('1.1'.$lineSeparator.'X-Injected: yes');
++    }
++
++    public static function startLineSeparatorProvider(): iterable
++    {
++        yield 'line feed' => ["\n"];
++        yield 'carriage return' => ["\r"];
++        yield 'CRLF' => ["\r\n"];
++    }
++
+     public function testSameInstanceWhenSameUri(): void
+     {
+         $r1 = new Request('GET', 'http://foo.com');
+diff --git a/tests/ResponseTest.php b/tests/ResponseTest.php
+index 70d3ecc..d15d726 100644
+--- a/tests/ResponseTest.php
++++ b/tests/ResponseTest.php
+@@ -135,6 +135,57 @@ class ResponseTest extends TestCase
+         self::assertSame('1000', $r->getProtocolVersion());
+     }
+ 
++    /**
++     * @dataProvider startLineSeparatorProvider
++     */
++    public function testConstructWithLineSeparatorsInProtocolVersion(string $lineSeparator): void
++    {
++        $this->expectException(\InvalidArgumentException::class);
++
++        new Response(200, [], null, '1.1'.$lineSeparator.'X-Injected: yes');
++    }
++
++    /**
++     * @dataProvider startLineSeparatorProvider
++     */
++    public function testWithProtocolVersionRejectsLineSeparators(string $lineSeparator): void
++    {
++        $r = new Response();
++
++        $this->expectException(\InvalidArgumentException::class);
++
++        $r->withProtocolVersion('1.1'.$lineSeparator.'X-Injected: yes');
++    }
++
++    /**
++     * @dataProvider startLineSeparatorProvider
++     */
++    public function testConstructWithLineSeparatorsInReasonPhrase(string $lineSeparator): void
++    {
++        $this->expectException(\InvalidArgumentException::class);
++
++        new Response(200, [], null, '1.1', 'OK'.$lineSeparator.'X-Injected: yes');
++    }
++
++    /**
++     * @dataProvider startLineSeparatorProvider
++     */
++    public function testWithStatusRejectsLineSeparatorsInReasonPhrase(string $lineSeparator): void
++    {
++        $r = new Response();
++
++        $this->expectException(\InvalidArgumentException::class);
++
++        $r->withStatus(200, 'OK'.$lineSeparator.'X-Injected: yes');
++    }
++
++    public static function startLineSeparatorProvider(): iterable
++    {
++        yield 'line feed' => ["\n"];
++        yield 'carriage return' => ["\r"];
++        yield 'CRLF' => ["\r\n"];
++    }
++
+     public function testSameInstanceWhenSameProtocol(): void
+     {
+         $r = new Response();
diff -Nru php-guzzlehttp-psr7-2.7.1/debian/patches/series php-guzzlehttp-psr7-2.7.1/debian/patches/series
--- php-guzzlehttp-psr7-2.7.1/debian/patches/series	2026-05-30 13:39:01.000000000 +0200
+++ php-guzzlehttp-psr7-2.7.1/debian/patches/series	2026-06-19 08:13:30.000000000 +0200
@@ -6,4 +6,5 @@
 0006-Normalize-global-header-values-718.patch
 0007-Reject-malformed-Host-authorities-717.patch
 0008-Reject-control-characters-in-URI-hosts-715.patch
+0009-Mitigate-CRLF-Injection-in-HTTP-Start-Line-Serializa.patch
 0004-Modernize-PHPUnit-syntax.patch

Attachment: signature.asc
Description: PGP signature

Reply via email to