fix(security): harden Python service boundaries

This commit is contained in:
Alexandre Teixeira
2026-10-06 03:16:54 +01:00
parent da3800b662
commit 3d91ad82cb
32 changed files with 1911 additions and 248 deletions
+49
View File
@@ -154,3 +154,52 @@ def check_outbound_url(
if not saw_ip:
return False, "host does not resolve to an IP"
return True, "ok"
class OutboundAddressBlocked(PermissionError):
"""A non-HTTP outbound host resolves into a disallowed address range."""
def connect_outbound_tcp(
host: str,
port: int,
*,
timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
block_private: bool = False,
source_address=None,
resolver: Optional[Callable[..., list]] = None,
) -> socket.socket:
"""Open a TCP connection to *host* under the outbound address policy.
For raw TCP clients (IMAP/SMTP). The host is resolved exactly once, every
resolved address is judged with the same policy as check_outbound_url, and
the socket connects only to those already-judged addresses. A second
lookup at connect time (what socket.create_connection(host) does) would let
a rebinding name pass the check with a public answer and then connect to
metadata/private space. TLS callers keep wrapping the returned socket with
``server_hostname=host``, so SNI and certificate checks are unchanged.
"""
resolve = resolver or socket.getaddrinfo
infos = resolve(host, port, 0, socket.SOCK_STREAM)
for _family, _type, _proto, _canon, sockaddr in infos:
ip = ipaddress.ip_address(str(sockaddr[0]).split("%")[0])
reason = _classify(ip, block_private=block_private)
if reason:
raise OutboundAddressBlocked(reason)
last_error: Optional[OSError] = None
for family, socktype, proto, _canon, sockaddr in infos:
sock = socket.socket(family, socktype, proto)
try:
if timeout is not socket._GLOBAL_DEFAULT_TIMEOUT: # same contract as create_connection
sock.settimeout(timeout)
if source_address:
sock.bind(source_address)
sock.connect(sockaddr)
return sock
except OSError as exc:
last_error = exc
sock.close()
if last_error is not None:
raise last_error
raise socket.gaierror(socket.EAI_NONAME, f"{host} did not resolve to an address")