Skip to content

Not support "no_proxy" including value of ipv6 prefix style #3221

Open

Description

@piamo

Example: no_proxy=fe11::/16

How to reproduce:

no_proxy=fe11::/16 python -c 'import httpx; c = httpx.Client()'

it will raise:

Traceback (most recent call last):
  File "/usr/local/lib/python3.9/dist-packages/httpx/_urlparse.py", line 346, in normalize_port
    port_as_int = int(port)
ValueError: invalid literal for int() with base 10: ':'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "/usr/local/lib/python3.9/dist-packages/httpx/_client.py", line 695, in __init__
    self._mounts: dict[URLPattern, BaseTransport | None] = {
  File "/usr/local/lib/python3.9/dist-packages/httpx/_client.py", line 696, in <dictcomp>
    URLPattern(key): None
  File "/usr/local/lib/python3.9/dist-packages/httpx/_utils.py", line 370, in __init__
    url = URL(pattern)
  File "/usr/local/lib/python3.9/dist-packages/httpx/_urls.py", line 115, in __init__
    self._uri_reference = urlparse(url, **kwargs)
  File "/usr/local/lib/python3.9/dist-packages/httpx/_urlparse.py", line 248, in urlparse
    parsed_port: int | None = normalize_port(port, scheme)
  File "/usr/local/lib/python3.9/dist-packages/httpx/_urlparse.py", line 348, in normalize_port
    raise InvalidURL(f"Invalid port: {port!r}")
httpx.InvalidURL: Invalid port: ':'

httpx version: 0.27.0

Activity

  1. changed the title [-]Not support "no_proxy" including pattern of ipv6 prefix style[/-] [+]Not support "no_proxy" including value of ipv6 prefix style[/+] on Jun 12, 2024
  2. matstrand commented on Aug 28, 2024

    @matstrand

    Another test case to throw in that is affecting us:

     no_proxy=[::1] python -c 'import httpx; c = httpx.Client()'
    ...
    httpx.InvalidURL: Invalid port: ':1]'
    
  3. Godsing commented on Sep 10, 2024

    @Godsing

    For no_proxy=127.0.0.0/8, get_environment_proxies() will get all://127.0.0.0/8.
    So, when creating a Client, in which URL class will be called, ⁠/8 will be treated as a URL path finally.

    python -c 'import httpx; url = httpx.URL("all://127.0.0.0/8"); print(url.path)'
    /8

    httpx version: 0.27.0

  4. lovelydinosaur commented on Sep 10, 2024

    @lovelydinosaur
    Member

    Duplicate of #1536

    No we don't currently support subnet masks here, yes we should at least raise an error if they're in play.

    Aside... what's an example of a real-world scenario where it's useful to have a subnet masking used to toggle proxy routing on/off?

  5. yanyongyu commented on Sep 29, 2024

    @yanyongyu

    I also encountered this issue when using proxy in company's network. We use proxy to visit outside networks and automatically set no_proxy with both ipv4/ipv6 masking to prevent proxying specific ranges of IPs, such as 127.0.0.0/8,169.254.0.0/16,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,10.0.0.0/8,::1,fe80::/10,fd00::/8.

  6. GreyElaina commented on Dec 7, 2024

    @GreyElaina
    Contributor

    Here’s my take:

    For no_proxy, I think we should address the issue in phases, so create a branch rather than do all in the main one.

    Let’s fix the crash problem first, rather than implementing the full semantic functionality (like subnet mask support) in httpx. Since no_proxy is commonly used in VPN and Zero Trust setups, It could be to adapt the no_proxy parser to handle these cases and prevent crashes at the first step.

    Of course, just preventing crashes isn’t a perfect solution, it will break the intended semantics of no_proxy and could lead to unexpected behavior.

    I also suggest considering a refactor of the proxy handling in httpx, allowing dispatching at both the domain and the ip:port level. However, this might be too extreme and could cause a breaking change to the API. I’d love to hear ideas from you all.

    As for some potential concerns, here are my quick responses:

    1. Is ensuring stability and preventing application crashes a valid intermediate step?

    I think this could change behavior between versions, which I’m not comfortable with—even with a warning mechanism in place.

    1. Is limiting or configurable advanced features acceptable?

    That would compromise the consistency of the API and break the intended semantics of no_proxy inside.

    1. Could this introduce too much complexity?

    I’m not sure, so I wander tom on this.

    1. How do other HTTP libraries handle subnet masks? Anything to learn from them?

    Some languages do often require extra configuration(java, nodejs), but Python’s tradition is to make things work out-of-the-box.
    (I doubt other languages would adopt the idea of ignoring environment variables by default.)

  7. jinyu121 commented on May 23, 2025

    @jinyu121

    I'm also facing this issue.

    In my environment, the following no_proxy environment variable is set on every machine. It looks so normal and standard:

    no_proxy=some_domain.org,localhost,127.0.0.1,::1,10.0.0.0/8,127.0.0.0/8,fd00::/8,100.64.0.0/10,fe80::/10,172.16.0.0/12,169.254.0.0/16,192.168.0.0/16
    

    And httpx==0.28.1 is installed by default with vllm or some other packages.

    When I want to do httpx.get(something), there must be an error:

    Traceback (most recent call last):
      File "/home/jinyu121/.pyenv/versions/3.11.2/lib/python3.11/site-packages/httpx/_urlparse.py", line 409, in normalize_port
        port_as_int = int(port)
                      ^^^^^^^^^
    ValueError: invalid literal for int() with base 10: ':'
    
    During handling of the above exception, another exception occurred:
    
    Traceback (most recent call last):
      (... my code is omitted there ...)
    
      File "/home/jinyu121/.pyenv/versions/3.11.2/lib/python3.11/site-packages/httpx/_api.py", line 195, in get
        return request(
               ^^^^^^^^
      File "/home/jinyu121/.pyenv/versions/3.11.2/lib/python3.11/site-packages/httpx/_api.py", line 102, in request
        with Client(
             ^^^^^^^
      File "/home/jinyu121/.pyenv/versions/3.11.2/lib/python3.11/site-packages/httpx/_client.py", line 697, in __init__
        self._mounts: dict[URLPattern, BaseTransport | None] = {
                                                               ^
      File "/home/jinyu121/.pyenv/versions/3.11.2/lib/python3.11/site-packages/httpx/_client.py", line 698, in <dictcomp>
        URLPattern(key): None
        ^^^^^^^^^^^^^^^
      File "/home/jinyu121/.pyenv/versions/3.11.2/lib/python3.11/site-packages/httpx/_utils.py", line 172, in __init__
        url = URL(pattern)
              ^^^^^^^^^^^^
      File "/home/jinyu121/.pyenv/versions/3.11.2/lib/python3.11/site-packages/httpx/_urls.py", line 117, in __init__
        self._uri_reference = urlparse(url, **kwargs)
                              ^^^^^^^^^^^^^^^^^^^^^^^
      File "/home/jinyu121/.pyenv/versions/3.11.2/lib/python3.11/site-packages/httpx/_urlparse.py", line 321, in urlparse
        parsed_port: int | None = normalize_port(port, scheme)
                                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "/home/jinyu121/.pyenv/versions/3.11.2/lib/python3.11/site-packages/httpx/_urlparse.py", line 411, in normalize_port
        raise InvalidURL(f"Invalid port: {port!r}")
    httpx.InvalidURL: Invalid port: ':'
    

    I tried to upgrade httpx to the newest version, but the problem isn't solved.

    The only way to solve this is to downgrade httpx to 0.23.3 via pip install httpx=0.23.3. I have to do this every time a dev environment is established. It's so annoying.

    Could anybody solve this? Thanks a lot.

  8. added 2 commits that reference this issue on Jan 6, 2026
    a0b1fcc
    3e53fe3
  9. p3ck commented on Jan 6, 2026

    @p3ck

    @jinyu121 Can you try my PR from above and tell me if everything works as expected?
    Thanks

  10. Indekkusu545 commented on Feb 4, 2026

    @Indekkusu545

    Duplicate of #1536

    No we don't currently support subnet masks here, yes we should at least raise an error if they're in play.

    Aside... what's an example of a real-world scenario where it's useful to have a subnet masking used to toggle proxy routing on/off?

    I think printing a WARNING is better than raising an error httpx.InvalidURL: Invalid port: ':]' that confusing. I think this is a helpful message style: WARNING: no_proxy entry ::1/128 will be ignored: httpx.InvalidURL: Invalid port: ':'.

  11. added a commit that references this issue on Sep 24, 2026
  12. DDDMUC commented on Oct 16, 2026

    @DDDMUC

    Adding a data point for a bracketed IPv6 loopback entry, which I believe is
    a distinct and more widespread variant than the IPv6-CIDR case in the original report.

    Reproduction

    $ NO_PROXY='127.0.0.1,localhost,::1,[::1]' python -c 'import httpx; httpx.Client()'
    Traceback (most recent call last):
      ...
    httpx.InvalidURL: Invalid port: ':1]'

    httpx version: 0.28.1

    Where it comes from

    NO_PROXY=127.0.0.1,localhost,::1,[::1] is the exact value Clash Verge / mihomo
    exports by default
    on Windows (and Linux). Any Python application using a bare
    httpx.Client() / httpx.get() then fails at client construction — before the first
    request is even attempted.

    Mechanism

    In httpx/_utils.py::get_environment_proxies, a bracketed entry misses every
    recognised branch:

    elif is_ipv4_hostname(hostname):      # [::1] -> False
    elif is_ipv6_hostname(hostname):      # [::1] -> False (brackets are not stripped)
    elif hostname.lower() == "localhost": # False
    else:
        mounts[f"all://*{hostname}"] = None   # -> "all://*[::1]"

    is_ipv6_hostname("[::1]") returns False, so the entry falls into the wildcard
    branch and produces the unparseable pattern all://*[::1]. URLPattern then
    URL-parses it and treats :1] as the port.

    Measured difference between bare and bracketed

    NO_PROXY value resulting mount key httpx.Client()
    ::1 (bare) all://[::1] ✅ constructs
    [::1] (bracketed) all://*[::1] ❌ InvalidURL: Invalid port: ':1]'
    [::1]:8080 all://*[::1]:8080 ❌ Invalid port: ':1]:8080'
    ::1/128 all://[::1/128] ❌ Invalid port: ':1'
    http://example.com http://example.com ✅ constructs

    So the bare form already works (fixed by #2659); the bracketed form — the one
    proxy tools actually emit — still crashes. It is dead weight in other consumers too:
    urllib.request.proxy_bypass_environment("::1", {"no": "[::1]"}) returns False
    (verified locally against CPython 3.12), so nothing matches the bracketed form. It is
    only meaningful as URL-authority syntax, which is why proxy UIs emit it, but it is not
    valid NO_PROXY syntax.

    Suggested fix

    Treat bracketed IPv6 as an IPv6 hostname rather than falling through to the wildcard
    branch, e.g. strip a leading [ / trailing ] (and any :port suffix) before the
    is_ipv6_hostname check. That keeps the existing bypass semantics — ::1 already maps
    to all://[::1] and bypasses every port — while no longer crashing on the form proxy
    software actually writes.

    Downstream impact

    This is being patched independently in a number of projects, which suggests the blast
    radius is wide: e.g. NousResearch/hermes-agent#131802 (merged), MoonshotAI/kimi-code#2186,
    safedep/pmg#340, and mistralai/mistral-vibe#759. A fix upstream would let all of them
    drop their local workarounds.

    Thanks for looking at this — happy to test a patch against the Clash Verge environment if
    that would help.

  13. xinyude commented on Oct 27, 2026

    @xinyude

    I tested the bracketed IPv6 variant described above and prepared an AI-assisted fix at xinyude@f28a4d9. The change recognizes [::1] and [::1]:port as exact IPv6 host patterns, and keeps the existing unsupported bracketed CIDR behavior unchanged. This is distinct from the CIDR support proposed in #3741.

    On Windows with Python 3.12, 65 targeted proxy tests passed, covering both clients, matching and non-matching addresses and ports, mixed entries, and bracketed CIDR. The broader non-network utility and proxy tests passed (125 passed, 3 deselected); Ruff and mypy checks passed.

    I attempted to open a PR, but GitHub reports that PR creation is restricted to collaborators. Would a maintainer be willing to review the branch and advise whether this change is appropriate to submit? The branch is https://github.com/xinyude/httpx/tree/codex/no-proxy-bracketed-ipv6.

  14. lxcxjxhx commented on Nov 7, 2026

    @lxcxjxhx

    Adding a second input shape that hits the same line, with a full traceback on a current release, because the bracketed form is what most shell wrappers actually export.

    Repro

    $ NO_PROXY='localhost,127.0.0.1,::1,[::1]' python -c 'import httpx; httpx.Client()'

    (The [::1] entry is the bracketed IPv6 literal form — the one curl, requests and most distro no_proxy conventions accept.)

    Traceback (most recent call last):
      File "<string>", line 1, in <module>
        httpx.AsyncClient()
      File "httpx/_client.py", line 1413, in __init__
        URLPattern(key): None
      File "httpx/_utils.py", line 172, in __init__
        url = URL(pattern)
      File "httpx/_urls.py", line 117, in __init__
        self._uri_reference = urlparse(url, **kwargs)
      File "httpx/_urlparse.py", line 321, in urlparse
        parsed_port: int | None = normalize_port(port, scheme)
      File "httpx/_urlparse.py", line 411, in normalize_port
        raise InvalidURL(f"Invalid port: {port!r}")
    httpx.InvalidURL: Invalid port: ':1]'
    

    httpx 0.28.1, CPython 3.13, Windows 11. On the reporter's fe11::/16 case the port fragment is ':'; here it is ':1]' — same path, same cause: a proxy pattern that is not a URL gets parsed as one.

    Why it is worth handling

    The failure is not graceful: every Client/AsyncClient construction raises, so the process cannot make any request at all, including to hosts the pattern was meant to exclude. Two suites I run on Windows were red for a while purely because of this (crewai-devtools project tests, semantic_kernel sessions plugin); dropping [::1] from NO_PROXY turns ~35 failures into passes without touching either repository.

    Shapes to consider

    no_proxy entry today
    example.com works
    10.0.0.0/8 works
    ::1 works
    [::1] InvalidURL
    fe11::/16 InvalidURL (reported above)

    A minimal fix in URLPattern.__init__ would be to parse a bracketed literal by its host part when the whole pattern is not a URL, e.g. strip the brackets before URL(...) and keep the bare address as the pattern; both failing rows above then behave like the third.

    If you want that as a PR I can send it with tests for the four rows — say whether you prefer the fix in URLPattern or in get_environment_proxies().

  15. draevonxx commented on Nov 9, 2026

    @draevonxx

    The bracketed variant is still fatal on the current release, and the in-flight fix does not cover it. I ran the same inputs against four trees instead of reasoning from the diff.

    #3741 does not close this. Its body says Closes: #3221, but the get_environment_proxies change only adds a / split inside the existing is_ipv6_hostname(hostname) branch. is_ipv6_hostname("[::1]") is still False, so a bracketed entry still falls through to mounts[f"all://*{hostname}"] and still produces all://*[::1]. The original report's fe11::/16 is fixed. The bracketed form reported in the comments above is not.

    Environment: httpx 0.28.1, CPython 3.11.14, Windows. All proxy variables unset except NO_PROXY. For each tree I asserted that httpx.__file__ pointed inside the tree under test, so no row silently fell back to the installed package.

    NO_PROXY stock 0.28.1 #3741 83d203a xinyude f28a4d9 both
    ::1 OK OK OK OK
    [::1] Invalid port: ':1]' Invalid port: ':1]' OK OK
    [::1]:8080 Invalid port: ':1]:8080' Invalid port: ':1]:8080' OK OK
    [2001:db8::1] Invalid port: 'db8::1]' Invalid port: 'db8::1]' OK OK
    fe11::/16 Invalid port: ':' OK Invalid port: ':' OK
    [::1]/128 Invalid port: ':1]' Invalid port: ':1]' Invalid port: ':1]' Invalid port: ':1]'
    127.0.0.0/8 OK OK OK OK
    localhost,127.0.0.1,::1,[::1] Invalid port: ':1]' Invalid port: ':1]' OK OK

    Every failing cell is httpx.InvalidURL raised from Client.__init__, before any request is attempted.

    Two things fall out of the matrix:

    1. The two proposed fixes are complementary, not competing. Fix no_proxy to support IPV6 CIDR format #3741 handles bare IPv6 CIDR; @xinyude's f28a4d9 handles bracketed hosts with and without a port. I applied the 7-line elif from f28a4d9 on top of Fix no_proxy to support IPV6 CIDR format #3741's head, and the last column is that result. They compose without conflict and together clear every row except bracketed CIDR. Since @xinyude reports being unable to open a PR, folding that elif into Fix no_proxy to support IPV6 CIDR format #3741 looks like the shortest path to both shapes being handled.

    2. [::1]/128 still raises under all four trees. f28a4d9 keeps that behaviour deliberately and has a test asserting it, so it is a stated limitation rather than an oversight. It is worth deciding explicitly whether that shape should raise a clear message or be supported, because @Indekkusu545's point above about Invalid port being a confusing way to report an unsupported no_proxy entry applies to it exactly.

    One more source of the bracketed form, since the thread currently attributes it to proxy UIs: on Windows a desktop agent tool here injects NO_PROXY=localhost,.localhost,127.0.0.1,127.0.0.0/8,[::1],::1 into the process environment of every shell it spawns. It is not a system setting, HKCU\Environment and the machine environment key both have no NO_PROXY, and ProxyEnable is 0. So any Python process launched from such a tool inherits a value that makes every httpx.Client() and httpx.AsyncClient() construction fail, including clients built inside third-party SDKs that the application cannot pass trust_env=False to.

    #3741 is currently mergeable: clean. Merged as-is it would close this issue while leaving the bracketed shape fatal, so either the bracketed elif lands in the same PR or the Closes: line should be narrowed to the CIDR case and this left open.

  16. Indekkusu545 commented on Nov 9, 2026

    @Indekkusu545

    Given that this repository is no longer maintained and both Issues and Pull Requests have been locked, should we consider moving to https://github.com/pydantic/httpx2 ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions