close
Skip to content

Session pin is inert behind every documented tunnel (cloudflared / ngrok / tailscale serve) #1762

Description

@CrysisDeu

Summary

A dashboard session's IP pin is inert behind every tunnel docs/guides/remote-and-mobile.md recommends — cloudflared, ngrok, and tailscale serve alike. This issue tracks the repair. The reporting of the state has landed (PR #1761); the pin itself has not been fixed.

Why it happens

The pin is bound from request.remote at the one bind site in the dashboard auth middleware. Every tunnel above runs on the gateway host and connects to the loopback-bound gateway from 127.0.0.1, so the address the session pins to is the tunnel process — identical for every client behind it.

Consequences, all live on main:

  • A leaked session link is a bearer credential. Anyone who reaches the dashboard through the same tunnel satisfies the pin, for the life of the session — up to the 20-hour MAX_SESSION_TTL_SECS, and kirocrew token defaults straight to 20h.
  • The remote-access audit trail cannot name anyone. The SEL log_api_access(caller=...) sites in the auth middleware all record request.remote, so every proxied request is attributed to 127.0.0.1.

This is not Tailscale-specific and not new. It is a property of "bind loopback + put a local proxy in front", which is the only remote-access shape the product documents.

What is already done

What is owed

Replace the address pin with a verified peer identity wherever one can be obtained from the local proxy, keeping the address pin as the fallback:

  • session pin key generalises from ip:<remote> to a peer key — ts:<login>@<node> when a peer resolves, unchanged ip:<remote> otherwise;
  • resolution is daemon-verified, never header-trusted: loopback immediate peer and exactly one X-Forwarded-For and tailscale whois confirming the address is a tailnet member and the resolved login agreeing with Tailscale-User-Login. Any miss falls back to the existing token path;
  • a mandatory login allowlist — "any tailnet member" would be a silently-permissive default on a shared corporate tailnet;
  • audit records name the resolved login as caller when a peer resolved.

Tailscale is the first case because its local daemon can answer "who is this peer". Cloudflare Access (a locally verifiable signed JWT) is the natural second, and is the test of whether the seam generalises — but not before there is a second consumer.

Explicitly not proposed here

Widening is_direct_local_request(). A verified tailnet identity is not the local machine; config-write and secret-reveal endpoints stay closed to proxied requests.

Blocking questions

Both are open in the RFC and one needs a product call:

  1. Pin scope: node or login? Node is strictly stronger than today and costs a flow that does not work today either (one link, two devices — today's IP pin already refuses the second). Login survives a device's node identity changing. Note this choice only affects what happens after a session leaks; it is not the defence against a hostile tailnet member — the allowlist is.
  2. Windows. The tailscale CLI location and the daemon's local API on Windows are unverified. If resolution is not reliable there, the repair is POSIX-only and must degrade to the token path rather than break.

Risk note

This is the only part of the RFC that touches the auth path. It should not land without adversarial review, and it needs explicit tests for X-Forwarded-For injection, multi-value X-Forwarded-For, header/whois disagreement, daemon-absent fallback, and timeout fail-closed.

Until it lands, PR #1761's posture row and guide text are the mitigation: they tell the user the truth so they can size their TTL and exposure accordingly.

Metadata

Metadata

Assignees

Labels

area: dashboardDashboard UI and its backend handlersarea: gatewayGateway boot, auth/portal, caller authenticationclaimedAn automated fix is in progresssecurityHardening, trust boundary, or sandbox escape

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions