Skip to content

fix(security): require token for Tailscale exposure, harden auth - #105

Merged
setkyar merged 1 commit into
mainfrom
fix/tailscale-tokenless-exposure
Aug 17, 2026
Merged

fix(security): require token for Tailscale exposure, harden auth#105
setkyar merged 1 commit into
mainfrom
fix/tailscale-tokenless-exposure

Conversation

@setkyar

@setkyar setkyar commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes findings from an external security review of pi-web.

HIGH — tokenless agent access over Tailscale. With no PI_WEB_TOKEN, startup ran tailscale serve (which proxies the tailnet to the loopback server) and then allowlisted the tailnet hostname for tokenless access. Any tailnet peer could reach POST /api/chat with no credential and drive the agent to run arbitrary shell — RCE. The existing "refuse non-loopback bind without a token" guard missed it because the Go process genuinely stays on 127.0.0.1; the Serve proxy is what reaches the tailnet. Fix: only configure Serve and allowlist its host when a token is set — otherwise stay loopback-only and print why.

Also addresses two lower-priority hardening notes from the same review:

  • Cookie Secure: the pi_token cookie is now Secure on HTTPS, detected via X-Forwarded-Proto (Tailscale terminates TLS; the server itself listens on loopback HTTP, so r.TLS is always nil).
  • Push SSRF: /api/push/subscribe now rejects endpoints that aren't absolute https:// URLs, so the push sender can't be pointed at http:///loopback/other schemes.

Changes

  • internal/app/app.go — gate Tailscale Serve + allowlist on PI_WEB_TOKEN
  • internal/auth/auth.goSecure cookie via isTLSRequest (+ tests)
  • internal/server/push.govalidPushEndpoint https check (+ tests)
  • docs/architecture/backend.md, user-docs/en/install.md — document token-conditional behavior

Testing

make check green (lint, format, tests, build, vet, install test).

Follow-up

Translated user-docs/<lang>/install.md need a regen pass via python3 scripts/build_userdocs.py (kept out to keep this diff scoped).

Close a HIGH-severity auth bypass from an external security review: with
no PI_WEB_TOKEN, startup published the loopback server via Tailscale Serve
and allowlisted the tailnet hostname for tokenless access, giving any
tailnet peer unauthenticated access to POST /api/chat (agent RCE). Only
configure Serve and allowlist its host when a token is set; otherwise stay
loopback-only and say so.

Also address two hardening notes from the same review:
- Set the pi_token cookie Secure over HTTPS (detected via X-Forwarded-Proto,
  since Tailscale terminates TLS and the server itself listens on loopback).
- Reject Web Push subscribe endpoints that are not absolute https:// URLs,
  preventing the push sender from being pointed at http/loopback/other schemes.

Docs (backend.md, en/install.md) updated to the token-conditional behavior.
Translated install.md locales still need a regen pass via build_userdocs.py.
@setkyar
setkyar merged commit b442466 into main Aug 17, 2026
6 checks passed
@setkyar
setkyar deleted the fix/tailscale-tokenless-exposure branch August 17, 2026 06:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant