feat(local): Windows named-pipe transport and ConPTY #4

Merged
dozken merged 3 commits from feat/windows-named-pipes into main 2026-09-24 18:42:05 +02:00
Owner

Summary

Nothing Windows-only here has run on Windows. This is RKD #1085: rkd-terminal and rkd-local-link on Windows. It compiles and passes clippy for x86_64-pc-windows-msvc, and its platform-neutral logic is unit-tested on the host. It stays WIP until someone runs local/WINDOWS-CHECKLIST.md on Windows 10 or 11.

  • Transport: \\.\pipe\rkd-plugins-<user SID>-<id> (ADR-0003 decision 1).
    • The first instance is created with FILE_FLAG_FIRST_PIPE_INSTANCE, an owner-only DACL (D:P(A;;GA;;;<SID>)) and reject_remote_clients.
    • The next instance is created before a connected one is handed off.
    • The server checks the client's token user before reading anything; clients check the server's the same way (GetNamedPipeServerProcessId).
    • connect_pipe opens with identification-only QoS.
    • The connection and handshake code is shared with unix through serve_connection<S: AsyncRead + AsyncWrite>. Unix behaviour is unchanged.
  • PTY: ConPTY through portable-pty. Close is TerminateProcess with no SIGHUP grace. Program lookup follows Windows rules: PATHEXT, never the current folder, drive and UNC paths, and \\?\ stripped for display.
  • Storage: config and secrets live under %LOCALAPPDATA% rather than roaming %APPDATA%, because a pairing belongs to one machine. with_lock takes a real LockFileEx.
  • CLI: serve, pair, status and unpair work. install/uninstall say "not on Windows yet" and exit non-zero; you run serve at login yourself.
  • CI: new windows-check job (Windows clippy on the Linux runner). No Windows release artifact yet.
  • Docs: a Windows (experimental) section in local/README.md, and local/WINDOWS-CHECKLIST.md: build, tests, the pipe's SID and DACL, a second serve refused, another user via runas (opening our pipe and squatting the name), pairing, tabs, and closing a tab.

Tests

  • Host: fmt, clippy -D warnings, cargo test --workspace; existing unix tests pass unchanged.
  • Windows: cargo clippy --target x86_64-pc-windows-msvc --workspace --all-targets -D warnings (type-check only; no MSVC linker or SDK here).
  • 22 hand-applied mutations on the host-testable rules (pipe name, SID, SDDL, PATHEXT, \\?\), all caught.
  • Unverified, listed in the checklist:
    • whether it links at all;
    • the pipe DACL and the first-instance refusal;
    • both peer checks;
    • pair_begin's reply when the pipe closes right after writing it;
    • ConPTY output, resize, exit codes and the ~500 ms exit delay;
    • that closing a tab ends what the shell started;
    • the Windows git run;
    • Ctrl+C shutdown.

Pairs with the RKD app PR for #1085, where the Tauri client connects over the pipe and Electron on Windows stays unsupported (#1101).

https://claude.ai/code/session_01P4hUduqyzHTYckP7dX5fXV

Merged without a Windows run: the maintainer waived the Windows checklist on 2026-09-24. local/WINDOWS-CHECKLIST.md stays as the first thing to run on a Windows machine, and the README keeps calling Windows experimental.

## Summary **Nothing Windows-only here has run on Windows.** This is RKD #1085: `rkd-terminal` and `rkd-local-link` on Windows. It compiles and passes clippy for `x86_64-pc-windows-msvc`, and its platform-neutral logic is unit-tested on the host. It stays WIP until someone runs `local/WINDOWS-CHECKLIST.md` on Windows 10 or 11. - **Transport:** `\\.\pipe\rkd-plugins-<user SID>-<id>` (ADR-0003 decision 1). - The first instance is created with `FILE_FLAG_FIRST_PIPE_INSTANCE`, an owner-only DACL (`D:P(A;;GA;;;<SID>)`) and `reject_remote_clients`. - The next instance is created before a connected one is handed off. - The server checks the client's token user before reading anything; clients check the server's the same way (`GetNamedPipeServerProcessId`). - `connect_pipe` opens with identification-only QoS. - The connection and handshake code is shared with unix through `serve_connection<S: AsyncRead + AsyncWrite>`. Unix behaviour is unchanged. - **PTY:** ConPTY through portable-pty. Close is `TerminateProcess` with no SIGHUP grace. Program lookup follows Windows rules: PATHEXT, never the current folder, drive and UNC paths, and `\\?\` stripped for display. - **Storage:** config and secrets live under `%LOCALAPPDATA%` rather than roaming `%APPDATA%`, because a pairing belongs to one machine. `with_lock` takes a real `LockFileEx`. - **CLI:** `serve`, `pair`, `status` and `unpair` work. `install`/`uninstall` say "not on Windows yet" and exit non-zero; you run `serve` at login yourself. - **CI:** new `windows-check` job (Windows clippy on the Linux runner). No Windows release artifact yet. - **Docs:** a Windows (experimental) section in `local/README.md`, and `local/WINDOWS-CHECKLIST.md`: build, tests, the pipe's SID and DACL, a second serve refused, another user via `runas` (opening our pipe and squatting the name), pairing, tabs, and closing a tab. ## Tests - Host: fmt, clippy `-D warnings`, `cargo test --workspace`; existing unix tests pass unchanged. - Windows: `cargo clippy --target x86_64-pc-windows-msvc --workspace --all-targets -D warnings` (type-check only; no MSVC linker or SDK here). - 22 hand-applied mutations on the host-testable rules (pipe name, SID, SDDL, PATHEXT, `\\?\`), all caught. - **Unverified, listed in the checklist:** - whether it links at all; - the pipe DACL and the first-instance refusal; - both peer checks; - `pair_begin`'s reply when the pipe closes right after writing it; - ConPTY output, resize, exit codes and the ~500 ms exit delay; - that closing a tab ends what the shell started; - the Windows git run; - Ctrl+C shutdown. Pairs with the RKD app PR for #1085, where the Tauri client connects over the pipe and Electron on Windows stays unsupported (#1101). https://claude.ai/code/session_01P4hUduqyzHTYckP7dX5fXV **Merged without a Windows run:** the maintainer waived the Windows checklist on 2026-09-24. `local/WINDOWS-CHECKLIST.md` stays as the first thing to run on a Windows machine, and the README keeps calling Windows experimental.
ADR-0003 decision 1 on Windows: `paths::socket_path` returns
`\\.\pipe\rkd-plugins-<user SID>-<id>` for the SID in the process token.
`pipe_name`, `is_sid_string` and `owner_only_sddl` (`D:P(A;;GA;;;<SID>)`)
are plain string rules, unit-tested on any host, and refuse anything that
could add to a pipe name or an ACE.

