GHSA-g936-7jqj-mwv8
TSDProxy: Internal proxy auth token forwarded to backend services enables management API escalation
GHSA-g936-7jqj-mwv8 is a critical vulnerability (CVSS 9.0) in github.com/almeidapaulopt/tsdproxy, published July 10, 2026. No source lists public exploit code. Fixed in github.com/almeidapaulopt/tsdproxy 1.4.4-0.20260603142855-434819b4421e.
- Severity
- 9.0 CriticalCVSS 3.1, GitHub Advisories
- Exploit chance
- UnscoredNo EPSS score yet
- Exploitation
- None knownNot on CISA’s or ENISA’s lists
- Fixed in
1.4.4-0.20260603142855-434819b4421egithub.com/almeidapaulopt/tsdproxy
Description
Description
A vulnerability was discovered in TSDProxy where it forwards its internal per-process authentication token to all proxied backend services. When identityHeaders is enabled (the default), tsdproxy injects x-tsdproxy-auth-token into every upstream HTTP request alongside user identity headers. This token is the same secret used by the management HTTP server to trust forwarded Tailscale identity claims. A backend that receives this token can replay it from localhost to the management port with an arbitrary x-tsdproxy-id value, bypassing Tailscale authentication entirely.
The token is forwarded unconditionally: ProviderUserMiddleware always calls WhoisNewContext regardless of whether the user is authenticated. In the ReverseProxy.Rewrite function, WhoisFromContext returns ok=true even for zero-value Whois{} (unauthenticated or Funnel requests). The HeaderAuthToken is set for every request when identityHeaders=true.
The attack requires the backend to reach 127.0.0.1:8080. This holds in: (1) non-Docker deployments where tsdproxy and a backend run on the same host, (2) Docker host-network-mode containers, (3) containers sharing tsdproxy's network namespace.
Affected files
internal/proxymanager/port.go:123-132internal/core/admin.go:160-182
// port.go: auth token forwarded regardless of user authentication state
if identityHeaders {
if user, ok := model.WhoisFromContext(r.In.Context()); ok {
// ok=true even for empty Whois{} stored by ProviderUserMiddleware
r.Out.Header.Set(consts.HeaderAuthToken, core.ProxyAuthToken()) // token sent to backend
}
}
// admin.go: management port trusts x-tsdproxy-id from localhost when token is valid
func ResolveWhois(r *http.Request) model.Whois {
if IsLocalhost(r.RemoteAddr) {
return model.Whois{
ID: r.Header.Get(consts.HeaderID), // attacker-controlled after stealing token
}
}
return model.Whois{}
}
Steps to reproduce
- Deploy tsdproxy on a host (non-Docker) with a backend at
http://localhost:3000. - Make a request through the Tailscale proxy. The backend receives
x-tsdproxy-auth-tokenin the request headers. - From the host, replay the token to the management API:
# Capture token from backend headers (e.g., via a header-reflection endpoint)
TOKEN=$(curl -s http://localhost:3000/debug/headers | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['headers'].get('X-Tsdproxy-Auth-Token',''))")
# Replay from localhost to gain admin access
curl -H "x-tsdproxy-auth-token: $TOKEN" \
-H "x-tsdproxy-id: attacker" \
http://127.0.0.1:8080/api/v1/proxies
# Returns full proxy list with admin access
Fix
Remove HeaderAuthToken from the outgoing backend request, and guard identity-header injection on user.ID != "":
// port.go: only inject headers for actually authenticated users
if identityHeaders {
if user, ok := model.WhoisFromContext(r.In.Context()); ok && user.ID != "" {
r.Out.Header.Set(consts.HeaderID, user.ID)
// HeaderAuthToken should NOT be forwarded to backends
}
}
Impact
An attacker with code execution in any backend proxied by tsdproxy (on the same host) gains full management API control: restart or pause all proxied services (DoS), enumerate all proxy configurations and backend network topology, and trigger webhook deliveries (SSRF via configured webhook URLs).
Credits
Reported by Vishal Shukla (@shukla304 / @therawdev).
Sponsorship
This audit is from an AI-assisted research agent at sechub.dev. Running it on OSS projects is free for maintainers.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- High
- Privileges needed
- None
- User interaction
- None
What an attacker can affect
- Confidentiality
- High
- Integrity
- High
- Availability
- High
Affected packages
| Package | Affected | Fixed |
|---|---|---|
github.com/almeidapaulopt/tsdproxyGo | before 1.4.4-0.20260603142855-434819b4421e | 1.4.4-0.20260603142855-434819b4421e |
What each source reports
GitHub Advisories Score used 9.0
TSDProxy: Internal proxy auth token forwarded to backend services enables management API escalation
Description
A vulnerability was discovered in TSDProxy where it forwards its internal per-process authentication token to all proxied backend services. When identityHeaders is enabled (the default), tsdproxy injects x-tsdproxy-auth-token into every upstream HTTP request alongside user identity headers. This token is the same secret used by the management HTTP server to trust forwarded Tailscale identity claims. A backend that receives this token can replay it from localhost to the management port with an arbitrary x-tsdproxy-id value, bypassing Tailscale authentication entirely.
The token is forwarded unconditionally: ProviderUserMiddleware always calls WhoisNewContext regardless of whether the user is authenticated. In the ReverseProxy.Rewrite function, WhoisFromContext returns ok=true even for zero-value Whois{} (unauthenticated or Funnel requests). The HeaderAuthToken is set for every request when identityHeaders=true.
The attack requires the backend to reach 127.0.0.1:8080. This holds in: (1) non-Docker deployments where tsdproxy and a backend run on the same host, (2) Docker host-network-mode containers, (3) containers sharing tsdproxy's network namespace.
Affected files
internal/proxymanager/port.go:123-132internal/core/admin.go:160-182
// port.go: auth token forwarded regardless of user authentication state
if identityHeaders {
if user, ok := model.WhoisFromContext(r.In.Context()); ok {
// ok=true even for empty Whois{} stored by ProviderUserMiddleware
r.Out.Header.Set(consts.HeaderAuthToken, core.ProxyAuthToken()) // token sent to backend
}
}
// admin.go: management port trusts x-tsdproxy-id from localhost when token is valid
func ResolveWhois(r *http.Request) model.Whois {
if IsLocalhost(r.RemoteAddr) {
return model.Whois{
ID: r.Header.Get(consts.HeaderID), // attacker-controlled after stealing token
}
}
return model.Whois{}
}
Steps to reproduce
- Deploy tsdproxy on a host (non-Docker) with a backend at
http://localhost:3000. - Make a request through the Tailscale proxy. The backend receives
x-tsdproxy-auth-tokenin the request headers. - From the host, replay the token to the management API:
# Capture token from backend headers (e.g., via a header-reflection endpoint)
TOKEN=$(curl -s http://localhost:3000/debug/headers | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['headers'].get('X-Tsdproxy-Auth-Token',''))")
# Replay from localhost to gain admin access
curl -H "x-tsdproxy-auth-token: $TOKEN" \
-H "x-tsdproxy-id: attacker" \
http://127.0.0.1:8080/api/v1/proxies
# Returns full proxy list with admin access
Fix
Remove HeaderAuthToken from the outgoing backend request, and guard identity-header injection on user.ID != "":
// port.go: only inject headers for actually authenticated users
if identityHeaders {
if user, ok := model.WhoisFromContext(r.In.Context()); ok && user.ID != "" {
r.Out.Header.Set(consts.HeaderID, user.ID)
// HeaderAuthToken should NOT be forwarded to backends
}
}
Impact
An attacker with code execution in any backend proxied by tsdproxy (on the same host) gains full management API control: restart or pause all proxied services (DoS), enumerate all proxy configurations and backend network topology, and trigger webhook deliveries (SSRF via configured webhook URLs).
Credits
Reported by Vishal Shukla (@shukla304 / @therawdev).
Sponsorship
This audit is from an AI-assisted research agent at sechub.dev. Running it on OSS projects is free for maintainers.
- Record
GHSA-g936-7jqj-mwv8- CVSS 3.1
- 9.0 Critical
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:H - Weakness
- CWE-200, CWE-287
- Updated
- 2026-11-02
OSV 9.0
TSDProxy: Internal proxy auth token forwarded to backend services enables management API escalation
Description
A vulnerability was discovered in TSDProxy where it forwards its internal per-process authentication token to all proxied backend services. When identityHeaders is enabled (the default), tsdproxy injects x-tsdproxy-auth-token into every upstream HTTP request alongside user identity headers. This token is the same secret used by the management HTTP server to trust forwarded Tailscale identity claims. A backend that receives this token can replay it from localhost to the management port with an arbitrary x-tsdproxy-id value, bypassing Tailscale authentication entirely.
The token is forwarded unconditionally: ProviderUserMiddleware always calls WhoisNewContext regardless of whether the user is authenticated. In the ReverseProxy.Rewrite function, WhoisFromContext returns ok=true even for zero-value Whois{} (unauthenticated or Funnel requests). The HeaderAuthToken is set for every request when identityHeaders=true.
The attack requires the backend to reach 127.0.0.1:8080. This holds in: (1) non-Docker deployments where tsdproxy and a backend run on the same host, (2) Docker host-network-mode containers, (3) containers sharing tsdproxy's network namespace.
Affected files
internal/proxymanager/port.go:123-132internal/core/admin.go:160-182
// port.go: auth token forwarded regardless of user authentication state
if identityHeaders {
if user, ok := model.WhoisFromContext(r.In.Context()); ok {
// ok=true even for empty Whois{} stored by ProviderUserMiddleware
r.Out.Header.Set(consts.HeaderAuthToken, core.ProxyAuthToken()) // token sent to backend
}
}
// admin.go: management port trusts x-tsdproxy-id from localhost when token is valid
func ResolveWhois(r *http.Request) model.Whois {
if IsLocalhost(r.RemoteAddr) {
return model.Whois{
ID: r.Header.Get(consts.HeaderID), // attacker-controlled after stealing token
}
}
return model.Whois{}
}
Steps to reproduce
- Deploy tsdproxy on a host (non-Docker) with a backend at
http://localhost:3000. - Make a request through the Tailscale proxy. The backend receives
x-tsdproxy-auth-tokenin the request headers. - From the host, replay the token to the management API:
# Capture token from backend headers (e.g., via a header-reflection endpoint)
TOKEN=$(curl -s http://localhost:3000/debug/headers | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['headers'].get('X-Tsdproxy-Auth-Token',''))")
# Replay from localhost to gain admin access
curl -H "x-tsdproxy-auth-token: $TOKEN" \
-H "x-tsdproxy-id: attacker" \
http://127.0.0.1:8080/api/v1/proxies
# Returns full proxy list with admin access
Fix
Remove HeaderAuthToken from the outgoing backend request, and guard identity-header injection on user.ID != "":
// port.go: only inject headers for actually authenticated users
if identityHeaders {
if user, ok := model.WhoisFromContext(r.In.Context()); ok && user.ID != "" {
r.Out.Header.Set(consts.HeaderID, user.ID)
// HeaderAuthToken should NOT be forwarded to backends
}
}
Impact
An attacker with code execution in any backend proxied by tsdproxy (on the same host) gains full management API control: restart or pause all proxied services (DoS), enumerate all proxy configurations and backend network topology, and trigger webhook deliveries (SSRF via configured webhook URLs).
Credits
Reported by Vishal Shukla (@shukla304 / @therawdev).
Sponsorship
This audit is from an AI-assisted research agent at sechub.dev. Running it on OSS projects is free for maintainers.
- Record
GHSA-g936-7jqj-mwv8- CVSS 3.1
- 9.0 Critical
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:H - Weakness
- CWE-200, CWE-287
- Updated
- 2026-11-02
OSVGO-2026-5969No score
TSDProxy: Internal proxy auth token forwarded to backend services enables management API escalation in github.com/almeidapaulopt/tsdproxy
TSDProxy: Internal proxy auth token forwarded to backend services enables management API escalation in github.com/almeidapaulopt/tsdproxy
- Record
GO-2026-5969- Updated
- 2026-11-02
History
| Recorded | Changed | Sources |
|---|---|---|
| first indexed | GitHub Advisories, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.