psf/requests · error · InvalidHeader

Invalid leading whitespace, reserved character(s), or…

Error message

Invalid leading whitespace, reserved character(s), or return character(s) in header {header_kind}: {header_part!r}

What it means

Raised by _validate_header_part() when a header name or value contains characters that fail the validator regex: leading whitespace (for values), colons or whitespace (for names), or CR/LF characters anywhere. This is primarily a security guard against HTTP header injection (CRLF injection) attacks.

Solutions

  1. Strip CR/LF and leading whitespace from all header values before passing to requests: v.replace('\r', '').replace('\n', '').strip().
  2. Sanitize user input before placing it in headers: reject or escape any control characters.
  3. Use a header sanitization function for any data sourced from external input.

Example fix

# before
requests.get(url, headers={'X-User': user_input})  # user_input = 'alice\r\nX-Admin: true'

# after — sanitize
def safe_header(v):
    return str(v).replace('\r', '').replace('\n', '').strip()

headers = {k: safe_header(v) for k, v in raw_headers.items()}
requests.get(url, headers=headers)
Defensive patterns

Strategy: validation

Validate before calling

import re
def sanitize_header(value):
    v = str(value).replace('\r', '').replace('\n', '').strip()
    if re.search(r'[\x00-\x1f]', v):
        raise ValueError(f'Header contains control characters: {v!r}')
    return v

clean_headers = {k: sanitize_header(v) for k, v in headers.items()}
resp = requests.get(url, headers=clean_headers)

Type guard

import re
def is_safe_header_value(v) -> bool:
    if not isinstance(v, (str, bytes)):
        return False
    s = v if isinstance(v, str) else v.decode('ascii', errors='replace')
    return not re.search(r'[\r\n]', s) and (not s or not s[0].isspace())

Try / catch

try:
    resp = requests.get(url, headers=headers)
except requests.exceptions.InvalidHeader as e:
    logger.error(f'Header validation failed: {e}')
    clean = {k: str(v).replace('\r', '').replace('\n', '').strip()
             for k, v in headers.items()}
    resp = requests.get(url, headers=clean)

Prevention

When it happens

Trigger: Header value containing '\r\n' or '\n' (newline injection); header name with a colon; header value with leading space; header value containing raw return characters from user input or multi-line strings.

Common situations: User-supplied data placed into headers without sanitization (security vulnerability); copy-pasting multi-line strings into header values; building headers from log lines or file contents that contain newlines; deliberate header injection attempts from malicious input.

Related errors


AI-assisted analysis of psf/requests@8068356288 (2026-08-11). Data as JSON: /api/errors/b89494c0d9bf600f. Report an issue: GitHub.

Appendix: source

Thrown at src/requests/utils.py:1116

def _validate_header_part(
    header: tuple[str | bytes, str | bytes],
    header_part: str | bytes,
    header_validator_index: int,
) -> None:
    if isinstance(header_part, str):
        validator = _HEADER_VALIDATORS_STR[header_validator_index]
    elif isinstance(header_part, bytes):
        # runtime guard for non-str/bytes input
        validator = _HEADER_VALIDATORS_BYTE[header_validator_index]
    else:
        raise InvalidHeader(
            f"Header part ({header_part!r}) from {header} "
            f"must be of type str or bytes, not {type(header_part)}"
        )

    if not validator.match(header_part):  # type: ignore[arg-type]
        header_kind = "name" if header_validator_index == 0 else "value"
        raise InvalidHeader(
            f"Invalid leading whitespace, reserved character(s), or return "
            f"character(s) in header {header_kind}: {header_part!r}"
        )


def urldefragauth(url: str) -> str:
    """
    Given a url remove the fragment and the authentication part.

    :rtype: str
    """
    scheme, netloc, path, params, query, _fragment = urlparse(url)

    # see func:`prepend_scheme_if_needed`
    if not netloc:
        netloc, path = path, netloc

    netloc = netloc.rsplit("@", 1)[-1]

View on GitHub (pinned to 8068356288)