You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
http.client.HTTPSConnection._tunnel raises AttributeError: 'NoneType' object has no attribute 'encode' when header value is None #159044
Calling http.client.HTTPSConnection._tunnel() raises AttributeError: 'NoneType' object has no attribute 'encode' when _tunnel_headers contains a header key with a None value.
This regression affects third-party libraries (such as Apache Thrift and CodeChecker) that pass header dictionaries containing None values into set_tunnel(..., headers=...) or HTTP connection objects.
In Python 3.10.21, _tunnel() introduced strict encoding of tunnel headers (value.encode("latin-1")) without checking if the header value is None.
Target Python Version
Python 3.10.21 (Regression introduced between 3.10.20 and 3.10.21)
Operating System
Linux / Ubuntu
Code to Reproduce
import http.client
conn = http.client.HTTPSConnection("127.0.0.1", 8080)
# Set tunnel with a header value of None
conn.set_tunnel("example.com", 443, headers={"X-Test-Header": None})
# Call _tunnel() to trigger the issue
conn._tunnel()
Expected Behavior
_tunnel() should gracefully handle or filter out header keys with None values (or handle string conversions) rather than crashing with an AttributeError.
Actual Behavior
Traceback (most recent call last):
File "reproduce.py", line 9, in <module>
conn._tunnel()
File "/usr/lib/python3.10/http/client.py", line 936, in _tunnel
value_bytes = value.encode("latin-1")
AttributeError: 'NoneType' object has no attribute 'encode'
As for the None value, I think that would require different considerations as to (1) omit, (2) error, (3) convert to "None", (4) convert to ""
I would lean towards 3 if it matches old behavior the closest (my opinion, not a core dev), but perhaps you could open an issue on discuss.python.org to get more exposure and opinions:)
Agreed on converting to str in general, that restores the old behavior for ints and other objects.
For None I'd lean towards (1) omit, but that's only the way I see it and, as I said, I'm new to the community.
With (3) we keep sending Proxy-Authorization: None, and with (4) we'd still send an auth header, just with an empty value. Omitting it matches what the caller actually means: "there is no value for this header". I think that's how requests treats a header set to None.
It would be simple: skip the header if the value is None, otherwise str(value) and then the CR/LF check as today.
I've never opened a thread on discuss.python.org, but I'm happy to do it if you think that's needed. This is my first contribution here and I'm really glad to help :D
I also agree that it’s important to preserve the current behavior so we don’t introduce a breaking change for existing users of this API and to keep it stable. That’s why I like the idea of returning "None" as a string.
That said, since Python is a language built for the future, it would be more versatile and more appropriate to follow industry standards and return null, that is, a proper None.
The first option benefits people who already depend on this behavior, while the second benefits those who will adopt it later. Out of respect for current users, we could emit a deprecation warning announcing the change and then make it effective after a transition period. That way, everyone is happy.
Just to clarify, I'm working with @bielpieroli, and as suggested to him by @edvilme, I started a discussion topic. If you'd like to follow the discussion, it's here.
This is a private API. Why are people even using it, and why are we considering providing support for such usage? If we want to make it supported usage, let's expose a new, official method (which can simply be called tunnel) and give it whatever semantics make the most sense for future users. Existing users can continue to use the private method (working around its limitations and quirks as needed) until they can transition to the new, supported one.
Calling .connect() with tunnels set internally calls the private method, so the bug is present in regular case scenarios and even with 3rd party libraries
Activity
edvilme commentedon Oct 10, 2026
I think we should convert to str either way
As for the
Nonevalue, I think that would require different considerations as to (1) omit, (2) error, (3) convert to"None", (4) convert to""I would lean towards 3 if it matches old behavior the closest (my opinion, not a core dev), but perhaps you could open an issue on discuss.python.org to get more exposure and opinions:)
bielpieroli commentedon Oct 10, 2026
Agreed on converting to
strin general, that restores the old behavior for ints and other objects.For
NoneI'd lean towards (1) omit, but that's only the way I see it and, as I said, I'm new to the community.With (3) we keep sending
Proxy-Authorization: None, and with (4) we'd still send an auth header, just with an empty value. Omitting it matches what the caller actually means: "there is no value for this header". I think that's howrequeststreats a header set toNone.It would be simple: skip the header if the value is
None, otherwisestr(value)and then the CR/LF check as today.I've never opened a thread on discuss.python.org, but I'm happy to do it if you think that's needed. This is my first contribution here and I'm really glad to help :D
LorenaMBorges commentedon Oct 10, 2026
I also agree that it’s important to preserve the current behavior so we don’t introduce a breaking change for existing users of this API and to keep it stable. That’s why I like the idea of returning "None" as a string.
That said, since Python is a language built for the future, it would be more versatile and more appropriate to follow industry standards and return null, that is, a proper None.
The first option benefits people who already depend on this behavior, while the second benefits those who will adopt it later. Out of respect for current users, we could emit a deprecation warning announcing the change and then make it effective after a transition period. That way, everyone is happy.
LorenaMBorges commentedon Oct 10, 2026
Just to clarify, I'm working with @bielpieroli, and as suggested to him by @edvilme, I started a discussion topic. If you'd like to follow the discussion, it's here.
ZeroIntensity commentedon Oct 11, 2026
I think we should just
str()both the key and the value, since that's what the previous behavior was. It should be as simple as:It'd also be nice if we explicitly documented that the headers are converted to a string.
pfmoore commentedon Oct 11, 2026
This is a private API. Why are people even using it, and why are we considering providing support for such usage? If we want to make it supported usage, let's expose a new, official method (which can simply be called
tunnel) and give it whatever semantics make the most sense for future users. Existing users can continue to use the private method (working around its limitations and quirks as needed) until they can transition to the new, supported one.edvilme commentedon Oct 11, 2026
Calling
.connect()with tunnels set internally calls the private method, so the bug is present in regular case scenarios and even with 3rd party librariespythongh-159044: Fix http.client tunnel headers with non-string values
pythongh-159044: Fix http.client tunnel headers with non-string values
ZeroIntensity commentedon Oct 12, 2026
I had Codex track down libraries that are affected and I came up with a script to reproduce it:
Details
So, concretely, (at least) all of the following are affected:
But, I couldn't find any reports on their issue trackers, so I guess this has just gone unnoticed so far.
gh-159044: Restore string conversion for HTTP tunnel header values (G…
[3.14] gh-159044: Restore string conversion for HTTP tunnel header va…
[3.15] gh-159044: Restore string conversion for HTTP tunnel header va…