Skip to content

BUG: connect stores tsnet state in a CWD-relative ./ts-state — leaks node identity into the working dir (and any auto-committed repo) #207

Description

@mlorentedev

Summary

ts-bridge connect writes its tsnet state (including tailscaled.state, which holds the
node's private identity) to a CWD-relative ./ts-state directory. Running the bridge from
any directory drops a ts-state/ there. If that directory is a git working tree — especially
one with auto-commit (e.g. an Obsidian vault with obsidian-git running git add -A on a timer)
— the node identity gets staged, committed, and pushed. That is a secret leak.

Logs already land in a fixed per-user location (%LOCALAPPDATA%\ts-bridge\logs\ on Windows);
state does not — the two are inconsistent.

Evidence

From a verbose start:

tsnet running state path ts-state\tailscaled.state
tsnet starting with hostname "ts-bridge", varRoot "./ts-state"

Observed ts-state/ directories created in three different CWDs on one machine just from
running connect from different shells:

  • ~/Apps/ts-bridge/ts-state (intended, next to the binary)
  • ~/Projects/Workspace/ts-bridge/ts-state
  • ~/Projects/Workspace/knowledge/ts-state ← an Obsidian vault with obsidian-git auto-commit

The third was untracked and not covered by that repo's .gitignore, so it was one
auto-commit timer tick away from pushing tailscaled.state to a remote. (The ts-bridge repo
itself gitignores ts-state — which is exactly the problem: the safety is offloaded onto every
consuming repo instead of ts-bridge owning a safe default.)

Impact

  • Leak of the tsnet node private identity into arbitrary repos / git history.
  • State fragmentation: a fresh ts-state per CWD means a different node identity per launch
    directory → inconsistent device identity across sessions/machines and orphaned nodes in the
    control plane.

Expected

State and logs should land in one fixed, per-user, cross-OS location, independent of CWD and
stable across sessions and machines. Suggested base directory:

  • Windows: %LOCALAPPDATA%\ts-bridge\ (already used for logs)
  • Linux: $XDG_STATE_HOME/ts-bridge or ~/.local/state/ts-bridge
  • macOS: ~/Library/Application Support/ts-bridge (or XDG)

with state/ and logs/ underneath. --state-dir already exists as an override; the default
is the bug.

Suggested fix

  1. Default the state dir to the fixed per-user path above (mirror the logs location), not
    ./ts-state.
  2. Defense-in-depth: refuse (or loudly warn) when the resolved state dir is CWD-relative and CWD
    is inside a git work tree.

Repro

cd /any/git/repo
ts-bridge connect --target <host>:<port>   # creates ./ts-state/tailscaled.state here

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workinggoPull requests that update go code

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions