CVE-2025-50182
Medium · 5.3urllib3 does not control redirects in browsers and Node.js
CVSS
5.3
EPSS
0.4%
Published
Jun 18, 2025
CISA due date
—
Cloudanix analysis
Some browser-based Python applications may follow web redirects even when they were configured not to, which could send users or data to unintended websites.
- Exploitation
- Requires User Interaction
- Attacker needs
- User Interaction
- Complexity
- Moderate
- Impact
- Other
- Component
- Library
- Class
- Open Redirect
- Remediation
- Upgrade
- Effort
- Medium
- Patch risk
- Low
What it actually does
In urllib3 when running under Pyodide, HTTP redirect controls passed through the `retries` and `redirect` parameters are not enforced as expected because redirect handling is determined by the underlying JavaScript runtime, such as the Fetch API or XMLHttpRequest. Code that assumes urllib3 will block, limit, or otherwise control redirects may instead follow redirects according to browser or Node.js runtime behavior. The practical technical risk is unintended outbound requests to attacker-controlled or untrusted locations, bypass of application-level redirect restrictions, and possible exposure of request metadata depending on how the affected application constructs requests.
Why it matters to the business
Applications that use urllib3 inside Pyodide may make network requests in ways developers did not intend, which can weaken trust boundaries in browser-based or Node.js-hosted Python workflows. The most likely business impact is user redirection to untrusted destinations, unexpected calls to third-party services, broken security assumptions in embedded Python applications, and potential leakage of URLs, tokens in query strings, or workflow context if the application sends sensitive data during redirected requests. This issue is not listed in CISA KEV, has low EPSS, and has no known mature public exploitation signal in the provided data, so it is generally a moderate-priority dependency update unless the affected Pyodide usage handles sensitive links, credentials, or user-controlled URLs.
How an attack unfolds
An attacker first identifies an application that runs Python code through Pyodide and uses urllib3 to make HTTP requests while relying on `redirect=False`, retry limits, or other redirect-related controls for safety. The attacker then supplies or influences a URL that initially points to a trusted or expected endpoint but returns an HTTP redirect to an attacker-controlled destination. Because the Pyodide runtime handles redirects independently through Fetch API or XMLHttpRequest behavior, the urllib3 parameters intended to prevent or constrain redirects may be ignored. The request can then be transparently followed to the attacker-controlled location, potentially exposing request metadata, causing the application to consume attacker-controlled content, or enabling downstream phishing, tracking, or policy bypass scenarios.
How to fix it
Upgrade affected urllib3 code to a version containing the fix. For source-based urllib3 deployments, update to commit `aaab4eccc10c965897540b21e15f11859d0b62e7` or a release that includes that commit. For Mageia 4 `nail`, update to `12.4-9.1.mga4` where applicable. Where immediate upgrade is not possible, do not rely on urllib3 redirect parameters for security decisions in Pyodide. Instead, validate URLs before making requests, enforce destination allowlists at the application layer, reject redirects to untrusted schemes or hosts, avoid sending secrets in URLs, and use runtime-specific redirect controls where available, such as Fetch API redirect handling configured explicitly by the surrounding JavaScript layer.
How to confirm the fix worked
Confirm dependency versions and build provenance to ensure the urllib3 fix is present, specifically commit `aaab4eccc10c965897540b21e15f11859d0b62e7` or a downstream release containing it. Review Pyodide-based code paths that call urllib3 and pass `redirect`, `retries`, or redirect-related options. Create a test endpoint that returns an HTTP 3xx redirect to a different host and verify that the application blocks, limits, or handles the redirect according to the intended security policy. Validate both browser and Node.js Pyodide execution paths if both are supported, because redirect behavior can be determined by the underlying JavaScript runtime.
Typical attack path
- Find an application or embedded workflow that uses urllib3 in a Pyodide runtime.
- Identify a request path where a URL or endpoint can be influenced directly or indirectly by an attacker.
- Host or control an HTTP endpoint that returns a redirect to an attacker-controlled destination.
- Trigger the affected application to request the controlled URL while the application expects urllib3 redirect controls to apply.
- Rely on Fetch API or XMLHttpRequest redirect behavior to follow the redirect despite the urllib3 `retries` or `redirect` settings.
- Use the redirected request to collect request metadata, deliver attacker-controlled content, bypass allowlist assumptions, or support phishing and tracking workflows.
How to detect it
To determine whether this behavior was abused, review network, proxy, CDN, browser telemetry, and application logs for Pyodide-driven HTTP requests that resulted in HTTP 3xx responses followed by requests to unexpected external domains. Focus on flows where the original destination was trusted but the final destination was outside the expected allowlist, used a different scheme, or was newly registered or uncommon. Look for user-controlled URLs, query parameters containing full URLs, repeated redirect chains, and requests where sensitive query-string values were sent to third-party hosts. If client-side telemetry is available, correlate Pyodide or WebAssembly application events with Fetch API or XMLHttpRequest requests. Because there are no known specific exploit artifacts, detection should be behavior-based: unexpected cross-origin redirects, redirects that contradict application policy, and outbound requests following attacker-influenced URLs.
What your security team should do
- Inventory applications that use urllib3 under Pyodide, especially browser-based Python, WebAssembly-hosted Python, and Node.js Pyodide integrations.
- Prioritize remediation for applications that request user-supplied URLs, process links from external content, handle authentication tokens, or make requests to security-sensitive services.
- Upgrade urllib3 to a fixed build containing commit `aaab4eccc10c965897540b21e15f11859d0b62e7` or the appropriate fixed downstream package version.
- Add application-level URL validation and destination allowlisting for Pyodide network requests instead of relying only on urllib3 redirect parameters.
- Test redirect handling explicitly in CI using cross-origin and cross-scheme redirect cases.
- Monitor dependency advisories for the packaged release version that includes the upstream fix, because the provided fixed version is identified by source commit rather than a conventional urllib3 release number.
The record
urllib3 [supports](https://urllib3.readthedocs.io/en/2.4.0/reference/contrib/emscripten.html) being used in a Pyodide runtime utilizing the [JavaScript Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) or falling back on [XMLHttpRequest](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest). This means you can use Python libraries to make HTTP requests from your browser or Node.js. Additionally, urllib3 provides [a mechanism](https://urllib3.readthedocs.io/en/2.4.0/user-guide.html#retrying-requests) to control redirects. However, the `retries` and `redirect` parameters are ignored with Pyodide; the runtime itself determines redirect behavior.
Affected packages
| Ecosystem | Package | Introduced | Fixed |
|---|---|---|---|
| PyPI | urllib3 | 2.2.0 | 2.5.0 |
| Chainguard | airflow-2 | 0 | 2.11.0-r5 |
| Chainguard | airflow-2-bitnami-compat | 0 | 2.11.0-r5 |
| Chainguard | airflow-2-compat | 0 | 2.11.0-r5 |
| Chainguard | airflow-2-iamguarded-compat | 0 | 2.11.0-r5 |
| Chainguard | airflow-3 | 0 | 3.0.2-r2 |
| Chainguard | airflow-3-bitnami-compat | 0 | 3.0.2-r2 |
| Chainguard | airflow-3-compat | 0 | 3.0.2-r2 |
| Chainguard | airflow-3-iamguarded-compat | 0 | 3.0.2-r2 |
| Chainguard | airflow-core-2 | 0 | 2.11.0-r3 |
| Chainguard | airflow-core-2-compat | 0 | 2.11.0-r3 |
| Chainguard | airflow-core-2-oci-entrypoint | 0 | 2.11.0-r3 |
| Chainguard | ansible-operator | 0 | 0 |
| Chainguard | ansible-operator-compat | 0 | 0 |
| Chainguard | apache-beam-python-3.11-sdk | 0 | 2.65.0-r2 |
| Chainguard | awx | 0 | 24.6.1-r19 |
| Chainguard | az | 0 | 2.74.0-r3 |
| Chainguard | az-iamguarded-compat | 0 | 2.74.0-r3 |
| Chainguard | barman | 0 | 3.14.1-r0 |
| Chainguard | barman-cloudnative-pg | 0 | 3.14.1-r0 |
| Chainguard | confluent-docker-utils | 0 | 0.0.162-r1 |
| Chainguard | dask-gateway | 0 | 2025.4.0-r1 |
| Chainguard | dask-gateway-server | 0 | 2025.4.0-r1 |
| Chainguard | dask-kubernetes | 0 | 2025.4.3-r4 |
| Chainguard | datadog-agent | 0 | 7.71.2-r3 |
| Chainguard | datadog-agent-core-integrations | 0 | 7.72.2-r0 |
| Chainguard | datadog-agent-core-integrations-fips | 0 | 7.72.1-r0 |
| Chainguard | datadog-agent-fakeintake | 0 | 7.72.1-r0 |
| Chainguard | datadog-agent-fakeintake-fips | 0 | 7.72.0-r0 |
| Chainguard | datadog-agent-fips | 0 | 7.72.0-r0 |
| Chainguard | datadog-agent-fips-7.71 | 0 | 7.71.2-r5 |
| Chainguard | datadog-agent-fips-7.71-fakeintake | 0 | 7.71.2-r5 |
| Chainguard | datadog-agent-fips-7.71-jmx | 0 | 7.71.2-r5 |
| Chainguard | datadog-agent-fips-7.71-oci-compat | 0 | 7.71.2-r5 |
| Chainguard | datadog-agent-fips-7.71-s6-overlay | 0 | 7.71.2-r5 |
| Chainguard | datadog-agent-jmx | 0 | 7.72.1-r0 |
| Chainguard | datadog-agent-jmx-fips | 0 | 7.72.0-r0 |
| Chainguard | datadog-agent-oci-compat | 0 | 7.71.2-r3 |
| Chainguard | datadog-agent-oci-compat-fips | 0 | 7.72.0-r0 |
| Chainguard | datadog-agent-s6-overlay | 0 | 7.71.2-r3 |
| Chainguard | datadog-agent-s6-overlay-fips | 0 | 7.72.0-r0 |
| Chainguard | datadog-cluster-agent | 0 | 7.71.2-r3 |
| Chainguard | datadog-cluster-agent-fips | 0 | 7.72.1-r0 |
| Chainguard | datadog-cluster-agent-fips-7.71 | 0 | 7.71.2-r5 |
| Chainguard | datadog-cluster-agent-fips-7.71-oci-compat | 0 | 7.71.2-r5 |
| Chainguard | datadog-cluster-agent-oci-compat | 0 | 7.71.2-r3 |
| Chainguard | datadog-cluster-agent-oci-compat-fips | 0 | 7.72.0-r0 |
| Chainguard | dogstatsd | 0 | 7.72.1-r0 |
| Chainguard | emissary | 0 | 3.10.0-r6 |
| Chainguard | emissary-apiext | 0 | 3.10.0-r6 |
| Chainguard | emissary-oci-entrypoint | 0 | 3.10.0-r6 |
| Chainguard | ggshield | 0 | 1.43.0-r0 |
| Chainguard | grafana-oncall | 0 | 1.16.3-r0 |
| Chainguard | grafana-oncall-compat | 0 | 1.16.3-r0 |
| Chainguard | jupyter-base-notebook | 0 | 7.4.3-r2 |
| Chainguard | jupyter-base-notebook-oci-entrypoint | 0 | 0.0.0_git20251013-r0 |
| Chainguard | jwt-tool | 0 | 2.3.0-r2 |
| Chainguard | k8s-sidecar | 0 | 1.30.5-r0 |
| Chainguard | katib-controller | 0 | 0.19.0-r0 |
| Chainguard | katib-controller-compat | 0 | 0.19.0-r0 |
| Chainguard | katib-db-manager | 0 | 0.19.0-r0 |
| Chainguard | katib-db-manager-compat | 0 | 0.19.0-r0 |
| Chainguard | katib-earlystopping | 0 | 0.19.0-r3 |
| Chainguard | katib-file-metricscollector | 0 | 0.19.0-r0 |
| Chainguard | katib-file-metricscollector-compat | 0 | 0.19.0-r0 |
| Chainguard | katib-suggestion-goptuna | 0 | 0.19.0-r0 |
| Chainguard | katib-suggestion-goptuna-compat | 0 | 0.19.0-r0 |
| Chainguard | katib-suggestion-hyperband | 0 | 0.19.0-r0 |
| Chainguard | katib-suggestion-hyperopt | 0 | 0.19.0-r0 |
| Chainguard | katib-suggestion-nas-darts | 0 | 0.19.0-r0 |
| Chainguard | katib-suggestion-nas-enas | 0 | 0.19.0-r0 |
| Chainguard | katib-suggestion-optuna-enas | 0 | 0.19.0-r0 |
| Chainguard | katib-suggestion-pbt-enas | 0 | 0.19.0-r0 |
| Chainguard | katib-suggestion-skopt-enas | 0 | 0.19.0-r0 |
| Chainguard | katib-tfevent-metricscollector | 0 | 0.19.0-r0 |
| Chainguard | kserve | 0 | 0.16.0-r6 |
| Chainguard | kserve-agent | 0 | 0.16.0-r6 |
| Chainguard | kserve-agent-compat | 0 | 0.16.0-r6 |
| Chainguard | kserve-manager | 0 | 0.16.0-r6 |
| Chainguard | kserve-manager-compat | 0 | 0.16.0-r6 |
| Chainguard | kserve-qpext | 0 | 0.16.0-r6 |
| Chainguard | kserve-qpext-compat | 0 | 0.16.0-r6 |
| Chainguard | kserve-router | 0 | 0.16.0-r6 |
| Chainguard | kserve-router-compat | 0 | 0.16.0-r6 |
| Chainguard | kserve-storage-controller | 0 | 0.16.0-r6 |
| Chainguard | kubeflow-jupyter-web-app | 0 | 1.10.0-r5 |
| Chainguard | kubeflow-katib | 0 | 0.19.0-r0 |
| Chainguard | kubeflow-pipelines | 0 | 2.5.0-r4 |
| Chainguard | kubeflow-pipelines-apiserver | 0 | 2.5.0-r4 |
| Chainguard | kubeflow-pipelines-cache-deployer | 0 | 2.5.0-r4 |
| Chainguard | kubeflow-pipelines-cache-deployer-compat | 0 | 2.5.0-r4 |
| Chainguard | kubeflow-pipelines-cache_server | 0 | 2.5.0-r4 |
| Chainguard | kubeflow-pipelines-frontend | 0 | 2.5.0-r4 |
| Chainguard | kubeflow-pipelines-metadata-envoy-config | 0 | 2.5.0-r4 |
| Chainguard | kubeflow-pipelines-metadata-writer | 0 | 2.5.0-r4 |
| Chainguard | kubeflow-pipelines-metadata-writer-compat | 0 | 2.5.0-r4 |
| Chainguard | kubeflow-pipelines-persistence_agent | 0 | 2.5.0-r4 |
| Chainguard | kubeflow-pipelines-scheduledworkflow | 0 | 2.5.0-r4 |
| Chainguard | kubeflow-pipelines-viewer-crd-controller | 0 | 2.5.0-r4 |
| Chainguard | kubeflow-pipelines-visualization-server | 0 | 2.14.3-r2 |
| Chainguard | kubeflow-volumes-web-app | 0 | 1.10.0-r5 |
| Chainguard | localstack | 0 | 4.10.0-r0 |
| Chainguard | localstack-compat | 0 | 4.10.0-r0 |
| Chainguard | mlflow | 0 | 3.1.0-r3 |
| Chainguard | mlflow-bitnami | 0 | 3.1.0-r3 |
| Chainguard | mlflow-iamguarded-compat | 0 | 3.1.0-r3 |
| Chainguard | nemo | 0 | 2.3.1-r4 |
| Chainguard | nvidia-apex-12.3 | 0 | 24.04.01-r1 |
| Chainguard | nvidia-apex-12.3-wheel | 0 | 24.04.01-r1 |
| Chainguard | open-webui | 0 | 0.6.36-r0 |
| Chainguard | open-webui-compat | 0 | 0.6.36-r0 |
| Chainguard | pgadmin4 | 0 | 9.4-r2 |
| Chainguard | pgadmin4-oci-entrypoint | 0 | 9.4-r2 |
| Chainguard | pgadmin4-pg12 | 0 | 9.4-r2 |
| Chainguard | pgadmin4-pg13 | 0 | 9.4-r2 |
| Chainguard | pgadmin4-pg14 | 0 | 9.4-r2 |
| Chainguard | pgadmin4-pg15 | 0 | 9.4-r2 |
| Chainguard | pgadmin4-pg16 | 0 | 9.4-r2 |
| Chainguard | pgadmin4-pg17 | 0 | 9.4-r2 |
| Chainguard | py3-cassandra-medusa | 0 | 0.26.0-r0 |
| Chainguard | py3-cassandra-medusa-compat | 0 | 0.26.0-r0 |
| Chainguard | py3-hashin | 0 | 1.0.5-r1 |
| Chainguard | py3-pip | 0 | 25.3-r1 |
| Chainguard | py3-pip-wheel | 0 | 25.3-r1 |
| Chainguard | py3-pipenv | 0 | 2025.0.3-r0 |
| Chainguard | py3-supported-pip | 0 | 25.3-r1 |
| Chainguard | py3-supported-pipenv | 0 | 2025.0.3-r0 |
| Chainguard | py3.10-ambassador | 0 | 3.10.0-r6 |
| Chainguard | py3.10-pip | 0 | 25.3-r1 |
| Chainguard | py3.10-pip-base | 0 | 25.3-r1 |
| Chainguard | py3.10-pipenv | 0 | 2025.0.3-r0 |
| Chainguard | py3.10-pipenv-bin | 0 | 2025.0.3-r0 |
| Chainguard | py3.10-pytorch-cuda-11.8 | 0 | 2.3.1-r6 |
| Chainguard | py3.10-pytorch-cuda-12.3 | 0 | 2.3.1-r7 |
| Chainguard | py3.10-torchvision-cuda-12.3 | 0 | 0.18.1-r6 |
| Chainguard | py3.10-vllm-cuda-11.8 | 0 | 0.9.1-r1 |
| Chainguard | py3.10-wheels-torch-cuda-11.8 | 0 | 2.3.1-r6 |
| Chainguard | py3.10-wheels-torch-cuda-12.3 | 0 | 2.3.1-r7 |
| Chainguard | py3.10-wheels-torchvision-cuda-12.3 | 0 | 0.18.1-r6 |
| Chainguard | py3.10-wheels-vllm-cuda-11.8 | 0 | 0.9.1-r1 |
| Chainguard | py3.11-ambassador | 0 | 3.10.0-r6 |
| Chainguard | py3.11-pip | 0 | 25.3-r1 |
| Chainguard | py3.11-pip-base | 0 | 25.3-r1 |
| Chainguard | py3.11-pipenv | 0 | 2025.0.3-r0 |
| Chainguard | py3.11-pipenv-bin | 0 | 2025.0.3-r0 |
| Chainguard | py3.11-pytorch-cuda-11.8 | 0 | 2.3.1-r7 |
| Chainguard | py3.11-pytorch-cuda-12.3 | 0 | 2.3.1-r10 |
| Chainguard | py3.11-text-generation-inference | 0 | 3.3.6-r3 |
| Chainguard | py3.11-torchaudio-cuda-12.3 | 0 | 2.3.1-r1 |
| Chainguard | py3.11-torchvision-cuda-12.3 | 0 | 0.18.1-r7 |
| Chainguard | py3.11-wheels-torch-cuda-11.8 | 0 | 2.3.1-r7 |
| Chainguard | py3.11-wheels-torch-cuda-12.3 | 0 | 2.3.1-r10 |
| Chainguard | py3.11-wheels-torchaudio-cuda-12.3 | 0 | 2.3.1-r1 |
| Chainguard | py3.11-wheels-torchvision-cuda-12.3 | 0 | 0.18.1-r7 |
| Chainguard | py3.12-ambassador | 0 | 3.10.0-r6 |
| Chainguard | py3.12-pip | 0 | 25.3-r1 |
| Chainguard | py3.12-pip-base | 0 | 25.3-r1 |
| Chainguard | py3.12-pipenv | 0 | 2025.0.3-r0 |
| Chainguard | py3.12-pipenv-bin | 0 | 2025.0.3-r0 |
| Chainguard | py3.12-pytorch-cuda-11.8 | 0 | 2.3.1-r4 |
| Chainguard | py3.12-pytorch-cuda-12.3 | 0 | 2.3.1-r4 |
| Chainguard | py3.12-torchvision-cuda-12.3 | 0 | 0.18.1-r2 |
| Chainguard | py3.12-wheels-torch-cuda-11.8 | 0 | 2.3.1-r4 |
| Chainguard | py3.12-wheels-torch-cuda-12.3 | 0 | 2.3.1-r4 |
| Chainguard | py3.12-wheels-torchvision-cuda-12.3 | 0 | 0.18.1-r2 |
| Chainguard | py3.13-ambassador | 0 | 3.10.0-r6 |
| Chainguard | py3.13-pip | 0 | 25.3-r1 |
| Chainguard | py3.13-pip-base | 0 | 25.3-r1 |
| Chainguard | py3.13-pipenv | 0 | 2025.0.3-r0 |
| Chainguard | py3.13-pipenv-bin | 0 | 2025.0.3-r0 |
| Chainguard | py3.14-pip | 0 | 25.3-r1 |
| Chainguard | py3.14-pip-base | 0 | 25.3-r1 |
| Chainguard | py3.9-pip | 0 | 25.1.1-r0 |
| Chainguard | py3.9-pytorch-cuda-11.8 | 0 | 2.3.1-r3 |
| Chainguard | py3.9-pytorch-cuda-12.3 | 0 | 2.3.1-r4 |
| Chainguard | py3.9-torchvision-cuda-11.8 | 0 | 0.18.1-r3 |
| Chainguard | py3.9-torchvision-cuda-12.3 | 0 | 0.18.1-r1 |
| Chainguard | py3.9-wheels-torch-cuda-11.8 | 0 | 2.3.1-r3 |
| Chainguard | py3.9-wheels-torch-cuda-12.3 | 0 | 2.3.1-r4 |
| Chainguard | py3.9-wheels-torchvision-cuda-11.8 | 0 | 0.18.1-r3 |
| Chainguard | py3.9-wheels-torchvision-cuda-12.3 | 0 | 0.18.1-r1 |
| Chainguard | pypy-3.10 | 0 | 7.3.19-r10 |
| Chainguard | pypy-3.10-compat | 0 | 7.3.19-r10 |
| Chainguard | pypy-3.11 | 0 | 7.3.19-r37 |
| Chainguard | pypy-3.11-compat | 0 | 7.3.19-r37 |
| Chainguard | reflex | 0 | 0.8.0-r0 |
| Chainguard | request-1276 | 0 | 0.26.0-r0 |
| Chainguard | request-1276-compat | 0 | 0.26.0-r0 |
| Chainguard | spamcheck | 0 | 3.5.3-r3 |
| Chainguard | spamcheck-compat | 0 | 3.5.3-r3 |
| Chainguard | superset | 0 | 4.1.2-r6 |
| Chainguard | superset-ci | 0 | 4.1.2-r6 |
| Chainguard | superset-entrypoint | 0 | 4.1.2-r6 |
| Chainguard | superset-iamguarded-compat | 0 | 4.1.2-r6 |
| Chainguard | tensorflow-cpu-jupyter | 0 | 2.20.0-r4 |
| Chainguard | tensorflow-gpu-jupyter | 0 | 2.20.0-r3 |
| Chainguard | text-generation-inference | 0 | 3.3.6-r3 |
| Chainguard | text-generation-inference-compat | 0 | 3.3.6-r3 |
| Debian:11 | python-urllib3 | 0 | No fix |
| Debian:12 | python-urllib3 | 0 | No fix |
Related vulnerabilities
- CVE-2026-21441 Decompression-bomb safeguards bypassed when following HTTP redirects (streaming API) Also affects urllib3
- CVE-2025-66471 urllib3 Streaming API improperly handles highly compressed data Also affects urllib3
- CVE-2025-66418 urllib3 allows an unbounded number of links in the decompression chain Also affects urllib3
- CVE-2025-50181 urllib3 redirects are not disabled when retries are disabled on PoolManager instantiation Also affects urllib3
- CVE-2026-4539 A security flaw has been discovered in pygments up to 2.19.2. Also affects airflow-2
- CVE-2026-30922 pyasn1 is a generic ASN.1 library for Python. Also affects airflow-2
- CVE-2026-27448 pyOpenSSL is a Python wrapper around the OpenSSL library. Also affects airflow-2
- CVE-2026-27459 pyOpenSSL is a Python wrapper around the OpenSSL library. Also affects airflow-2