oobabooga/textgen · error · ValueError

Invalid URL: userinfo (credentials) in URLs is not allowed

Error message

Invalid URL: userinfo (credentials) in URLs is not allowed

What it means

Raised by _validate_url() when the URL's network location contains an '@', i.e. embedded userinfo ('user:pass@host'). Different URL parsers and HTTP clients disagree about where the host begins when userinfo is present, which has been used to bypass SSRF host checks (the validator sees one host, the fetcher connects to another). It is rejected unconditionally, even for userinfo without a password.

Source

Thrown at modules/web_search.py:27

from ddgs import DDGS

from modules import shared
from modules.logging_colors import logger


def _validate_url(url):
    """Validate that a URL is safe to fetch (not targeting private/internal networks)."""
    # Reject characters that cause parsing discrepancies between urlparse and requests,
    # which can be exploited to bypass SSRF protections (GHSA-27xf-58m5-vxmc).
    if '\\' in url:
        raise ValueError("Invalid URL: backslashes are not allowed")

    parsed = urlparse(url)
    if parsed.scheme not in ('http', 'https'):
        raise ValueError(f"Unsupported URL scheme: {parsed.scheme}")

    if '@' in parsed.netloc:
        raise ValueError("Invalid URL: userinfo (credentials) in URLs is not allowed")

    hostname = parsed.hostname
    if not hostname:
        raise ValueError("No hostname in URL")

    # Resolve hostname and check all returned addresses
    try:
        for family, _, _, _, sockaddr in socket.getaddrinfo(hostname, None):
            ip = ipaddress.ip_address(sockaddr[0])
            if not ip.is_global:
                raise ValueError(f"Access to non-public address {ip} is blocked")
    except socket.gaierror:
        raise ValueError(f"Could not resolve hostname: {hostname}")


def safe_get(url, headers=None, timeout=10, max_redirects=5):
    """Fetch a URL with SSRF-safe redirect handling. Validates every hop."""
    _validate_url(url)

View on GitHub (pinned to ed888c71f2)

Solutions

  1. Remove the userinfo and pass credentials via headers instead (e.g. Authorization header), then re-submit the clean URL.
  2. If credentials are genuinely needed, strip them from the URL and configure HTTP Basic auth at the request layer outside the guarded fetch path.
  3. Treat this error on a redirect hop as a security signal — do not retry; report the offending upstream URL.

Example fix

# before
resp = safe_get('https://user:pass@example.com/api')  # raises ValueError

# after
from urllib.parse import urlsplit, urlunsplit
parts = urlsplit('https://user:pass@example.com/api')
clean = urlunsplit((parts.scheme, parts.netloc.rpartition('@')[2], parts.path, parts.query, ''))
resp = safe_get(clean, headers={'Authorization': 'Basic <b64 user:pass>'})
Defensive patterns

Strategy: validation

Validate before calling

from urllib.parse import urlsplit, urlunsplit

def strip_userinfo(url: str) -> str:
    parts = urlsplit(url)
    if '@' in parts.netloc:
        host = parts.netloc.rpartition('@')[2]
        parts = parts._replace(netloc=host)
    return urlunsplit(parts)

url = strip_userinfo(url)  # then pass credentials via headers

Try / catch

try:
    resp = safe_get(url)
except ValueError as e:
    if 'userinfo' in str(e):
        raise RuntimeError('Embedded credentials are not supported; move them to an Authorization header') from e
    raise

Prevention

When it happens

Trigger: Fetching 'https://user:pass@example.com/data', 'https://token@example.com', or any URL where the netloc contains '@'. Also triggered by a redirect Location containing embedded credentials, which is a classic SSRF-bypass attempt.

Common situations: User pastes an authenticated URL copied from a browser or API doc (basic-auth style); scripts that embed API tokens in the URL; malicious redirect chains trying to smuggle a private host past validation.

Related errors


AI-assisted analysis of oobabooga/textgen@ed888c71f2 (2026-08-15). Data as JSON: /api/errors/89c75c1993290808. Report an issue: GitHub.