Jul 14, 2026·5 min read·14 visits
TsDProxy failed to sanitize client-provided 'X-Forwarded-For' and 'X-Real-IP' headers, enabling authenticated attackers to spoof their identity and bypass downstream IP-based administrative restrictions.
A high-severity input sanitization and header injection vulnerability in TsDProxy allows authenticated Tailscale users to inject arbitrary values into the X-Forwarded-For and X-Real-IP HTTP headers. Because downstream backend services frequently trust these headers to resolve client identities, attackers can exploit this flaw to bypass IP-based access control lists, audit logs, and geo-blocking restrictions.
TsDProxy is a Go-based helper proxy designed to route traffic from a Tailscale network (tailnet) to internal backend services. It acts as an authenticated reverse proxy that terminates TLS and routes HTTP requests to non-exposed internal containers. In typical deployments, downstream services reside behind TsDProxy and implicitly trust its authentication header metadata.
The vulnerability is classified under CWE-74 (Improper Neutralization of Special Elements in Output Used by a Downstream Component). It manifests as an IP spoofing flaw where the proxy fails to scrub or validate incoming 'X-Forwarded-For' and 'X-Real-IP' headers before forwarding them to downstream backend components.
Because the backend applications often rely on these headers to enforce security-critical access policies, an attacker can manipulate them to execute unauthorized actions. This flaw allows any authenticated Tailscale user to bypass network segregation and assume administrative roles on the backend.
The root cause of this vulnerability lies in the 'newPortProxy' function within 'internal/proxymanager/port.go'. The proxy uses Go's 'net/http/httputil' reverse proxy standard library package to manage incoming connections. It registers a 'Rewrite' closure to execute custom headers management before sending the request onward.
While the proxy correctly strips custom application headers like 'HeaderID' or 'HeaderRemoteUser', it originally failed to sanitize standard proxy headers like 'X-Forwarded-For' and 'X-Real-IP'. When 'r.SetXForwarded()' is called, it does not overwrite existing 'X-Forwarded-For' values if they are already present in the incoming client request.
Instead, according to Go's standard library documentation, 'SetXForwarded' appends the actual remote address of the client to the pre-existing list. Consequently, if an attacker sends an initial header of 'X-Forwarded-For: 127.0.0.1', the outgoing request becomes 'X-Forwarded-For: 127.0.0.1, <attacker_ip>'. Downstream engines that parse the first element of this CSV string are deceived into identifying the client as '127.0.0.1'.
To understand the vulnerability, examine the 'newPortProxy' function before the fix was implemented. The proxy stripped key identification headers but left the standard proxy routing headers untouched before execution of 'r.SetXForwarded()':
// Vulnerable Code Path
Rewrite: func(r *httputil.ProxyRequest) {
r.SetURL(pconfig.GetFirstTarget())
r.Out.Host = r.In.Host
// Delete custom authentication headers
r.Out.Header.Del(consts.HeaderID)
r.Out.Header.Del(consts.HeaderRemoteUser)
// CRITICAL OMISSION: X-Forwarded-For is not deleted
r.SetXForwarded()
}The patch introduced in version '3.0.0-alpha.3' fixes this behavior by declaring 'HeaderXForwardedFor' in 'TrustedProxyHeaders' to ensure automatic scrubbing before rewriting. The revised code strips incoming headers and enforces authoritative IP overwriting:
// Patched Code Path in internal/proxymanager/port.go
r.SetXForwarded() // SetXForwarded now works on clean headers
if peerIP := resolvePeerIP(r.In); peerIP != "" {
r.Out.Header.Set(consts.HeaderRealIP, peerIP)
r.Out.Header.Set(consts.HeaderXForwardedFor, peerIP) // Overrides with authoritative IP
}This remediation completely removes reliance on client-supplied values. By overriding both headers with the authoritative IP extracted from the connection socket state via 'resolvePeerIP', the proxy eliminates downstream parsing ambiguity.
Exploitation of this vulnerability requires network access to the TsDProxy endpoint, which implies the attacker must be authenticated to the target Tailscale network (tailnet). The attacker does not require administrative privileges on either the proxy or the target backend system.
The attack is executed by appending custom HTTP headers to the request. The following bash command demonstrates how an attacker can craft a request targeting a backend server configured with local administrative restrictions:
curl -H "X-Forwarded-For: 127.0.0.1" \
-H "X-Real-IP: 127.0.0.1" \
https://backend.tailscale-proxy.ts.net/adminUpon receiving the request, the proxy appends the legitimate tailnet IP to the existing spoofed header list. When Nginx or another reverse proxy upstream on the backend server processes the headers, it extracts '127.0.0.1' as the original sender, granting access to restricted endpoints.
The impact of this header injection vulnerability is rated High, with a CVSS v3.1 score of 8.5. The primary consequence is the total bypass of IP-based authentication controls on downstream systems. This is particularly critical because many administrators run backends behind TsDProxy under the assumption that only valid, non-spoofed Tailscale client IPs can reach them.
The attack vector is network-based (AV:N), requiring low complexity (AC:L) and low privileges (PR:L). Critically, the scope metric is changed (S:C) because the security boundary of the proxy is violated, directly compromising the authorization layer of downstream components.
While there is no direct risk of Denial of Service (A:N), confidentiality (C:L) and integrity (I:H) are heavily impacted. Attackers can execute administrative state-changing operations if backend management interfaces rely on source IP validation.
The recommended remediation is to upgrade TsDProxy to version '3.0.0-alpha.3' or higher immediately. This version integrates 'HeaderXForwardedFor' into the 'TrustedProxyHeaders' block, enforcing proper stripping of client-supplied routing parameters before they are processed.
If upgrading is not immediately possible, administrators must implement workarounds at the downstream application tier. For example, if Nginx is deployed as the backend server, avoid trusting the leftmost entry of the header unless recursive verification is enabled. Ensure configuration uses 'real_ip_recursive on' and restrict the trusted proxy addresses exclusively to the TsDProxy IP range.
Furthermore, developers should avoid relying solely on the 'X-Forwarded-For' or 'X-Real-IP' headers for authentication and authorization. Critical admin endpoints should enforce robust cryptographic sessions or application-level authentication (e.g., tokens or client certs) rather than relying exclusively on source-network layer identification.
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:H/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
tsdproxy almeidapaulopt | < 3.0.0-alpha.3 | 3.0.0-alpha.3 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-74 |
| Attack Vector | Network |
| CVSS v3.1 | 8.5 (High) |
| Impact | Security Bypass / IP Spoofing |
| Exploit Status | poc |
| KEV Status | Not Listed |
Improper Neutralization of Special Elements in Output Used by a Downstream Component ('Injection')
CVE-2026-12243 is a path traversal vulnerability in the Natural Language Toolkit (NLTK) version 3.9.4. The flaw exists because the input validation routine fails to account for percent-encoded directory traversal sequences like '..%2f' before passing them to urllib.request.url2pathname(), which decodes them into active traversal sequences.
CVE-2026-73654 is a high-severity prototype pollution vulnerability in Trigger.dev. The flaw occurs during the handling of run-metadata updates through the PUT /api/v1/runs/:runId/metadata endpoint. Because user-supplied keys are parsed directly by the @jsonhero/path library without sanitization, an authenticated attacker with low privileges can pollute the global Object.prototype. This causes database queries via Prisma ORM to fail validation and induces unhandled exceptions in the Prometheus metrics client, resulting in a process-wide denial of service.
CVE-2026-73559 is an uncontrolled resource consumption vulnerability in the vLLM engine, specifically within the /v1/completions API endpoint, allowing authenticated attackers to cause application-level denial of service via unbounded prompt arrays.
A critical security bypass vulnerability in Argo Workflows allows authenticated attackers with workflow submission privileges to bypass 'Strict' or 'Secure' template referencing restrictions. By injecting unvalidated fields into the nested ArtifactGC configuration, attackers can execute arbitrary pod patches, leading to host namespace escape and cluster-wide privilege escalation.
A Server-Side Request Forgery (SSRF) / Confused Deputy vulnerability has been identified in Pydantic AI UI Adapters (such as VercelAIAdapter). Under certain conditions, a malicious client can supply manipulated message history with provider metadata that forces the server to resolve files within privileged cloud environments (AWS S3, Google Cloud Storage) or model providers. This occurs because the adapters deserialize client-provided metadata structures directly into UploadedFile instances without validation, which are subsequently fetched using high-privilege server credentials.
A path traversal vulnerability in the optional dashboard server of atomic-agents-stack before version 1.1.0 allows unauthenticated remote attackers to read arbitrary files from the host filesystem.