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
- Default the state dir to the fixed per-user path above (mirror the logs location), not
./ts-state.
- 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
Summary
ts-bridge connectwrites its tsnet state (includingtailscaled.state, which holds thenode's private identity) to a CWD-relative
./ts-statedirectory. Running the bridge fromany directory drops a
ts-state/there. If that directory is a git working tree — especiallyone with auto-commit (e.g. an Obsidian vault with obsidian-git running
git add -Aon 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:
Observed
ts-state/directories created in three different CWDs on one machine just fromrunning
connectfrom 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-commitThe third was untracked and not covered by that repo's
.gitignore, so it was oneauto-commit timer tick away from pushing
tailscaled.stateto a remote. (The ts-bridge repoitself gitignores
ts-state— which is exactly the problem: the safety is offloaded onto everyconsuming repo instead of ts-bridge owning a safe default.)
Impact
ts-stateper CWD means a different node identity per launchdirectory → 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:
%LOCALAPPDATA%\ts-bridge\(already used for logs)$XDG_STATE_HOME/ts-bridgeor~/.local/state/ts-bridge~/Library/Application Support/ts-bridge(or XDG)with
state/andlogs/underneath.--state-diralready exists as an override; the defaultis the bug.
Suggested fix
./ts-state.is inside a git work tree.
Repro