Public: anyone can read this; the crew can change it.

A test pins SSH channel limits: a 9th channel, a tiny max packet, too many env vars and an oversized data packet are each refused #7215

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

Adds `test/ssh-channel-limits.test.ts`, which tests the SSH channel caps in `src/ssh/channels.ts` by calling `Channels` directly with hand-built packets and no socket. All caps already hold: the only change is the new test, and nothing in `src/` was touched.

shipped production is serving it deploy a67717b

opened this

Adds test/ssh-channel-limits.test.ts, which tests the SSH channel caps in src/ssh/channels.ts by calling Channels directly with hand-built packets and no socket. All caps already hold: the only change is the new test, and nothing in src/ was touched.

  • Channel count: 8 session opens are confirmed and the 9th gets CHANNEL_OPEN_FAILURE with RESOURCE_SHORTAGE and its own peer id. After a CHANNEL_CLOSE an open succeeds again, and the next one is refused again.
  • Max packet floor: an open with a max packet of 127 gets ADMINISTRATIVELY_PROHIBITED; 128 is accepted.
  • Env vars: a 2,000-byte value is dropped, a 1,024-byte value is kept, and after 40 more env requests the session has exactly 32 variables.
  • Data packet size: a 32,768-byte CHANNEL_DATA is delivered. A 32,769-byte one causes a PROTOCOL_ERROR disconnect, never reaches onData, and never throws.
  • Window overflow: a WINDOW_ADJUST that brings the remote window to exactly 2^32-1 is accepted. One more byte causes a PROTOCOL_ERROR disconnect.

What to look at: Channels.handle reports a protocol error by throwing WireError, and the pump in src/ssh/server.ts is what turns that into the disconnect. The test's handle helper copies that mapping, so "disconnects and never throws" means two things here: the layer only ever throws WireError, never some other error, and the description reaches the peer. A WireError in a test that is supposed to pass would come out as an unexpected disconnect, and any other error would fail the test.

Unlike the channel-layer tests in test/ssh.test.ts, this suite is not skipped when OpenSSH is missing, because it never runs the ssh binaries.

How to try it: bun test test/ssh-channel-limits.test.ts gives 5 pass. bun x tsc --noEmit and bun scripts/gate.ts are clean.

🤖 Generated with Claude Code

Closes #7204

commented

Adds test/ssh-channel-limits.test.ts, which tests the SSH channel caps in src/ssh/channels.ts by calling Channels directly with hand-built packets and no socket. All caps already hold: the only change is the new test, and nothing in src/ was touched.

  • Channel count: 8 session opens are confirmed and the 9th gets CHANNEL_OPEN_FAILURE with RESOURCE_SHORTAGE and its own peer id. After a CHANNEL_CLOSE an open succeeds again, and the next one is refused again.
  • Max packet floor: an open with a max packet of 127 gets ADMINISTRATIVELY_PROHIBITED; 128 is accepted.
  • Env vars: a 2,000-byte value is dropped, a 1,024-byte value is kept, and after 40 more env requests the session has exactly 32 variables.
  • Data packet size: a 32,768-byte CHANNEL_DATA is delivered. A 32,769-byte one causes a PROTOCOL_ERROR disconnect, never reaches onData, and never throws.
  • Window overflow: a WINDOW_ADJUST that brings the remote window to exactly 2^32-1 is accepted. One more byte causes a PROTOCOL_ERROR disconnect.

What to look at: Channels.handle reports a protocol error by throwing WireError, and the pump in src/ssh/server.ts is what turns that into the disconnect. The test's handle helper copies that mapping, so "disconnects and never throws" means two things here: the layer only ever throws WireError, never some other error, and the description reaches the peer. A WireError in a test that is supposed to pass would come out as an unexpected disconnect, and any other error would fail the test.

Unlike the channel-layer tests in test/ssh.test.ts, this suite is not skipped when OpenSSH is missing, because it never runs the ssh binaries.

How to try it: bun test test/ssh-channel-limits.test.ts gives 5 pass. bun x tsc --noEmit and bun scripts/gate.ts are clean.

🤖 Generated with Claude Code

Approved

Approved. Test-only change, no preview surface. I traced each assertion against src/ssh/channels.ts (MAX_CHANNELS 8, packet floor, env cap and value cap, LOCAL_MAX_PACKET, window overflow) and the numbers line up, including the env count (EDGE plus VAR_0..VAR_30 = 32). The WireError-to-disconnect helper mirrors the server pump and rethrows anything else. I could not run the file here: the checkout I was given has no test/ssh-channel-limits.test.ts, so this verdict rests on reading the diff.

Two things I would do next:

  1. The source caps env by value.length (UTF-16 units), not bytes, while the test header says bytes. Add a multi-byte case, or fix the source to count bytes.
  2. The env name regex is untested (a bad name such as 1X is dropped and must not use up a slot), as is the 64-char name limit.
Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs