Skip to content

Silent proxy bypass: kill switch doesn't trigger if proxy degrades without exception #2

@ponich

Description

@ponich

Problem

The kill switch activates only when a RequestException is raised (__init__.py:72-74):

try:
    return super().request(method, url, **kwargs)
except RequestException as e:
    self._killed = True
    raise ProxyError(f"Proxy failed, kill switch ON: {e}") from e

This design assumes that a failing proxy will always raise an exception. However, there are scenarios where the proxy could silently stop routing traffic through itself without raising any exception:

  1. Transparent proxy failover — some proxy configurations or network setups may silently fall back to a direct connection if the SOCKS5 handshake fails at the network level but the TCP connection to the destination succeeds via a different route.
  2. Misconfigured proxy — a proxy that accepts the connection but doesn't actually tunnel the traffic (returns the response directly or via a different path).
  3. Library behavior changes — future versions of requests or PySocks could change exception-raising behavior.

In any of these cases, traffic flows directly from the user's real IP, and the kill switch never activates because no RequestException was thrown.

Why check_ip() alone is insufficient

The check_ip() method can detect this scenario, but it must be called manually by the user. There is no automatic, periodic, or per-request IP verification. A user who calls session.get(url) 1000 times without ever calling check_ip() has no protection against silent proxy bypass.

Proposed solution

Option A: Periodic automatic IP check

Add an optional automatic check_ip() call every N requests:

def __init__(self, ..., check_interval: int = 0):
    self._check_interval = check_interval
    self._request_count = 0

def request(self, method, url, **kwargs):
    if self._killed:
        raise ProxyError(...)
    
    self._request_count += 1
    if self._check_interval > 0 and self._request_count % self._check_interval == 0:
        self.check_ip()
    
    # ... proceed with request

Option B: Verify proxy headers on every response

Check for proxy-identifying response headers (e.g., Via, X-Forwarded-For) or verify the connection was actually routed through SOCKS5 at the socket level.

Option C: Document the limitation

At minimum, clearly document that check_ip() should be called periodically by user code, and explain the risk of silent bypass.

Impact

  • Severity: High — real IP is exposed with no warning or kill switch activation
  • Scope: All users who don't manually call check_ip() between requests
  • Claim affected: "your IP never leaks" is false in this scenario

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions