symfony/http-foundation · error · BadRequestException

Invalid URI: Userinfo is malformed.

Error message

Invalid URI: Userinfo is malformed.

What it means

Request::create() validates every component of the URI it is asked to turn into a Request before constructing it. This check guards against userinfo-spoofing/open-redirect tricks: if the userinfo part (user:pass before @) contains '[' or ']', the URI is ambiguous or malicious (e.g. 'http://normal.com[@evil.com/') and the library rejects it with a BadRequestException (HTTP 400). It means the caller passed a syntactically unsafe URI string, not a transient failure.

Solutions

  1. URL-encode or remove '[' and ']' from the userinfo (user:password) portion of the URI before passing it to Request::create().
  2. Use rawurlencode() on the username and password separately and rebuild the URI: scheme://rawurlencode($user).':'.rawurlencode($pass).'@host'.
  3. If the brackets are meant for the host (IPv6 literal like http://[::1]/), ensure they are in the host component, not the userinfo, and that the URI parses as intended.
  4. Wrap the call in try/catch for BadRequestException when URLs come from untrusted input and return a 400 to the client.

Example fix

// before
$request = Request::create('http://normal.com[@vulndetector.com/');
// after
$uri = 'http://' . rawurlencode('normal.com[') . '@vulndetector.com/';
// or better: reject it up front
if (preg_match('#^[a-z][a-z0-9+.-]*://[^/?#@]*\[|^https?://[^/?#]*\][^/]#i', $uri)) {
    throw new \InvalidArgumentException('URI userinfo contains brackets');
}
$request = Request::create($uri);
Defensive patterns

Strategy: validation

Validate before calling

function hasSafeUserinfo(string $uri): bool
{
    $u = parse_url($uri);
    if (!isset($u['user']) && !isset($u['pass'])) {
        return true;
    }
    $part = ($u['user'] ?? '') . ':' . ($u['pass'] ?? '');

    return ':' === $part || strlen($part) === strcspn($part, '[]');
}
if (!hasSafeUserinfo($uri)) {
    throw new \InvalidArgumentException('URI userinfo contains brackets');
}

Type guard

function isCreatableUri(string $uri): bool
{
    return '' !== $uri
        && false === strpos($uri, '\\')
        && strlen($uri) === strcspn($uri, "\r\n\t")
        && ord($uri[0]) > 32 && ord($uri[-1]) > 32;
}

Try / catch

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Exception\BadRequestException;

try {
    $request = Request::create($uri);
} catch (BadRequestException $e) {
    return new Response('Invalid URL supplied.', 400);
}

Prevention

When it happens

Trigger: Calling Request::create() (or Request::createRequestUri-based factory paths) with a URI whose userinfo component contains square brackets, e.g. 'http://user[name]:pass@host/' or spoofing forms like 'http://normal.com[@vulndetector.com/' or 'http://[normal.com@vulndetector.com/'. parse_url() accepted the URI but the strict post-parse check in Request.php:401-403 (strlen($part) !== strcspn($part, '[]')) fails.

Common situations: Constructing requests programmatically from user-supplied URLs; SSRF/open-redirect test suites; proxies or crawlers feeding raw URLs into Request::create(); copy-pasted URLs containing brackets in credentials; security scanners deliberately probing host-header confusion.

Understand the failure class

Background: "Invalid URL" errors: why new URL(), URI.parse, and reqwest::Url reject your string — missing scheme, whitespace, and bad path format — this error's family across 39 libraries.

Related errors


AI-assisted analysis of symfony/http-foundation@5aea19cd67 (2026-09-13). Data as JSON: /api/errors/2f89fc5fbf784158. Report an issue: GitHub.

Appendix: source

Thrown at Request.php:403

            'SERVER_PROTOCOL' => 'HTTP/1.1',
            'REQUEST_TIME' => time(),
            'REQUEST_TIME_FLOAT' => microtime(true),
        ], $server);

        $server['PATH_INFO'] = '';
        $server['REQUEST_METHOD'] = strtoupper($method);

        if (($i = strcspn($uri, ':/?#')) && ':' === ($uri[$i] ?? null) && (strspn($uri, 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+-.') !== $i || strcspn($uri, 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'))) {
            throw new BadRequestException('Invalid URI: Scheme is malformed.');
        }
        if (false === $components = parse_url(\strlen($uri) !== strcspn($uri, '?#') ? $uri : $uri.'#')) {
            throw new BadRequestException('Invalid URI.');
        }

        $part = ($components['user'] ?? '').':'.($components['pass'] ?? '');

        if (':' !== $part && \strlen($part) !== strcspn($part, '[]')) {
            throw new BadRequestException('Invalid URI: Userinfo is malformed.');
        }
        if (($part = $components['host'] ?? '') && !self::isHostValid($part)) {
            throw new BadRequestException('Invalid URI: Host is malformed.');
        }
        if (false !== ($i = strpos($uri, '\\')) && $i < strcspn($uri, '?#')) {
            throw new BadRequestException('Invalid URI: A URI cannot contain a backslash.');
        }
        if (\strlen($uri) !== strcspn($uri, "\r\n\t")) {
            throw new BadRequestException('Invalid URI: A URI cannot contain CR/LF/TAB characters.');
        }
        if ('' !== $uri && (\ord($uri[0]) <= 32 || \ord($uri[-1]) <= 32)) {
            throw new BadRequestException('Invalid URI: A URI must not start nor end with ASCII control characters or spaces.');
        }

        if (isset($components['host'])) {
            $server['SERVER_NAME'] = $components['host'];
            $server['HTTP_HOST'] = $components['host'];
        }

View on GitHub (pinned to 5aea19cd67)