{"record":{"id":"9e699a0e737729f1","repo":"dgtlmoon/changedetection.io","slug":"redirect-blocked-redirect-url-resolves-to-a-p","errorCode":null,"errorMessage":"Redirect blocked: '{redirect_url}' resolves to a private/reserved IP address or contains a parser-differential payload.","messagePattern":"Redirect blocked: '(.+?)' resolves to a private/reserved IP address or contains a parser-differential payload\\.","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"critical","filePath":"changedetectionio/content_fetchers/requests.py","lineNumber":116,"sourceCode":"                                data=request_body.encode('utf-8') if type(request_body) is str else request_body,\n                                url=url,\n                                headers=request_headers,\n                                timeout=timeout,\n                                proxies=proxies,\n                                verify=False,\n                                allow_redirects=False)\n\n            # Manually follow redirects so each hop's resolved IP can be validated,\n            # preventing SSRF via an open redirect on a public host.\n            current_url = url\n            for _ in range(10):\n                if not r.is_redirect:\n                    break\n                location = r.headers.get('Location', '')\n                redirect_url = urljoin(current_url, location)\n                if not allow_iana_restricted:\n                    if is_url_private_or_parser_confused(redirect_url):\n                        raise Exception(f\"Redirect blocked: '{redirect_url}' resolves to a private/reserved IP address \"\n                                        f\"or contains a parser-differential payload.\")\n                current_url = redirect_url\n                r = session.request('GET', redirect_url,\n                                    headers=request_headers,\n                                    timeout=timeout,\n                                    proxies=proxies,\n                                    verify=False,\n                                    allow_redirects=False)\n            else:\n                raise Exception(\"Too many redirects\")\n\n        except Exception as e:\n            msg = str(e)\n            if proxies and 'SOCKSHTTPSConnectionPool' in msg:\n                msg = f\"Proxy connection failed? {msg}\"\n            raise Exception(msg) from e\n\n        # If the response did not tell us what encoding format to expect, Then use chardet to override what `requests` thinks.","sourceCodeStart":98,"sourceCodeEnd":134,"githubUrl":"https://github.com/dgtlmoon/changedetection.io/blob/5d9c7c6da76340597243e8163c4f2439237fa0e8/changedetectionio/content_fetchers/requests.py#L98-L134","documentation":"The requests fetcher manually follows redirects (allow_redirects=False) and blocks any Location target that resolves to a private/reserved IP or looks like a parser-differential (SSRF) payload, when allow_iana_restricted is off.","triggerScenarios":"A watched public URL redirecting to http://127.0.0.1, 10.x, 169.254.169.254, or a URL with mixed encodings that parsers resolve differently.","commonSituations":"Sites redirecting to internal hosts; malicious or compromised target attempting SSRF via redirect; legitimate services redirecting to internal hostnames in on-prem networks.","solutions":["Verify the redirect chain (curl -IL) and where Location points","If the internal target is legitimate in your network, configure the fetcher to allow it / set allow_iana_restricted appropriately","Re-check the watch URL — a changed or hijacked domain may be redirecting somewhere unexpected"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"# Resolve redirect target host pre-check (mirror of guard)\nimport socket, ipaddress, urllib.parse\nloc = urljoin(url, response.headers.get('Location',''))\nhost = urllib.parse.urlparse(loc).hostname\nip = socket.gethostbyname(host)\nassert not ipaddress.ip_address(ip).is_private, 'redirect would be blocked'","typeGuard":null,"tryCatchPattern":"try:\n    fetcher.run()\nexcept Exception as e:\n    if 'Redirect blocked' in str(e):\n        log_security_event(str(e))  # potential SSRF attempt by target site\n        disable_watch_or_alert()","preventionTips":["Treat this error as a security signal, not a bug — alert on it","Watch final canonical URLs directly to avoid redirect chains","Keep the SSRF protection enabled in shared/multi-tenant deployments"],"tags":["ssrf","redirect","requests","security"],"backgroundTag":"ssrf-redirect-blocked","analyzedSha":"5d9c7c6da76340597243e8163c4f2439237fa0e8","analyzedAt":"2026-08-27T19:41:16.067Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}