GHSA-G936-7JQJ-MWV8
Vulnerability from github – Published: 2026-07-10 21:43 – Updated: 2026-07-10 21:43Description
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.
{
"affected": [
{
"package": {
"ecosystem": "Go",
"name": "github.com/almeidapaulopt/tsdproxy"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "1.4.4-0.20260603142855-434819b4421e"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [],
"database_specific": {
"cwe_ids": [
"CWE-200",
"CWE-287"
],
"github_reviewed": true,
"github_reviewed_at": "2026-07-10T21:43:13Z",
"nvd_published_at": null,
"severity": "CRITICAL"
},
"details": "## Description\n\nA 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.\n\nThe 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`.\n\nThe 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\u0027s network namespace.\n\n## Affected files\n\n- `internal/proxymanager/port.go:123-132`\n- `internal/core/admin.go:160-182`\n\n```go\n// port.go: auth token forwarded regardless of user authentication state\nif identityHeaders {\n if user, ok := model.WhoisFromContext(r.In.Context()); ok {\n // ok=true even for empty Whois{} stored by ProviderUserMiddleware\n r.Out.Header.Set(consts.HeaderAuthToken, core.ProxyAuthToken()) // token sent to backend\n }\n}\n\n// admin.go: management port trusts x-tsdproxy-id from localhost when token is valid\nfunc ResolveWhois(r *http.Request) model.Whois {\n if IsLocalhost(r.RemoteAddr) {\n return model.Whois{\n ID: r.Header.Get(consts.HeaderID), // attacker-controlled after stealing token\n }\n }\n return model.Whois{}\n}\n```\n\n## Steps to reproduce\n\n1. Deploy tsdproxy on a host (non-Docker) with a backend at `http://localhost:3000`.\n2. Make a request through the Tailscale proxy. The backend receives `x-tsdproxy-auth-token` in the request headers.\n3. From the host, replay the token to the management API:\n\n```bash\n# Capture token from backend headers (e.g., via a header-reflection endpoint)\nTOKEN=$(curl -s http://localhost:3000/debug/headers | python3 -c \"import sys,json; d=json.load(sys.stdin); print(d[\u0027headers\u0027].get(\u0027X-Tsdproxy-Auth-Token\u0027,\u0027\u0027))\")\n\n# Replay from localhost to gain admin access\ncurl -H \"x-tsdproxy-auth-token: $TOKEN\" \\\n -H \"x-tsdproxy-id: attacker\" \\\n http://127.0.0.1:8080/api/v1/proxies\n# Returns full proxy list with admin access\n```\n\n## Fix\n\nRemove `HeaderAuthToken` from the outgoing backend request, and guard identity-header injection on `user.ID != \"\"`:\n\n```go\n// port.go: only inject headers for actually authenticated users\nif identityHeaders {\n if user, ok := model.WhoisFromContext(r.In.Context()); ok \u0026\u0026 user.ID != \"\" {\n r.Out.Header.Set(consts.HeaderID, user.ID)\n // HeaderAuthToken should NOT be forwarded to backends\n }\n}\n```\n\n## Impact\n\nAn 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).\n\n## Credits\n\nReported by Vishal Shukla (@shukla304 / @therawdev).\n\n## Sponsorship\n\nThis audit is from an AI-assisted research agent at [sechub.dev](https://sechub.dev). Running it on OSS projects is free for maintainers.",
"id": "GHSA-g936-7jqj-mwv8",
"modified": "2026-07-10T21:43:13Z",
"published": "2026-07-10T21:43:13Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/almeidapaulopt/tsdproxy/security/advisories/GHSA-g936-7jqj-mwv8"
},
{
"type": "PACKAGE",
"url": "https://github.com/almeidapaulopt/tsdproxy"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:H",
"type": "CVSS_V3"
}
],
"summary": "TSDProxy: Internal proxy auth token forwarded to backend services enables management API escalation"
}
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.