# TSDProxy: Internal proxy auth token forwarded to backend services enables management API escalation

TSDProxy forwards its internal management auth token to all proxied backends, letting a compromised backend replay it from localhost to bypass Tailscale auth and gain admin API access.

- Payout: Research
- Severity: critical
- Program: TSDProxy
- Found by: therawdev
- Disclosed: 2026-07-10
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-g936-7jqj-mwv8
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-g936-7jqj-mwv8
- Tags: area:cloud domain:infra impact:dos impact:internal-access impact:privesc kind:research platform:ghsa sev:critical tech:go technique:source-review vuln:access-control vuln:auth-bypass vuln:secrets-leak vuln:ssrf

**Target:** Go reverse proxy / management HTTP API
**Root cause:** ProviderUserMiddleware stores a zero-value Whois in context even for unauthenticated requests, and WhoisFromContext returns ok=true for it, so the internal ProxyAuthToken is injected into every upstream request. The management server trusts x-tsdproxy-id from localhost when that same token is presented.
**Steps:**
1. Deploy tsdproxy on a host with a backend at http://localhost:3000 and identityHeaders enabled (default).
2. Send a request through the Tailscale proxy; the backend receives `x-tsdproxy-auth-token` in its headers.
3. Capture the token from the backend, e.g. via a header-reflection endpoint: `curl -s http://localhost:3000/debug/headers`.
4. Replay from localhost: `curl -H "x-tsdproxy-auth-token: $TOKEN" -H "x-tsdproxy-id: attacker" http://127.0.0.1:8080/api/v1/proxies` to get admin access.
**How found:** Code review of the proxy rewrite path: noticed WhoisFromContext returns ok=true for zero-value Whois stored by ProviderUserMiddleware, so the auth token is set unconditionally; then traced admin.go ResolveWhois trusting x-tsdproxy-id from localhost when the token matches.
**Exploitation:** Attacker with code execution in any backend proxied by tsdproxy (same host, host-network container, or shared netns) reads the leaked x-tsdproxy-auth-token from incoming request headers, then replays it from 127.0.0.1 to the management port 8080 with an arbitrary x-tsdproxy-id, impersonating any Tailscale identity and obtaining full management API control.
**Escalation:** From backend code execution to full management API: enumerate proxy configs/topology, restart/pause services (DoS), and trigger webhook deliveries (SSRF).
**Insight:** When a proxy injects identity headers, check whether the injection is gated on actual authentication vs. mere context presence (ok=true for zero values). Also check whether the same secret used to trust forwarded identity is ever sent downstream to untrusted backends.

## Proof of concept (copied exactly from the original report)

**Replay stolen token to management API**

```
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
```

**Token forwarded to backend in port.go**

```
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
```

**Management port trusts localhost x-tsdproxy-id**

```
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
```

