fix(hopper): bound bridge request frames like the Cutter bridge (#5262)

* fix(hopper): bound bridge request frames like the Cutter bridge

The Hopper bridge read each request with an unbounded readline, so a peer
that never wrote a newline kept Hopper allocating for as long as it kept
writing. The Cutter bridge already refuses a frame above MAX_FRAME_BYTES;
the Hopper bridge had no request ceiling at all.

Read the request line with a 16 MiB bound and refuse a line that fills the
limit without a terminator, using the existing invalid_request diagnostic
before closing the connection. Such a line cannot be answered or
resynchronized, because its remainder would be read as the next request.
The response direction is unchanged, so large decompilation results still
flow, and every dispatchable method carries small bounded parameters.

The stalled-line case keeps its connection open instead of half-closing
it, so the test pins the exact ceiling: a bound one byte larger waits for
a byte the peer never sends and fails instead of passing quietly.

Validation: npm run check:fast (typecheck, lint) passed uncached, and the
new boundary test passed against the real bridge over socket pairs at the
frame limit and above it. Removing only the read bound makes the same test
fail, and widening the bound to one byte past the limit fails it too. The
Hopper boundary and bridge-packaging suites report the same 17 failing
files and 126 failing tests before and after this change on this Windows
host; those failures are POSIX-only probes (Unix socket directories,
os.statvfs) unrelated to this diff. Native macOS Hopper and real-client
traffic were not run.

* fix(bridges): exclude line delimiter from payload limit

* test(hopper): cover partial request at EOF

---------

Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
This commit is contained in:
Dery Ferdikaandmorluto authored and GitHub committed 2026-10-23 02:25:24 +08:00
1 parent b2048d46d1
commit 26573c3aa4
6 files changed
+266 -3

No files matched your search

+5 -1
View File
@@ -71,7 +71,11 @@ Cutter commands use the local REA Python bridge and authenticated loopback messa
The client and plugin must support the current v2 handshake; older peers are
rejected before dispatch because they cannot preserve cross-caller recovery proof.
The client checks the final encoded request against the bridge's 16 MiB frame
limit before dispatch; an oversized command does not retain a cleanup owner.
limit before dispatch; the 16 MiB budget applies to request payload bytes and
excludes the terminating LF. The bridge accepts that full payload size and
rejects larger or incomplete frames while bounding the read; an oversized
command does not retain a cleanup owner. Hopper uses the same request payload
budget.
Each bridge request waits 25000 ms. Discovery admits at most 256 descriptor
candidates and reads and probes at most eight concurrently. A larger directory
reports `bridge_directory_capacity_exceeded`. This release has no runtime