Skip to content

Publish a fresh peer-link pipe name per bind on Windows - #1252

Merged
nedtwigg merged 5 commits into
mainfrom
peer-pipe-random-name
Oct 20, 2026
Merged

nedtwigg merged 5 commits into
mainfrom
peer-pipe-random-name

Conversation

@nedtwigg

@nedtwigg nedtwigg commented Oct 20, 2026 •

Copy link
Copy Markdown
Member

Closes the Windows peer-link pipe squat recorded in #1241.

On Windows every VS Code window used to bind one pipe name derived from the extension's storage path. Windows lists every pipe to every account, so another account could create that name first after a restart, and every window's bind then failed: remote control and peer surfaces stayed down (the mutual token handshake still served it nothing).

Design (Windows only)

  • A fresh name per bind. The broker binds \\.\pipe\dormouse-peer-<32 random hex> (128 CSPRNG bits, freshEndpointName in the new vscode-ext/src/peer-endpoint.ts), so nobody can create it in advance.
  • Published only in the user's storage. The broker writes the name and its own pid with an exclusive wx create of burrow.peer-endpoint, mode 0600, beside burrow.peer-token in the extension's global storage, under the same protection as the token. Other windows find the broker only through that file.
  • Two windows starting together. Both bind, one wins the exclusive create; the loser closes its pipe and dials the winner. An empty file is waited out as not yet written, like the token.
  • A published bind is provisional. It is believed only if the file still names it after RECLAIM_VERIFY_MS, since a window retiring a stale file can remove a fresh one in between; a command landing meanwhile is held, as for unix's reclaimed bind.
  • A stale file from a dead broker. A published name that twice fails to yield a verified welcome, a jittered re-read apart, is retired, only while the file still names it, and the window publishes a fresh name. A refusal or a holder that cannot prove the token retires it: a dead broker's name was listed while it lived, so another account could bind it as the broker goes and would otherwise hold every window off it.
  • A stalled broker is waited for, never replaced. Silence is the one failure a live broker also gives (a paused debugger, a busy extension host), and replacing it would leave two Burrows under one enrollment once it caught up, since a broker never stands down. So silence retires the name only once the pid the file records is gone.
  • A broker restart while peers are connected. Each peer's socket closes, it re-reads the file, and the peers elect one fresh broker the rest join. A closing broker retracts its own name.

The handshake is unchanged. macOS and Linux are unchanged: their derived socket already sits in a per-user 0700 directory no other account can write, and moving them to the published design would only add churn.

Specs

  • docs/specs/vscode.md → "Burrow: a service in the extension host": a new "Windows: a published endpoint" rule list, with the evidence in vscode.rationale.md.
  • docs/specs/security-local.md: a new audited section "The VS Code peer link" with FAIL IF S274, claimed by local-terminal in .github/audit/local.md.
  • docs/specs/security.md → "What is not defended": the Record the Windows peer-link pipe squat as not defended #1241 entry is rewritten to the one residual. Another account can hold a dead broker's name silently while one of its processes reuses that broker's pid, which keeps windows waiting.
  • Word budgets ratcheted for security-local.md (6100) and vscode.md (4500).

Tests

vscode-ext/test/peer-endpoint.test.ts runs the Windows arbitration over unix sockets (a per-window vi.doMock of peer-endpoint.ts keeps the real name's random part; on Windows it uses real pipes). It covers:

  • name freshness per bind, and retraction on close
  • the file's 0600 mode
  • stale-file recovery, including a dead name rebound by something that cannot prove the token, or that never answers
  • waiting for a live broker that stopped answering, then joining it once it does
  • three windows starting together
  • connected peers finding a fresh broker after theirs crashes and leaves its file behind

Each test went red with its part of the fix reverted. In peer-link.test.ts, the derived path is now unix-only. The handshake tests dial the published name on Windows. The two squatter tests on the derived name and the delayed-answer test, which fakes a broker there, are unix-only. The Windows-only derived-pipe reclaim test is replaced by the new suite.

🤖 Generated with Claude Code

On Windows the VS Code peer link no longer binds a pipe name derived from
the storage path, which another account could create first. Every bind
takes a fresh random name, published by an exclusive create of
burrow.peer-endpoint in the extension's storage; windows find the broker
only through that file. A published bind is believed only once the file
still names it, and a name that twice fails to yield a verified broker is
retired, so a dead broker's name taken by someone else holds nobody off.
The broker also listens on the old derived name so windows on an older
build join it rather than electing a second Burrow.

Unix keeps bind-as-lease on its derived path in the per-user directory.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

Deploying mouseterm with  Cloudflare Pages  Cloudflare Pages

Latest commit: cea1a5b
Status: ✅  Deploy successful!
Preview URL: https://a7267943.mouseterm.pages.dev
Branch Preview URL: https://peer-pipe-random-name.mouseterm.pages.dev

View logs

@dormouse-bot dormouse-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Retiring a published name on any failure can replace a broker that is alive but stalled, and the stalled broker never stands down afterwards. That leaves two Burrows under one burrowId until a window reloads, not "until the hung one recovers" (inline). Whether to accept this or narrow the retirement rule is a design call.

Comment thread docs/specs/vscode.rationale.md Outdated
A broker stalled behind a busy extension host answers a dial with
silence, exactly like a squatter holding a dead broker's name. Replacing
it left two Burrows under one enrollment once it caught up, since a
broker never stands down. The endpoint file now records the broker's
pid, and silence retires a published name only once that process is
gone; a refusal or a holder that cannot prove the token still retires it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@dormouse-bot dormouse-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pid wait checks only the second dial, but vscode.md says "unless both dials met only silence", and S274 fails if a window keeps dialing a name "whose holder ... failed to prove the token". With the code as written, a first dial that gets a bad proof followed by a silent second dial, while the recorded pid is alive, keeps the name, which is the case the audited rule names. In practice a live pid means the broker still holds its name, so this is hard to reach, but the auditor reads the code against the FAIL IF. The suggestions below make the code match the spec. The alternative is to reword both rules to "the second dial".

Comment thread vscode-ext/src/peer-link.ts Outdated
Comment thread vscode-ext/src/peer-link.ts Outdated
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There are no existing installations to stay compatible with, so a
Windows broker no longer listens on the old derived pipe name and no
window dials it. Tests and spec text that existed only for that path go
with it, and the derived name is now unix-only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@nedtwigg

Copy link
Copy Markdown
Member Author

Removed the compatibility path for older builds in c9baad3, since there are no existing users. A Windows broker no longer listens on the old derived pipe name and no window dials it. The tests and spec text that existed only for it are gone. The rest of the design is unchanged.

Comment thread vscode-ext/test/peer-endpoint.test.ts Outdated
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@nedtwigg
nedtwigg merged commit 4d64f88 into main Oct 20, 2026
15 checks passed
@nedtwigg
nedtwigg deleted the peer-pipe-random-name branch October 20, 2026 21:43

This branch is waiting to be deployed

1 waiting deployment
hosted-preview — cea1a5b1 Waiting Oct 20, 2026 by nedtwigg via cleanup #1700
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.

2 participants