VYPR
Moderate severityNVD Advisory· Published Jun 19, 2025· Updated Dec 22, 2025

urllib3 does not control redirects in browsers and Node.js

CVE-2025-50182

Description

urllib3 is a user-friendly HTTP client library for Python. Starting in version 2.2.0 and prior to 2.5.0, urllib3 does not control redirects in browsers and Node.js. urllib3 supports being used in a Pyodide runtime utilizing the JavaScript Fetch API or falling back on XMLHttpRequest. This means Python libraries can be used to make HTTP requests from a browser or Node.js. Additionally, urllib3 provides a mechanism to control redirects, but the retries and redirect parameters are ignored with Pyodide; the runtime itself determines redirect behavior. This issue has been patched in version 2.5.0.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
urllib3PyPI
>= 2.2.0, < 2.5.02.5.0

Affected products

1

Patches

1
7eb4a2aafe49

Merge commit from fork

https://github.com/urllib3/urllib3Illia VolochiiJun 18, 2025via ghsa
4 files changed · +69 1
  • CHANGES.rst+2 0 modified
    @@ -4,6 +4,8 @@
     - Fixed a security issue where restricting the maximum number of followed
       redirects at the ``urllib3.PoolManager`` level via the ``retries`` parameter
       did not work.
    +- Made the Node.js runtime respect redirect parameters such as ``retries``
    +  and ``redirects``.
     - TODO: add other entries in the release PR.
     
     
    
  • docs/reference/contrib/emscripten.rst+1 1 modified
    @@ -65,7 +65,7 @@ Features which are usable with Emscripten support are:
     * Timeouts
     * Retries
     * Streaming (with Web Workers and Cross-Origin Isolation)
    -* Redirects (determined by browser/runtime, not restrictable with urllib3)
    +* Redirects (urllib3 controls redirects in Node.js but not in browsers where behavior is determined by runtime)
     * Decompressing response bodies
     
     Features which don't work with Emscripten:
    
  • src/urllib3/contrib/emscripten/fetch.py+20 0 modified
    @@ -573,6 +573,11 @@ def send_jspi_request(
             "method": request.method,
             "signal": js_abort_controller.signal,
         }
    +    # Node.js returns the whole response (unlike opaqueredirect in browsers),
    +    # so urllib3 can set `redirect: manual` to control redirects itself.
    +    # https://stackoverflow.com/a/78524615
    +    if _is_node_js():
    +        fetch_data["redirect"] = "manual"
         # Call JavaScript fetch (async api, returns a promise)
         fetcher_promise_js = js.fetch(request.url, _obj_from_dict(fetch_data))
         # Now suspend WebAssembly until we resolve that promise
    @@ -693,6 +698,21 @@ def has_jspi() -> bool:
             return False
     
     
    +def _is_node_js() -> bool:
    +    """
    +    Check if we are in Node.js.
    +
    +    :return: True if we are in Node.js.
    +    :rtype: bool
    +    """
    +    return (
    +        hasattr(js, "process")
    +        and hasattr(js.process, "release")
    +        # According to the Node.js documentation, the release name is always "node".
    +        and js.process.release.name == "node"
    +    )
    +
    +
     def streaming_ready() -> bool | None:
         if _fetcher:
             return _fetcher.streaming_ready
    
  • test/contrib/emscripten/test_emscripten.py+46 0 modified
    @@ -960,6 +960,52 @@ def pyodide_test(selenium_coverage: typing.Any, host: str, port: int) -> None:
         )
     
     
    +@pytest.mark.with_jspi
    +def test_disabled_redirects(
    +    selenium_coverage: typing.Any, testserver_http: PyodideServerInfo
    +) -> None:
    +    """
    +    Test that urllib3 can control redirects in Node.js.
    +    """
    +
    +    @run_in_pyodide  # type: ignore[misc]
    +    def pyodide_test(selenium_coverage: typing.Any, host: str, port: int) -> None:
    +        import pytest
    +
    +        from urllib3 import PoolManager, request
    +        from urllib3.contrib.emscripten.fetch import _is_node_js
    +        from urllib3.exceptions import MaxRetryError
    +
    +        if not _is_node_js():
    +            pytest.skip("urllib3 does not control redirects in browsers.")
    +
    +        redirect_url = f"http://{host}:{port}/redirect"
    +
    +        with PoolManager(retries=0) as http:
    +            with pytest.raises(MaxRetryError):
    +                http.request("GET", redirect_url)
    +
    +            response = http.request("GET", redirect_url, redirect=False)
    +            assert response.status == 303
    +
    +        with PoolManager(retries=False) as http:
    +            response = http.request("GET", redirect_url)
    +            assert response.status == 303
    +
    +        with pytest.raises(MaxRetryError):
    +            request("GET", redirect_url, retries=0)
    +
    +        response = request("GET", redirect_url, redirect=False)
    +        assert response.status == 303
    +
    +        response = request("GET", redirect_url, retries=0, redirect=False)
    +        assert response.status == 303
    +
    +    pyodide_test(
    +        selenium_coverage, testserver_http.http_host, testserver_http.http_port
    +    )
    +
    +
     def test_insecure_requests_warning(
         selenium_coverage: typing.Any, testserver_http: PyodideServerInfo
     ) -> None:
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

5

News mentions

0

No linked articles in our index yet.