usestrix/strix · error · ValueError

Invalid URL: {url}

Error message

Invalid URL: {url}

What it means

build_raw_request() converts a logical request (method/url/headers/body) into a Caido ConnectionInfoInput plus raw bytes. It urlparse()s the URL and requires both a scheme and a netloc; otherwise ValueError. This catches malformed replay targets before any socket is opened.

Source

Thrown at strix/tools/proxy/caido_api.py:182

    # "Field required" on the missing raw field. Always request both —
    # the caller picks which one to surface via ``part``.
    opts = RequestGetOptions(request_raw=True, response_raw=True)
    return await client.request.get(request_id, opts)


_FRAMING_HEADERS = frozenset({"content-length", "transfer-encoding"})


def build_raw_request(
    *,
    method: str,
    url: str,
    headers: dict[str, str],
    body: str,
) -> tuple[ConnectionInfoInput, bytes]:
    parsed = urlparse(url)
    if not parsed.scheme or not parsed.netloc:
        raise ValueError(f"Invalid URL: {url}")
    is_tls = parsed.scheme.lower() == "https"
    host = parsed.hostname or ""
    port = parsed.port or (443 if is_tls else 80)
    path = parsed.path or "/"
    if parsed.query:
        path = f"{path}?{parsed.query}"

    final_headers = {**headers}
    final_headers.setdefault("Host", parsed.netloc)
    final_headers.setdefault(
        "User-Agent",
        "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 "
        "(KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36",
    )
    # Framing headers inherited from the captured request describe the ORIGINAL
    # body; once the body is modified for replay they are stale. We always send a
    # plain (non-chunked) body with an explicit Content-Length, so drop any
    # inherited Content-Length AND Transfer-Encoding (case-insensitively) and

View on GitHub (pinned to 8551339130)

Solutions

  1. Pass modifications with an explicit absolute URL including scheme and host when replaying origin-form requests.
  2. Ensure the captured request retains a Host header so full_url_from_components can reconstruct the absolute URL.
  3. Validate the URL with urlparse before calling repeat_request and fix or skip malformed ones.
  4. If targeting a different host, set both the url and the Host header consistently in modifications.

Example fix

# before
await repeat_request(req_id, modifications={"url": "/api/login", ...})

# after
await repeat_request(req_id, modifications={"url": "https://target.example/api/login", ...})
Defensive patterns

Strategy: validation

Validate before calling

from urllib.parse import urlparse

def is_absolute_http_url(url: str) -> bool:
    p = urlparse(url)
    return p.scheme in {"http", "https"} and bool(p.netloc)

if not is_absolute_http_url(mods.get("url", "")):
    mods["url"] = f"https://{host_from_host_header}{path}"  # absolutize before replay

Type guard

def is_replayable_url(url: object) -> bool:
    if not isinstance(url, str):
        return False
    p = urlparse(url)
    return p.scheme in {"http", "https"} and bool(p.netloc)

Try / catch

try:
    await repeat_request(request_id, modifications=mods)
except ValueError as exc:
    if "Invalid URL" in str(exc):
        continue  # skip malformed replay target, log the id
    raise

Prevention

When it happens

Trigger: Calling repeat_request (or build_raw_request directly) with a URL like '/api/foo' (no scheme/host), 'example.com/x' (no scheme), or 'http://' (no host). Usually the URL comes from full_url_from_components() reconstructing it from a captured raw request whose Host header or absolute-URI request line is missing.

Common situations: Replaying a captured request whose original form was origin-form (path-only) and no Host header survived; LLM-generated modification sets url to a relative path; proxy captures with mangled request lines.

Related errors


AI-assisted analysis of usestrix/strix@8551339130 (2026-08-15). Data as JSON: /api/errors/5818b3bf82ef59f3. Report an issue: GitHub.