{"record":{"id":"d6a0c565a544115d","repo":"JuliusBrussee/caveman","slug":"cave-redirect-not-allowed","errorCode":null,"errorMessage":"cave_redirect_not_allowed","messagePattern":"cave_redirect_not_allowed","errorType":"http","errorClass":"HTTPError","httpStatus":302,"severity":"error","filePath":"packages/sdk/python/caveman_cloud/core.py","lineNumber":28,"sourceCode":"import threading\nimport time\nimport urllib.error\nimport urllib.request\nfrom contextlib import contextmanager\nfrom urllib.parse import quote, urlsplit\nfrom dataclasses import dataclass, field\nfrom typing import Any, Callable, Iterator, Literal\n\n\nclass _RejectRedirects(urllib.request.HTTPRedirectHandler):\n    \"\"\"Keep gateway and upstream credentials on the configured endpoint.\"\"\"\n\n    def http_error_302(self, req: Any, fp: Any, code: int, msg: str, response_headers: Any) -> Any:\n        # urllib otherwise forwards Authorization and custom credential headers\n        # to another origin. Close the redirect response before raising, since\n        # the caller never receives a response context manager in this case.\n        fp.close()\n        raise urllib.error.HTTPError(req.full_url, code, \"cave_redirect_not_allowed\", response_headers, None)\n\n    http_error_301 = http_error_302\n    http_error_303 = http_error_302\n    http_error_307 = http_error_302\n    http_error_308 = http_error_302\n\n\ndef _urlopen(req: urllib.request.Request, *, timeout: float) -> Any:\n    # A private opener avoids changing urllib's process-wide redirect policy.\n    return urllib.request.build_opener(_RejectRedirects()).open(req, timeout=timeout)\n\n\ndef _strict_non_negative_int(value: Any) -> int | None:\n    \"\"\"Accept JSON integers only; reject bools, floats, strings, and negatives.\"\"\"\n    if isinstance(value, bool) or not isinstance(value, int) or value < 0 or value > 2**53 - 1:\n        return None\n    return value\n","sourceCodeStart":10,"sourceCodeEnd":46,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/sdk/python/caveman_cloud/core.py#L10-L46","documentation":"The caveman_cloud SDK deliberately blocks HTTP redirects for its API client. A custom urllib handler installs http_error_302 (aliased to 301/303/307/308) that closes the redirect response and raises an HTTPError whose message is the literal 'cave_redirect_not_allowed'. This prevents urllib from forwarding the Authorization header and other credential headers to a different origin, which would leak credentials on cross-origin redirects.","triggerScenarios":"Any SDK request (urllib-based) that receives a 301/302/303/307/308 redirect response from the server; the handler immediately raises urllib.error.HTTPError with code and headers and msg 'cave_redirect_not_allowed'.","commonSituations":"The API base URL was misconfigured (e.g. http instead of https and the server redirects); a proxy or gateway redirects to a login page; a tenant endpoint moved and issues 301s; an expired session causes the server to redirect to an auth page instead of returning 401.","solutions":["Fix the configured base URL to the final, correct https endpoint so no redirect occurs","Check for a proxy/load balancer issuing redirects and address it (or bypass the proxy)","Re-authenticate if the redirect is an auth-page bounce; the SDK expects 401s, not redirects","Inspect error.headers on the raised HTTPError to see the Location header and identify where the server is redirecting"],"exampleFix":"# before\nclient = CloudClient(base_url=\"http://cave.example.com\")  # 301 -> https\n# after\nclient = CloudClient(base_url=\"https://cave.example.com\")","handlingStrategy":"try-catch","validationCode":"from urllib.parse import urlparse\n\ndef assert_no_redirect_expected(base_url: str) -> None:\n    parsed = urlparse(base_url)\n    if parsed.scheme != \"https\":\n        raise ValueError(f\"base_url should be final https endpoint, got {base_url}\")","typeGuard":null,"tryCatchPattern":"import urllib.error\n\ntry:\n    resp = client.request(\"/v1/things\")\nexcept urllib.error.HTTPError as e:\n    if e.msg == \"cave_redirect_not_allowed\":\n        # do NOT follow; inspect where it wanted to go and fix base_url instead\n        location = e.headers.get(\"Location\")\n        raise RuntimeError(f\"redirect blocked, server wanted {location}; fix base_url\") from e\n    raise","preventionTips":["Always configure the final https base URL, never an endpoint known to redirect","Watch for proxies or SSO gateways that 302 API traffic to login pages","Handle 401 by re-authenticating rather than expecting a redirect to an auth page","On this error, read the HTTPError headers' Location to diagnose the redirect source"],"tags":["http","redirect","security","credentials"],"backgroundTag":"http-error-response","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}