VYPR
Medium severity5.3GHSA Advisory· Published Jun 11, 2026· Updated Jun 11, 2026

CVE-2026-48998

CVE-2026-48998

Description

Malformed Host header parsing in guzzlehttp/psr7 versions prior to 2.10.2 allows an attacker to redirect requests or credentials to an unintended host via userinfo injection.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Malformed Host header parsing in guzzlehttp/psr7 versions prior to 2.10.2 allows an attacker to redirect requests or credentials to an unintended host via userinfo injection.

Vulnerability

This flaw resides in the guzzlehttp/psr7 library's Host header validation when parsing raw HTTP request messages via GuzzleHttp\Psr7\Message::parseRequest() or the legacy 1.x GuzzleHttp\Psr7\parse_request() function, and when deriving server request URIs from server variables via ServerRequest::fromGlobals() or ServerRequest::getUriFromGlobals(). In versions prior to 2.10.2, a malformed Host header such as trusted.example@evil.example is improperly reinterpreted: the URI parser treats the portion before @ as userinfo and the portion after @ as the hostname. This causes the PSR‑7 request URI host to differ from the original Host header value [2][3]. Version 1.x is end‑of‑life and will not receive a patch [1][2][3].

Exploitation

An attacker must control either a raw HTTP request or server variables (e.g., via a gateway, reverse proxy, or manipulated input) and supply a malicious Host header containing URI authority delimiters. The attacker sends a request with a Host value like trusted.example@evil.example. When the vulnerable code constructs a URI from this value, the resulting URI's host becomes evil.example while the Host header remains unchanged. No special authentication, write access, or user interaction is required beyond controlling the Host header input [2][3].

Impact

If an application relies on the derived URI host for routing decisions, allow‑list checks, credential selection, or forwarding, this host confusion may direct requests or sensitive credentials to an unintended host controlled by the attacker. This is a host‑confusion attack that can lead to information disclosure, credential leakage, or unintended forwarding in gateway scenarios. The CIA impact is primarily confidentiality (credential or request data disclosure) and integrity (misrouting) [2][3].

Mitigation

Upgrade to 2.10.2 or later, which contains the fix. Version 1.x is end‑of‑life (EOL as of 2024‑06‑30) and will not receive a patch; users must migrate to 2.x. As a workaround, validate Host headers before passing untrusted data to the vulnerable functions: accept only the format uri-host [ ":" port ] and reject any value containing @, path, query, or fragment delimiters [1][2][3].

AI Insight generated on Jun 11, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected products

1

Patches

2
c68fe44ea6b5

