Skip to content

Expose Tailcat node identity and harden Android channel buffering - #44

Merged
Sniperlyf3 merged 1 commit into
mainfrom
feature/tailcat-node-key-and-android-backpressure
Sep 16, 2026
Merged

Sniperlyf3 merged 1 commit into
mainfrom
feature/tailcat-node-key-and-android-backpressure

Conversation

@Sniperlyf3

Copy link
Copy Markdown
Owner

Summary

Expose the Tailcat connection's public node identity through the agent protocol and fix a false-positive channel backpressure failure observed in the packaged Android E2E probe.

Closes #43.

Tailcat public node identity

  • successful Tailcat connected control frames now include optional node_key: "nodekey:<hex>"
  • ordinary TCP SSH connections omit the field
  • the value is derived from session.tcKey.Public().String() only; private node-key material is never serialized
  • older .NET clients remain compatible because the new JSON field is optional/ignorable
  • new .NET clients expose MeowshellAgentConnection.TailcatNodeKey
  • TailcatSshSession.TailcatNodeKey forwards the same public identity
  • protocol regressions cover both the new field and legacy connected frames without it
  • the packaged Android probe requires a valid nodekey: value after a real Tailcat connection, exercising Go agent -> wire protocol -> .NET API on-device

This unlocks the first identity prerequisite for MeowSSH's future node-bound, self-hosted DERP admission flow. It deliberately does not make client telemetry authoritative for relay metering.

Android channel backpressure fix

Run 34995781341, job 104474119042 reached a real Tailcat SSH session but failed while actively reading output with:

channel data is not being consumed fast enough

The per-channel AgentChannelDataPump is correctly bounded to 32 queued frames so one abandoned channel cannot block the shared multiplexed connection. The downstream shell/download Pipes, however, used System.IO.Pipelines' much smaller default pause threshold. A legitimate burst of many small SSH frames could therefore block the pump around the default pipe threshold, fill the remaining 32 frame slots, and falsely classify an active consumer as stalled.

This PR keeps the 32-frame isolation queue unchanged and gives shell/download output pipes byte-oriented bounded burst headroom:

  • pause writer at 1 MiB
  • resume at 512 KiB
  • no synchronization-context capture

A focused regression sends a 128 KiB tiny-frame burst (above the old default pipe pause threshold) and proves it drains without triggering the backpressure callback. Existing tests for a genuinely blocked channel remain in place, so the cleanup/memory-safety behavior is preserved.

Validation

  • exact-string source patch guards all matched the intended existing code once
  • gofmt applied to touched Go files
  • focused go test ./cmd/meowshell: green
  • focused .NET test validation passed before the source commit was accepted
  • full repository CI is running on the real branch head; the critical gate is dotnet-android-e2e, which reproduces the originally failing packaged Android path

Security boundaries

  • no private Tailcat key bytes cross the protocol
  • no global/static node-key side channel; identity belongs to the connection/session
  • the multiplexed channel queue remains bounded
  • client-side relay information remains diagnostic only; future paid relay usage must be accounted by MeowSSH-owned relays

@Sniperlyf3
Sniperlyf3 force-pushed the feature/tailcat-node-key-and-android-backpressure branch from da4b750 to c86f7e0 Compare September 16, 2026 07:18
@Sniperlyf3
Sniperlyf3 merged commit 91eccb9 into main Sep 16, 2026
18 checks passed
@Sniperlyf3
Sniperlyf3 deleted the feature/tailcat-node-key-and-android-backpressure branch September 16, 2026 07:32
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.

Expose Tailcat node public key through agent protocol

1 participant