New `win` module (windows-sys 0.61, the version tokio already pulls in):
current_user_sid, process_is_current_user (EqualSid on token users),
pipe_server_is_current_user / pipe_client_is_current_user via
GetNamedPipe{Server,Client}ProcessId, OwnerOnly security attributes for
CreateNamedPipeW, and connect_pipe, a client that opens with
identification-only QoS and checks the server's user before returning.
Each call carries the Win32 contract it relies on.

Store: config_home is %LOCALAPPDATA% (not roaming: a pairing is per
machine) and the profile ACL is the boundary for secret files there;
with_lock uses std's File::lock (LockFileEx) on Windows instead of being
a no-op.

Compile-checked for x86_64-pc-windows-msvc only; not run on Windows.

Claude-Session: https://claude.ai/code/session_01P4hUduqyzHTYckP7dX5fXV
rkd-terminal now builds for Windows instead of refusing to start.

- server: the connection code past the peer check is serve_connection,
  generic over AsyncRead + AsyncWrite (tokio::io::split), shared by unix
  and Windows; unix accept, rebind and peer_cred are unchanged.
  server/pipe.rs serves the pipe with tokio's ServerOptions: first
  instance with first_pipe_instance(true), the owner-only DACL on every
  instance, reject_remote_clients, the next instance created before a
  connected one is handed off, and the client's token user checked
  before anything is read. A taken name says whether it is our own
  running plugin or another user's pipe.
- program lookup: winpath::find_program takes (name, cwd, PATH, PATHEXT,
  is_file) as strings so it is tested on any host: PATHEXT extensions,
  both separators, drive and UNC paths absolute, C:x and \x refused, no
  current-folder or relative PATH entry lookup. strip_verbatim drops the
  \\?\ canonicalize adds, where the plain path means the same.
- pty: ConPTY via portable-pty; close is TerminateProcess plus
  ClosePseudoConsole, no SIGHUP grace (documented).
- dirs: run_bounded on Windows reads git's stdout on a thread,
  CREATE_NO_WINDOW, kill on timeout.
- cli: serve (Ctrl+C / Ctrl+Break / console close), pair, status and
  unpair work over the pipe; install and uninstall exit non-zero with
  "not available on Windows yet; run `rkd-terminal serve` at login".
- tests/pipe.rs (Windows only): second serve refused, pairing, a ConPTY
  run, closing a tab. Compiled in CI, not run.

Claude-Session: https://claude.ai/code/session_01P4hUduqyzHTYckP7dX5fXV
ci, docs: windows-check job, Windows README section and checklist (#1085)
All checks were successful
CI / rust (pull_request) Successful in 34s
CI / windows-check (pull_request) Successful in 24s
CI / install-script (pull_request) Successful in 19s
c2017b233d
CI gains windows-check: rustup target add x86_64-pc-windows-msvc, then
clippy --workspace --all-targets --locked -D warnings for that target.
Clippy only type-checks, so no MSVC linker or SDK (or cargo-xwin) is
needed. No Windows release artifact yet.

README: Windows is experimental and unverified; install with cargo
install --git, run `rkd-terminal serve`, start it at login from the
Startup folder; what differs and what has not been verified.
local/WINDOWS-CHECKLIST.md walks a Windows 10/11 user through build,
tests, the pipe's SID and DACL, a second serve, another user (runas,
and a squatted name), pair/status, tabs and closing them.

Claude-Session: https://claude.ai/code/session_01P4hUduqyzHTYckP7dX5fXV
dozken changed title from WIP: feat(local): Windows named-pipe transport and ConPTY (untested on Windows) to feat(local): Windows named-pipe transport and ConPTY 2026-09-24 18:42:04 +02:00
dozken merged commit e90a24b4ee into main 2026-09-24 18:42:05 +02:00
dozken deleted branch feat/windows-named-pipes 2026-09-24 18:42:05 +02:00
dozken referenced this pull request from a commit 2026-09-24 19:18:47 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
rkd/plugins!4
No description provided.