Reject malformed Host authorities (#717)

https://github.com/guzzle/psr7Graham CampbellMay 25, 2026Fixed in 2.10.2via ghsa-release-walk
8 files changed · +289 30
  • CHANGELOG.md+1 0 modified
    @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
     ### Security
     
     - Reject control and whitespace characters in URI host components (GHSA-hq7v-mx3g-29hw)
    +- Reject malformed Host values when constructing request URIs (GHSA-34xg-wgjx-8xph)
     
     ### Fixed
     
    
  • src/Message.php+22 4 modified
    @@ -233,6 +233,23 @@ public static function parseMessage(string $message): array
          * @param array  $headers Array of headers (each value an array).
          */
         public static function parseRequestUri(string $path, array $headers): string
    +    {
    +        $host = self::getHostFromHeaders($headers);
    +
    +        // If no host is found, then a full URI cannot be constructed.
    +        if ($host === null) {
    +            return $path;
    +        }
    +
    +        $scheme = substr($host, -4) === ':443' ? 'https' : 'http';
    +
    +        return $scheme.'://'.$host.'/'.ltrim($path, '/');
    +    }
    +
    +    /**
    +     * @param array $headers Array of headers (each value an array).
    +     */
    +    private static function getHostFromHeaders(array $headers): ?string
         {
             $hostKey = array_filter(array_keys($headers), function ($k) {
                 // Numeric array keys are converted to int by PHP.
    @@ -241,15 +258,16 @@ public static function parseRequestUri(string $path, array $headers): string
                 return strtolower($k) === 'host';
             });
     
    -        // If no host is found, then a full URI cannot be constructed.
             if (!$hostKey) {
    -            return $path;
    +            return null;
             }
     
             $host = $headers[reset($hostKey)][0];
    -        $scheme = substr($host, -4) === ':443' ? 'https' : 'http';
    +        if (!is_string($host) || Rfc7230::parseHostHeader($host) === null) {
    +            throw new \InvalidArgumentException('Invalid request string');
    +        }
     
    -        return $scheme.'://'.$host.'/'.ltrim($path, '/');
    +        return $host;
         }
     
         /**
    
  • src/Rfc3986.php+25 0 added
    @@ -0,0 +1,25 @@
    +<?php
    +
    +declare(strict_types=1);
    +
    +namespace GuzzleHttp\Psr7;
    +
    +/**
    + * @internal
    + */
    +final class Rfc3986
    +{
    +    /**
    +     * Sub-delims for use in a regex.
    +     *
    +     * @see https://datatracker.ietf.org/doc/html/rfc3986#section-2.2
    +     */
    +    public const CHAR_SUB_DELIMS = '!\$&\'\(\)\*\+,;=';
    +
    +    /**
    +     * Unreserved characters for use in a regex.
    +     *
    +     * @see https://datatracker.ietf.org/doc/html/rfc3986#section-2.3
    +     */
    +    public const CHAR_UNRESERVED = 'a-zA-Z0-9_\-\.~';
    +}
    
  • src/Rfc7230.php+83 0 modified
    @@ -20,4 +20,87 @@ final class Rfc7230
          */
         public const HEADER_REGEX = "(^([^()<>@,;:\\\"/[\]?={}\x01-\x20\x7F]++):[ \t]*+((?:[ \t]*+[\x21-\x7E\x80-\xFF]++)*+)[ \t]*+\r?\n)m";
         public const HEADER_FOLD_REGEX = "(\r?\n[ \t]++)";
    +
    +    /**
    +     * @return array{0: string, 1: int|null}|null
    +     */
    +    public static function parseHostHeader(string $authority): ?array
    +    {
    +        if ($authority === '') {
    +            return null;
    +        }
    +
    +        $host = $authority;
    +        $port = null;
    +
    +        if ($authority[0] === '[') {
    +            $closingBracket = strpos($authority, ']');
    +            if ($closingBracket === false) {
    +                return null;
    +            }
    +
    +            $host = substr($authority, 0, $closingBracket + 1);
    +            $remainder = substr($authority, $closingBracket + 1);
    +            if ($remainder !== '') {
    +                if ($remainder[0] !== ':') {
    +                    return null;
    +                }
    +
    +                $port = self::parseAuthorityPort(substr($remainder, 1));
    +                if ($port === null) {
    +                    return null;
    +                }
    +            }
    +        } elseif (false !== ($colon = strpos($authority, ':'))) {
    +            $host = substr($authority, 0, $colon);
    +            $port = self::parseAuthorityPort(substr($authority, $colon + 1));
    +            if ($port === null) {
    +                return null;
    +            }
    +        }
    +
    +        if ($host === '' || !self::isValidHostHeaderHost($host)) {
    +            return null;
    +        }
    +
    +        return [$host, $port];
    +    }
    +
    +    private static function isValidHostHeaderHost(string $host): bool
    +    {
    +        if (preg_match('/[\x00-\x20\x7F\/\?#@\\\\]/', $host)) {
    +            return false;
    +        }
    +
    +        if (strpos($host, '[') !== false || strpos($host, ']') !== false) {
    +            if ($host[0] !== '[' || substr($host, -1) !== ']') {
    +                return false;
    +            }
    +
    +            $address = substr($host, 1, -1);
    +
    +            return filter_var($address, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6) !== false
    +                || preg_match('/^v[0-9a-f]+\.['.Rfc3986::CHAR_UNRESERVED.Rfc3986::CHAR_SUB_DELIMS.':]+$/iD', $address) === 1;
    +        }
    +
    +        return strpos($host, ':') === false;
    +    }
    +
    +    private static function parseAuthorityPort(string $port): ?int
    +    {
    +        if ($port === '' || !ctype_digit($port)) {
    +            return null;
    +        }
    +
    +        $normalized = ltrim($port, '0');
    +        if ($normalized === '') {
    +            return 0;
    +        }
    +
    +        if (strlen($normalized) > 5 || (int) $normalized > 0xFFFF) {
    +            return null;
    +        }
    +
    +        return (int) $normalized;
    +    }
     }
    
  • src/ServerRequest.php+21 10 modified
    @@ -166,7 +166,7 @@ private static function normalizeNestedFileSpec(array $files = []): array
         public static function fromGlobals(): ServerRequestInterface
         {
             $method = self::getServerParam('REQUEST_METHOD') ?? 'GET';
    -        $headers = self::getAllHeaders();
    +        $headers = self::removeInvalidHostHeader(self::getAllHeaders());
             $uri = self::getUriFromGlobals();
             $body = new CachingStream(new LazyOpenStream('php://input', 'r+'));
             $serverProtocol = self::getServerParam('SERVER_PROTOCOL');
    @@ -213,20 +213,31 @@ private static function getServerParam(string $key): ?string
         }
     
         /**
    -     * @return array{0: string|null, 1: int|null}
    +     * @param array<array-key, string> $headers
    +     *
    +     * @return array<array-key, string>
          */
    -    private static function extractHostAndPortFromAuthority(string $authority): array
    +    private static function removeInvalidHostHeader(array $headers): array
         {
    -        $uri = 'http://'.$authority;
    -        $parts = parse_url($uri);
    -        if (!is_array($parts)) {
    -            return [null, null];
    +        foreach ($headers as $name => $value) {
    +            if (strtolower((string) $name) !== 'host') {
    +                continue;
    +            }
    +
    +            if (Rfc7230::parseHostHeader($value) === null) {
    +                unset($headers[$name]);
    +            }
             }
     
    -        $host = $parts['host'] ?? null;
    -        $port = $parts['port'] ?? null;
    +        return $headers;
    +    }
     
    -        return [$host, $port];
    +    /**
    +     * @return array{0: string|null, 1: int|null}
    +     */
    +    private static function extractHostAndPortFromAuthority(string $authority): array
    +    {
    +        return Rfc7230::parseHostHeader($authority) ?? [null, null];
         }
     
         /**
    
  • src/Uri.php+3 16 modified
    @@ -38,19 +38,6 @@ class Uri implements UriInterface, \JsonSerializable
             'ldap' => 389,
         ];
     
    -    /**
    -     * Unreserved characters for use in a regex.
    -     *
    -     * @see https://datatracker.ietf.org/doc/html/rfc3986#section-2.3
    -     */
    -    private const CHAR_UNRESERVED = 'a-zA-Z0-9_\-\.~';
    -
    -    /**
    -     * Sub-delims for use in a regex.
    -     *
    -     * @see https://datatracker.ietf.org/doc/html/rfc3986#section-2.2
    -     */
    -    private const CHAR_SUB_DELIMS = '!\$&\'\(\)\*\+,;=';
         private const QUERY_SEPARATORS_REPLACEMENT = ['=' => '%3D', '&' => '%26', '+' => '%2B'];
     
         /** @var string Uri scheme. */
    @@ -647,7 +634,7 @@ private function filterUserInfoComponent($component): string
             }
     
             return preg_replace_callback(
    -            '/(?:[^%'.self::CHAR_UNRESERVED.self::CHAR_SUB_DELIMS.']+|%(?![A-Fa-f0-9]{2}))/',
    +            '/(?:[^%'.Rfc3986::CHAR_UNRESERVED.Rfc3986::CHAR_SUB_DELIMS.']+|%(?![A-Fa-f0-9]{2}))/',
                 [$this, 'rawurlencodeMatchZero'],
                 $component
             );
    @@ -749,7 +736,7 @@ private function filterPath($path): string
             }
     
             return preg_replace_callback(
    -            '/(?:[^'.self::CHAR_UNRESERVED.self::CHAR_SUB_DELIMS.'%:@\/]++|%(?![A-Fa-f0-9]{2}))/',
    +            '/(?:[^'.Rfc3986::CHAR_UNRESERVED.Rfc3986::CHAR_SUB_DELIMS.'%:@\/]++|%(?![A-Fa-f0-9]{2}))/',
                 [$this, 'rawurlencodeMatchZero'],
                 $path
             );
    @@ -769,7 +756,7 @@ private function filterQueryAndFragment($str): string
             }
     
             return preg_replace_callback(
    -            '/(?:[^'.self::CHAR_UNRESERVED.self::CHAR_SUB_DELIMS.'%:@\/\?]++|%(?![A-Fa-f0-9]{2}))/',
    +            '/(?:[^'.Rfc3986::CHAR_UNRESERVED.Rfc3986::CHAR_SUB_DELIMS.'%:@\/\?]++|%(?![A-Fa-f0-9]{2}))/',
                 [$this, 'rawurlencodeMatchZero'],
                 $str
             );
    
  • tests/MessageTest.php+56 0 modified
    @@ -107,6 +107,62 @@ public function testParsesRequestMessagesWithUriWhenHostIsNotFirst(): void
             self::assertSame('http://foo.com/', (string) $request->getUri());
         }
     
    +    /**
    +     * @dataProvider invalidHostHeaderProvider
    +     */
    +    public function testParseRequestRejectsInvalidHostHeader(string $host): void
    +    {
    +        $this->expectException(\InvalidArgumentException::class);
    +
    +        Psr7\Message::parseRequest("GET / HTTP/1.1\r\nHost: {$host}\r\n\r\n");
    +    }
    +
    +    public static function invalidHostHeaderProvider(): iterable
    +    {
    +        yield 'userinfo delimiter' => ['trusted.example@evil.example'];
    +        yield 'path delimiter' => ['example.com/path'];
    +        yield 'query delimiter' => ['example.com?query'];
    +        yield 'fragment delimiter' => ['example.com#fragment'];
    +        yield 'backslash delimiter' => ['example.com\\evil'];
    +        yield 'space' => ['bad host'];
    +        yield 'tab' => ["bad\thost"];
    +        yield 'control character' => ['example'.chr(1).'com'];
    +        yield 'delete' => ['example'.chr(0x7F).'com'];
    +        yield 'multiple ports' => ['example.com:443:8443'];
    +        yield 'missing closing bracket' => ['[::1'];
    +        yield 'unexpected bracket suffix' => ['[::1]x'];
    +        yield 'invalid ip literal' => ['[bad]'];
    +        yield 'unexpected opening bracket' => ['foo[bar'];
    +        yield 'unexpected closing bracket' => ['foo]bar'];
    +    }
    +
    +    /**
    +     * @dataProvider validHostHeaderProvider
    +     */
    +    public function testParseRequestAcceptsValidHostHeader(string $host, string $expectedUri): void
    +    {
    +        $request = Psr7\Message::parseRequest("GET / HTTP/1.1\r\nHost: {$host}\r\n\r\n");
    +
    +        self::assertSame($host, $request->getHeaderLine('Host'));
    +        self::assertSame($expectedUri, (string) $request->getUri());
    +    }
    +
    +    public static function validHostHeaderProvider(): iterable
    +    {
    +        yield 'host' => ['foo.com', 'http://foo.com/'];
    +        yield 'https default port' => ['foo.com:443', 'https://foo.com/'];
    +        yield 'non-default port' => ['foo.com:8080', 'http://foo.com:8080/'];
    +        yield 'ipv6' => ['[::1]', 'http://[::1]/'];
    +        yield 'ipv6 port' => ['[::1]:443', 'https://[::1]/'];
    +    }
    +
    +    public function testParseRequestAcceptsMissingHostHeader(): void
    +    {
    +        $request = Psr7\Message::parseRequest("GET /abc HTTP/1.1\r\nFoo: bar\r\n\r\n");
    +
    +        self::assertSame('/abc', (string) $request->getUri());
    +    }
    +
         public function testParsesRequestMessagesWithFullUri(): void
         {
             $req = "GET https://www.google.com:443/search?q=foobar HTTP/1.1\r\nHost: www.google.com\r\n\r\n";
    
  • tests/ServerRequestTest.php+78 0 modified
    @@ -354,6 +354,46 @@ public static function dataGetUriFromGlobals(): iterable
                     'https://localhost/blog/article.php?id=10&user=foo',
                     array_merge($server, ['HTTP_HOST' => 'a:b']),
                 ],
    +            'Host header with userinfo delimiter' => [
    +                'https://localhost/blog/article.php?id=10&user=foo',
    +                array_merge($server, ['HTTP_HOST' => 'trusted.example@evil.example']),
    +            ],
    +            'Host header with path delimiter' => [
    +                'https://localhost/blog/article.php?id=10&user=foo',
    +                array_merge($server, ['HTTP_HOST' => 'example.com/path']),
    +            ],
    +            'Host header with query delimiter' => [
    +                'https://localhost/blog/article.php?id=10&user=foo',
    +                array_merge($server, ['HTTP_HOST' => 'example.com?x=1']),
    +            ],
    +            'Host header with fragment delimiter' => [
    +                'https://localhost/blog/article.php?id=10&user=foo',
    +                array_merge($server, ['HTTP_HOST' => 'example.com#frag']),
    +            ],
    +            'Host header with backslash delimiter' => [
    +                'https://localhost/blog/article.php?id=10&user=foo',
    +                array_merge($server, ['HTTP_HOST' => 'example.com\\evil']),
    +            ],
    +            'Host header with space' => [
    +                'https://localhost/blog/article.php?id=10&user=foo',
    +                array_merge($server, ['HTTP_HOST' => 'bad host']),
    +            ],
    +            'Host header with multiple ports' => [
    +                'https://localhost/blog/article.php?id=10&user=foo',
    +                array_merge($server, ['HTTP_HOST' => 'example.com:80:90']),
    +            ],
    +            'Host header with invalid ip literal' => [
    +                'https://localhost/blog/article.php?id=10&user=foo',
    +                array_merge($server, ['HTTP_HOST' => '[bad]']),
    +            ],
    +            'Host header with unexpected opening bracket' => [
    +                'https://localhost/blog/article.php?id=10&user=foo',
    +                array_merge($server, ['HTTP_HOST' => 'foo[bar']),
    +            ],
    +            'Host header with unexpected closing bracket' => [
    +                'https://localhost/blog/article.php?id=10&user=foo',
    +                array_merge($server, ['HTTP_HOST' => 'foo]bar']),
    +            ],
                 'Different port with SERVER_PORT' => [
                     'https://www.example.org:8324/blog/article.php?id=10&user=foo',
                     array_merge($server, ['SERVER_PORT' => '8324']),
    @@ -536,6 +576,44 @@ public function testFromGlobalsDefaultsNonStringMethodAndProtocol(): void
             self::assertSame('1.1', $server->getProtocolVersion());
         }
     
    +    /**
    +     * @dataProvider invalidHostHeaderFromGlobalsProvider
    +     */
    +    public function testFromGlobalsDropsInvalidHostHeaderWhenUriFallsBack(string $host): void
    +    {
    +        if (!\function_exists('getallheaders')) {
    +            self::markTestSkipped('getallheaders() is not available.');
    +        }
    +
    +        $_SERVER = [
    +            'REQUEST_URI' => '/',
    +            'HTTP_HOST' => $host,
    +            'SERVER_PORT' => '443',
    +            'HTTPS' => 'on',
    +        ];
    +
    +        $_COOKIE = $_POST = $_GET = $_FILES = [];
    +
    +        $request = ServerRequest::fromGlobals();
    +
    +        self::assertSame('localhost', $request->getUri()->getHost());
    +        self::assertSame('localhost', $request->getHeaderLine('Host'));
    +    }
    +
    +    public static function invalidHostHeaderFromGlobalsProvider(): iterable
    +    {
    +        yield 'userinfo delimiter' => ['trusted.example@evil.example'];
    +        yield 'path delimiter' => ['example.com/path'];
    +        yield 'query delimiter' => ['example.com?x=1'];
    +        yield 'fragment delimiter' => ['example.com#frag'];
    +        yield 'backslash delimiter' => ['example.com\\evil'];
    +        yield 'space' => ['bad host'];
    +        yield 'multiple ports' => ['example.com:80:90'];
    +        yield 'invalid ip literal' => ['[bad]'];
    +        yield 'unexpected opening bracket' => ['foo[bar'];
    +        yield 'unexpected closing bracket' => ['foo]bar'];
    +    }
    +
         public function testUploadedFiles(): void
         {
             $request1 = new ServerRequest('GET', '/');
    
12caca7f2302

Reject control characters in URI hosts (#715)

https://github.com/guzzle/psr7Graham CampbellMay 25, 2026Fixed in 2.10.2via ghsa-release-walk
5 files changed · +94 4
  • CHANGELOG.md+6 0 modified
    @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
     The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
     and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
     
    +## 2.10.2 - TBD
    +
    +### Security
    +
    +- Reject control and whitespace characters in URI host components (GHSA-hq7v-mx3g-29hw)
    +
     ## 2.10.1 - 2026-05-20
     
     ### Fixed
    
  • src/Request.php+4 0 modified
    @@ -132,10 +132,14 @@ private function updateHostFromUri(): void
                 return;
             }
     
    +        Uri::assertValidHost($host);
    +
             if (($port = $this->uri->getPort()) !== null) {
                 $host .= ':'.$port;
             }
     
    +        $this->assertValue($host);
    +
             if (isset($this->headerNames['host'])) {
                 $header = $this->headerNames['host'];
             } else {
    
  • src/Uri.php+35 4 modified
    @@ -81,7 +81,13 @@ public function __construct(string $uri = '')
                 if ($parts === false) {
                     throw new MalformedUriException("Unable to parse URI: $uri");
                 }
    -            $this->applyParts($parts);
    +            try {
    +                $this->applyParts($parts);
    +            } catch (MalformedUriException $e) {
    +                throw $e;
    +            } catch (\InvalidArgumentException $e) {
    +                throw new MalformedUriException($e->getMessage(), 0, $e);
    +            }
             }
         }
     
    @@ -390,12 +396,34 @@ public static function withQueryValues(UriInterface $uri, array $keyValueArray):
         public static function fromParts(array $parts): UriInterface
         {
             $uri = new self();
    -        $uri->applyParts($parts);
    -        $uri->validateState();
    +        try {
    +            $uri->applyParts($parts);
    +            $uri->validateState();
    +        } catch (MalformedUriException $e) {
    +            throw $e;
    +        } catch (\InvalidArgumentException $e) {
    +            throw new MalformedUriException($e->getMessage(), 0, $e);
    +        }
     
             return $uri;
         }
     
    +    /**
    +     * @throws \InvalidArgumentException If the host is invalid.
    +     *
    +     * @internal
    +     */
    +    public static function assertValidHost(string $host): void
    +    {
    +        if ($host === '') {
    +            return;
    +        }
    +
    +        if (preg_match('/[\x00-\x20\x7F]/', $host)) {
    +            throw new \InvalidArgumentException(sprintf('Invalid host: "%s"', $host));
    +        }
    +    }
    +
         public function getScheme(): string
         {
             return $this->scheme;
    @@ -636,7 +664,10 @@ private function filterHost($host): string
                 throw new \InvalidArgumentException('Host must be a string');
             }
     
    -        return \strtr($host, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz');
    +        $host = \strtr($host, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz');
    +        self::assertValidHost($host);
    +
    +        return $host;
         }
     
         /**
    
  • tests/RequestTest.php+23 0 modified
    @@ -9,6 +9,7 @@
     use GuzzleHttp\Psr7\Uri;
     use PHPUnit\Framework\TestCase;
     use Psr\Http\Message\StreamInterface;
    +use Psr\Http\Message\UriInterface;
     
     /**
      * @covers \GuzzleHttp\Psr7\MessageTrait
    @@ -314,6 +315,28 @@ public function testAddsPortToHeaderAndReplacePreviousPort(): void
             self::assertSame('foo.com:8125', $r->getHeaderLine('host'));
         }
     
    +    public function testGeneratedHostHeaderRejectsInvalidUriHostFromCustomUri(): void
    +    {
    +        $uri = $this->createMock(UriInterface::class);
    +        $uri->method('getHost')->willReturn("foo\nbar");
    +        $uri->method('getPort')->willReturn(null);
    +
    +        $this->expectException(\InvalidArgumentException::class);
    +
    +        new Request('GET', $uri);
    +    }
    +
    +    public function testGeneratedHostHeaderValidatesAssembledHostWithPort(): void
    +    {
    +        $uri = $this->createMock(UriInterface::class);
    +        $uri->method('getHost')->willReturn('example.com');
    +        $uri->method('getPort')->willReturn(8080);
    +
    +        $request = new Request('GET', $uri);
    +
    +        self::assertSame('example.com:8080', $request->getHeaderLine('Host'));
    +    }
    +
         /**
          * @dataProvider provideHeaderValuesContainingNotAllowedChars
          */
    
  • tests/UriTest.php+26 0 modified
    @@ -214,6 +214,32 @@ public function testHostMustHaveCorrectType(): void
             (new Uri())->withHost([]);
         }
     
    +    /**
    +     * @dataProvider getInvalidHostsWithControlCharacters
    +     */
    +    public function testHostMustRejectControlCharacters(string $host): void
    +    {
    +        $this->expectException(\InvalidArgumentException::class);
    +
    +        (new Uri())->withHost($host);
    +    }
    +
    +    public static function getInvalidHostsWithControlCharacters(): iterable
    +    {
    +        for ($i = 0; $i <= 0x20; ++$i) {
    +            yield 'ascii 0x'.strtoupper(dechex($i)) => ['example'.chr($i).'com'];
    +        }
    +
    +        yield 'ascii 0x7F' => ['example'.chr(0x7F).'com'];
    +    }
    +
    +    public function testParseUriRejectsHostWithControlCharacter(): void
    +    {
    +        $this->expectException(MalformedUriException::class);
    +
    +        new Uri("http://example.com\r\nX-Injected:%20yes/");
    +    }
    +
         public function testPathMustHaveCorrectType(): void
         {
             $this->expectException(\InvalidArgumentException::class);
    

Vulnerability mechanics

No source-code context for this CVE — mechanics is only generated when we can read the actual fix diff. Without that, the four sections (root cause, attack vector, affected code, fix) would be speculation rather than analysis.

References

2

News mentions

0

No linked articles in our index yet.