diff --git a/apps/console/test/computer-directory.test.ts b/apps/console/test/computer-directory.test.ts index 21848d9..4d04f11 100644 --- a/apps/console/test/computer-directory.test.ts +++ b/apps/console/test/computer-directory.test.ts @@ -411,8 +411,10 @@ test("untrusted payloads are bounded and cannot substitute signatures, audiences headers: { "Content-Type": "text/plain" }, body: "{}", }); - expect(wrongType.status).toBe(415); - await wrongType.body?.cancel(); + expect({ status: wrongType.status, body: await wrongType.text() }).toEqual({ + status: 415, + body: expect.stringContaining('"code":"json_required"'), + }); expect((await request(f, "PUT", "/records/" + host.id, good)).status).toBe(200); const read = await lookup(f, host, phone); expect(read.status).toBe(200); diff --git a/daemon/internal/surface/mac.go b/daemon/internal/surface/mac.go index 4f83ca6..5df9703 100644 --- a/daemon/internal/surface/mac.go +++ b/daemon/internal/surface/mac.go @@ -189,11 +189,26 @@ func macConnectionError(ctx context.Context, err error) error { return ctx.Err() } var network net.Error - if errors.As(err, &network) && network.Timeout() { + if errors.Is(err, context.DeadlineExceeded) || (errors.As(err, &network) && network.Timeout()) { return problem("timeout", "The Mac's screen-sharing service did not respond. Try again.") } - // Never propagate an authentication library's raw response into logs or chats. - return problem("desktop_authentication", "Mac screen sharing could not authenticate. Run mindwire desktop enable on your Mac to check access and update its login.") + var specific *Error + if errors.As(err, &specific) { + return specific + } + var handshake *rfbHandshakeFailure + if errors.As(err, &handshake) { + err = handshake.cause + } + // go-vnc has no typed rejection error. Match its fixed prefix only; never + // propagate the server-supplied reason or turn a broken socket into bad login. + if strings.HasPrefix(err.Error(), "SecurityResult handshake failed:") { + return problem("desktop_authentication", "The Mac did not accept this login. Run mindwire desktop enable on your Mac to retry its password and check Screen Sharing access.") + } + if strings.HasPrefix(err.Error(), "Security handshake failed; no suitable auth schemes found;") { + return problem("desktop_auth_protocol", "Allow your Mac account in Screen Sharing settings, then run mindwire desktop enable again.") + } + return problem("unavailable", "The Mac's screen-sharing connection ended. Check Screen Sharing and try again.") } func (p *MacDesktop) Capture(ctx context.Context) (image.Image, Geometry, error) { diff --git a/daemon/internal/surface/mac_native_test.go b/daemon/internal/surface/mac_native_test.go index 6f43d27..bdad0cd 100644 --- a/daemon/internal/surface/mac_native_test.go +++ b/daemon/internal/surface/mac_native_test.go @@ -23,10 +23,17 @@ func TestNativeMacDesktopFixture(t *testing.T) { t.Skip("opt in with the iOS Mac desktop integration test") } s, store, frames, _ := macTestService(t) + live := os.Getenv("MINDWIRE_MAC_PERFORMANCE_STATE_DIR") != "" + if live { + // The read-only performance test uses existing, explicitly enabled access. + // Keep the real login only in memory, never in this fixture's state files. + s.configureProvider(performanceMac(t)) + } frames.resize = true // A six-MiB frame crosses the native forwarder's write high-water mark. // Tiny RFB fixtures miss stalls when NIOSSH reads resume after backpressure. frames.largeFrame = true + frames.tiledFrame = true s.SetApproval(func(context.Context, Actor, string) error { return nil }) directory := t.TempDir() const token = "synthetic-mac-desktop-fixture" @@ -107,7 +114,7 @@ func TestNativeMacDesktopFixture(t *testing.T) { } invitations = append(invitations, inv) } - data, _ := json.Marshal(map[string]any{"invitations": invitations}) + data, _ := json.Marshal(map[string]any{"invitations": invitations, "live": live}) if err = os.WriteFile(file, data, 0600); err != nil { t.Fatal(err) } diff --git a/daemon/internal/surface/mac_performance_test.go b/daemon/internal/surface/mac_performance_test.go new file mode 100644 index 0000000..472a115 --- /dev/null +++ b/daemon/internal/surface/mac_performance_test.go @@ -0,0 +1,140 @@ +package surface + +import ( + "bytes" + "context" + "encoding/binary" + "encoding/json" + "io" + "os" + "path/filepath" + "sort" + "strconv" + "testing" + "time" + + "github.com/oblien/mindwire/daemon/internal/session" +) + +// Opt-in, read-only measurement against the owner's already-enabled Mac. +// No input, screenshots, settings changes or credentials are emitted or saved. +func TestMacDesktopReadOnlyPerformance(t *testing.T) { + p := performanceMac(t) + defer p.Close() + ctx, cancel := context.WithTimeout(t.Context(), 45*time.Second) + defer cancel() + measureMacDesktop(t, ctx, p) +} + +func performanceMac(t *testing.T) *MacDesktop { + t.Helper() + directory := os.Getenv("MINDWIRE_MAC_PERFORMANCE_STATE_DIR") + if directory == "" { + t.Skip("requires an explicitly selected, enabled Mac desktop") + } + store, err := session.Open(filepath.Join(directory, "agent-state.json")) + if err != nil { + t.Fatal("could not read local desktop setup") + } + var saved savedLocalDesktop + if json.Unmarshal([]byte(store.Get(localDesktopKey)), &saved) != nil || !saved.Enabled { + t.Fatal("enable desktop access locally before measuring") + } + p, err := newMacDesktop(saved.LocalDesktopSettings) + if err != nil { + t.Fatal(err) + } + return p +} + +func measureMacDesktop(t *testing.T, ctx context.Context, p *MacDesktop) { + geometry, err := p.Connect(ctx) + if err != nil { + t.Fatal(err) + } + var checks []time.Duration + for range 10 { + start := time.Now() + if _, err := p.Connect(ctx); err != nil { + t.Fatal(err) + } + checks = append(checks, time.Since(start)) + } + sort.Slice(checks, func(i, j int) bool { return checks[i] < checks[j] }) + t.Logf("display=%dx%d geometry-check median=%s max=%s", geometry.Width, geometry.Height, checks[len(checks)/2], checks[len(checks)-1]) + + // Match the compressed encoding used by Apple Screen Sharing without + // retaining/decoding pixels. This isolates server/transport cost from UI work. + conn, geometry, err := p.openViewer(ctx) + if err != nil { + t.Fatal(err) + } + defer conn.Close() + _ = conn.SetDeadline(time.Now().Add(25 * time.Second)) + var encodings bytes.Buffer + encodings.Write([]byte{2, 0, 0, 4}) + preferred := int32(6) + if value, err := strconv.Atoi(os.Getenv("MINDWIRE_MAC_PERFORMANCE_ENCODING")); err == nil && value == 16 { + preferred = 16 + } + for _, encoding := range []int32{preferred, 0, -224, -223} { + _ = binary.Write(&encodings, binary.BigEndian, encoding) + } + if _, err := conn.Write(encodings.Bytes()); err != nil { + t.Fatal(err) + } + for frame := range 3 { + start := time.Now() + request := []byte{3, 0, 0, 0, 0, 0, 0, 0, 0, 0} + binary.BigEndian.PutUint16(request[6:8], uint16(geometry.Width)) + binary.BigEndian.PutUint16(request[8:10], uint16(geometry.Height)) + if _, err := conn.Write(request); err != nil { + t.Fatal(err) + } + var header [4]byte + if _, err := io.ReadFull(conn, header[:]); err != nil || header[0] != 0 { + t.Fatal("expected a framebuffer update", err) + } + count := int(binary.BigEndian.Uint16(header[2:])) + wireBytes, rectangles := int64(4), 0 + used := map[int32]int{} + for range count { + var rect [12]byte + if _, err := io.ReadFull(conn, rect[:]); err != nil { + t.Fatal(err) + } + wireBytes += 12 + encoding := int32(binary.BigEndian.Uint32(rect[8:])) + if encoding == -224 { + break + } + if encoding == -223 { + continue + } + var size int64 + switch encoding { + case 6, 16: + var length [4]byte + if _, err := io.ReadFull(conn, length[:]); err != nil { + t.Fatal(err) + } + wireBytes += 4 + size = int64(binary.BigEndian.Uint32(length[:])) + case 0: + size = int64(binary.BigEndian.Uint16(rect[4:])) * int64(binary.BigEndian.Uint16(rect[6:])) * 4 + default: + t.Fatalf("unexpected encoding %d", encoding) + } + if size > 64<<20 { + t.Fatal("frame exceeds measurement limit") + } + if _, err := io.CopyN(io.Discard, conn, size); err != nil { + t.Fatal(err) + } + wireBytes += size + rectangles++ + used[encoding]++ + } + t.Logf("frame=%d receive=%s rectangles=%d wireBytes=%d encodings=%v", frame, time.Since(start), rectangles, wireBytes, used) + } +} diff --git a/daemon/internal/surface/mac_test.go b/daemon/internal/surface/mac_test.go index 0080aa0..087d165 100644 --- a/daemon/internal/surface/mac_test.go +++ b/daemon/internal/surface/mac_test.go @@ -282,8 +282,8 @@ func TestMacDesktopOptInAuthenticationAndIdempotentSetup(t *testing.T) { if _, err := s.Open(t.Context(), Actor{Kind: "user"}, OpenRequest{RequestID: "before", Mode: "view"}); err == nil { t.Fatal("desktop opened before opt-in") } - if _, err := s.ConfigureLocalDesktop(t.Context(), LocalDesktopSettings{Enabled: true, Username: "owner", Password: "wrong"}, store); err == nil { - t.Fatal("bad Mac login was accepted") + if _, err := s.ConfigureLocalDesktop(t.Context(), LocalDesktopSettings{Enabled: true, Username: "owner", Password: "wrong"}, store); asError(err).Code != "desktop_authentication" { + t.Fatal("bad Mac login did not return a retryable authentication error", err) } if store.Get(localDesktopKey) != "" { t.Fatal("failed authentication persisted settings") @@ -295,6 +295,13 @@ func TestMacDesktopOptInAuthenticationAndIdempotentSetup(t *testing.T) { if accepted.Load() != count || s.Snapshot().Controller == nil || s.Snapshot().Controller.SessionID != viewer.ID { t.Fatal("identical setup restarted authentication or lost control") } + saved := store.Get(localDesktopKey) + if _, err := s.ConfigureLocalDesktop(t.Context(), LocalDesktopSettings{Enabled: true, Username: "owner", Password: "wrong again"}, store); asError(err).Code != "desktop_authentication" { + t.Fatal("failed reconfiguration did not preserve the login error", err) + } + if saved != store.Get(localDesktopKey) || s.Snapshot().Controller == nil || s.Snapshot().Controller.SessionID != viewer.ID { + t.Fatal("rejected password changed working credentials or dropped control") + } capture, err := s.Capture(t.Context(), viewer.ID, Actor{Kind: "user"}) if err != nil || capture.Geometry.Width != 2 { t.Fatal("Mac capture", err) @@ -335,6 +342,26 @@ func TestMacDesktopOptInAuthenticationAndIdempotentSetup(t *testing.T) { } } +func TestMacConnectionErrorsDistinguishRejectedLoginFromBrokenConnection(t *testing.T) { + for _, test := range []struct { + err error + code string + }{ + {errors.New("SecurityResult handshake failed: private upstream response"), "desktop_authentication"}, + {&rfbHandshakeFailure{cause: errors.New("SecurityResult handshake failed: private upstream response")}, "desktop_authentication"}, + {&rfbHandshakeFailure{cause: context.DeadlineExceeded}, "timeout"}, + {&rfbHandshakeFailure{cause: io.EOF}, "unavailable"}, + {io.ErrUnexpectedEOF, "unavailable"}, + {problem("desktop_auth_protocol", "Unsupported Screen Sharing authentication."), "desktop_auth_protocol"}, + {errors.New("Security handshake failed; no suitable auth schemes found; server supports: private upstream response"), "desktop_auth_protocol"}, + } { + got := asError(macConnectionError(t.Context(), test.err)) + if got.Code != test.code || strings.Contains(got.Message, "private upstream") { + t.Fatalf("wrong or unsanitized desktop failure: %+v", got) + } + } +} + func TestMacViewerSharesNativeFramesButCannotSendInput(t *testing.T) { s, store, fixture, _ := macTestService(t) enableTestMac(t, s, store) diff --git a/daemon/internal/surface/rfb.go b/daemon/internal/surface/rfb.go index 608da8b..20172a3 100644 --- a/daemon/internal/surface/rfb.go +++ b/daemon/internal/surface/rfb.go @@ -143,7 +143,10 @@ func newRFBWithAuth(ctx context.Context, conn net.Conn, auth []vnc.ClientAuth) ( client, err := connectDesktopRFB(ctx, conn, cfg) if err != nil { conn.Close() - return nil, problem("rfb_handshake", "The desktop VNC connection could not be opened.") + if ctx.Err() != nil { + err = ctx.Err() + } + return nil, &rfbHandshakeFailure{cause: err} } width, height := int(client.FramebufferWidth()), int(client.FramebufferHeight()) if width < 1 || height < 1 || width*height > 16<<20 { diff --git a/daemon/internal/surface/rfb_handshake.go b/daemon/internal/surface/rfb_handshake.go index d0930b0..b2ae2ab 100644 --- a/daemon/internal/surface/rfb_handshake.go +++ b/daemon/internal/surface/rfb_handshake.go @@ -9,6 +9,13 @@ import ( vnc "github.com/kward/go-vnc" ) +// Keep the underlying failure available for classification without exposing an +// RFB server's raw authentication response in logs or API messages. +type rfbHandshakeFailure struct{ cause error } + +func (*rfbHandshakeFailure) Error() string { return "The desktop VNC connection could not be opened." } +func (e *rfbHandshakeFailure) Unwrap() error { return e.cause } + // The pinned go-vnc revision writes the RGB maxima in SetPixelFormat using // little-endian rather than RFB's network byte order. Connect sends this message // itself: correcting the format afterwards is too late for strict servers such diff --git a/daemon/internal/surface/rfb_test.go b/daemon/internal/surface/rfb_test.go index 5d04d61..4604709 100644 --- a/daemon/internal/surface/rfb_test.go +++ b/daemon/internal/surface/rfb_test.go @@ -29,6 +29,7 @@ type rfbFixture struct { inputs []wireInput resize bool largeFrame bool + tiledFrame bool debug bool frameRequests int authentication func(io.ReadWriteCloser) bool @@ -130,6 +131,28 @@ func (f *rfbFixture) serve(conn io.ReadWriteCloser) { } } var frame bytes.Buffer + if encoding == 0 && f.tiledFrame { + // Apple/ZRLE-style batches expose hundreds of dirty regions to the + // native viewer. Full-screen copies per region used to stall it. + const tile = 64 + frame.Write([]byte{0, 0}) + _ = binary.Write(&frame, binary.BigEndian, uint16(((int(w)+tile-1)/tile)*((int(h)+tile-1)/tile))) + for y := 0; y < int(h); y += tile { + for x := 0; x < int(w); x += tile { + tw, th := min(tile, int(w)-x), min(tile, int(h)-y) + for _, value := range []uint16{uint16(x), uint16(y), uint16(tw), uint16(th)} { + _ = binary.Write(&frame, binary.BigEndian, value) + } + _ = binary.Write(&frame, binary.BigEndian, int32(0)) + frame.Write(bytes.Repeat([]byte{40, 30, 200, 0}, tw*th)) + } + } + sentPixels = true + if _, err := conn.Write(frame.Bytes()); err != nil { + return + } + continue + } frame.Write([]byte{0, 0, 0, 1}) for _, v := range []uint16{0, 0, w, h} { _ = binary.Write(&frame, binary.BigEndian, v) diff --git a/packages/sdk/README.md b/packages/sdk/README.md index b618b01..101c05e 100644 --- a/packages/sdk/README.md +++ b/packages/sdk/README.md @@ -34,6 +34,9 @@ mindwire desktop disable # Close desktop access and forget its login ``` The phone uses its existing paired SSH tunnel and never receives the Mac password. +If macOS rejects the login, the CLI lets you retry the password or check Screen +Sharing permissions. Canceling or entering a wrong password keeps the existing +desktop setup and active connections unchanged. Opening a workspace or checking status does not enable screen access. See the [desktop contract](../../daemon/DESKTOP.md#personal-mac-desktop) for platform support, permissions and the shared input protocol. Existing installations need a release diff --git a/packages/sdk/src/computer/desktop.ts b/packages/sdk/src/computer/desktop.ts index a2d9b47..32383a5 100644 --- a/packages/sdk/src/computer/desktop.ts +++ b/packages/sdk/src/computer/desktop.ts @@ -2,6 +2,7 @@ import { spawn } from "node:child_process"; import { readFile } from "node:fs/promises"; import * as path from "node:path"; import type { Mindwire } from "../client.js"; +import { ApiError } from "../errors.js"; import type { LocalDesktopInfo } from "../surfaces.js"; import { terminalSetupPrompt, type SetupPrompt } from "./setup-prompt.js"; import { terminalText } from "./terminal-ui.js"; @@ -67,9 +68,33 @@ export async function manageDesktop(options: { } info = await client.surfaces.localStatus(); } - const password = await prompt.secret(`Mac password for ${terminalText(info.username)} (hidden): `); - if (!password || Buffer.byteLength(password) > 63 || password.includes("\0")) throw new Error("Enter your Mac password (up to 63 UTF-8 bytes). Desktop settings were not changed."); - await client.surfaces.configureLocal({ enabled: true, username: info.username, password }, localToken); + while (true) { + const password = await prompt.secret(`Mac password for ${terminalText(info.username)} (hidden): `); + let retryMessage: string; + if (!password || Buffer.byteLength(password) > 63 || password.includes("\0")) { + retryMessage = "Enter your Mac login password (up to 63 UTF-8 bytes)."; + } else { + try { + await client.surfaces.configureLocal({ enabled: true, username: info.username, password }, localToken); + break; + } catch (error) { + if (!(error instanceof ApiError) || !error.body || typeof error.body !== "object" || + !("code" in error.body) || error.body.code !== "desktop_authentication") throw error; + retryMessage = `The Mac didn’t accept that login. Check the password for ${terminalText(info.username)} and try again.\nUse your Mac login password, not your Apple Account password. If it’s correct, check that Screen Sharing allows this account.`; + } + } + prompt.print(`${retryMessage}\nDesktop settings were not changed.`); + const retry = prompt.select ? await prompt.select("Mac login", [ + { value: "retry", label: "Try password again" }, + { value: "settings", label: "Open Screen Sharing settings" }, + { value: "cancel", label: "Cancel setup" }, + ]) : ((await prompt.question("Try password again? [y/N]: ")).toLowerCase() === "y" ? "retry" : "cancel"); + if (retry !== "retry" && retry !== "settings") return info; + if (retry === "settings") { + (options.openSettings ?? openScreenSharingSettings)(); + await prompt.question("Check that your Mac account is allowed, then press Return to retry: "); + } + } prompt.print("Desktop access is on. Open Desktop in Mindwire on your phone.\nTurn it off anytime with mindwire desktop disable."); return client.surfaces.localStatus(); } diff --git a/packages/sdk/test/computer-desktop.test.ts b/packages/sdk/test/computer-desktop.test.ts index 8bae8b1..4c3c71b 100644 --- a/packages/sdk/test/computer-desktop.test.ts +++ b/packages/sdk/test/computer-desktop.test.ts @@ -7,13 +7,18 @@ function fixture(choices: (string | undefined)[], initiallyEnabled = false) { let info: LocalDesktopInfo = { supported: true, enabled: initiallyEnabled, screenSharing: false, username: "owner" }; const writes: LocalDesktopSettings[] = [], output: string[] = []; let opened = 0, passwords = 0, forbiddenQuestion = false; + let rejected: { code: string; status: number }[] = []; + let secrets = [" test Ω password "]; const client = new Mindwire({ target: remote("http://127.0.0.1:1234", { token: "daemon-token" }), fetch: async (url, init) => { expect(new URL(url).pathname).toBe("/surfaces/desktop/local"); expect(new Headers(init?.headers).get("Authorization")).toBe("Bearer daemon-token"); if (init?.method === "PUT") { expect(new Headers(init.headers).get("X-Mindwire-Local-Control")).toBe("a".repeat(64)); const settings = JSON.parse(String(init.body)) as LocalDesktopSettings; - writes.push(settings); info = { ...info, enabled: settings.enabled }; + writes.push(settings); + const rejection = rejected.shift(); + if (rejection) return Response.json({ error: "Private authentication details must not be printed", code: rejection.code }, { status: rejection.status }); + info = { ...info, enabled: settings.enabled }; return Response.json({ enabled: info.enabled }); } return Response.json(info); @@ -21,7 +26,7 @@ function fixture(choices: (string | undefined)[], initiallyEnabled = false) { const prompt: SetupPrompt = { print: value => output.push(value), open: () => { throw new Error("Use the fixed Mac settings destination"); }, question: async () => { if (forbiddenQuestion) throw new Error("Cancelled picker reopened as a question"); return ""; }, - secret: async () => { passwords++; return " test Ω password "; }, + secret: async () => { passwords++; return secrets.shift() ?? " test Ω password "; }, select: async () => choices.shift() as T | undefined, }; return { @@ -30,6 +35,8 @@ function fixture(choices: (string | undefined)[], initiallyEnabled = false) { readControlToken: async () => "a".repeat(64), setSharing: () => { info = { ...info, screenSharing: true }; }, forbidQuestions: () => { forbiddenQuestion = true; }, + reject: (code = "desktop_authentication", status = 428) => { rejected.push({ code, status }); }, + passwords: (...values: string[]) => { secrets = values; }, counts: () => ({ opened, passwords }), }; } @@ -43,6 +50,49 @@ test("Mac desktop status never enables screen sharing or asks for a password", a expect(desktopSummary(info)).toContain("mindwire desktop enable"); }); +test("rejected Mac password offers a private retry and saves only after acceptance", async () => { + const f = fixture(["allow", "retry"]); f.setSharing(); f.reject(); + f.passwords("incorrect private password", "correct private password"); + const result = await manageDesktop({ ...f, directory: "/unused", action: "enable", interactive: true }); + expect(result.enabled).toBe(true); + expect(f.counts().passwords).toBe(2); + expect(f.writes.map(write => write.password)).toEqual(["incorrect private password", "correct private password"]); + const output = f.output.join("\n"); + expect(output).toContain("Check the password for owner and try again"); + expect(output).toContain("Desktop settings were not changed"); + expect(output).not.toMatch(/127\.0\.0\.1|PUT |428|private password|Private authentication details/); +}); + +test("cancelling a rejected password leaves an existing desktop setup enabled", async () => { + for (const choice of ["cancel", undefined]) { + const f = fixture(["allow", choice], true); f.setSharing(); f.reject(); f.forbidQuestions(); + const result = await manageDesktop({ ...f, directory: "/unused", action: "enable", interactive: true }); + expect(result.enabled).toBe(true); + expect(f.writes).toHaveLength(1); + expect(f.counts().passwords).toBe(1); + expect(f.output.join("\n")).not.toContain("Desktop access is on."); + } +}); + +test("password retry can open Mac settings and rejects invalid passwords locally", async () => { + const f = fixture(["allow", "retry", "settings"]); f.setSharing(); f.reject(); + f.passwords("", "incorrect private password", " test Ω password "); + const result = await manageDesktop({ ...f, directory: "/unused", action: "enable", interactive: true }); + expect(result.enabled).toBe(true); + expect(f.counts()).toEqual({ opened: 1, passwords: 3 }); + expect(f.writes).toHaveLength(2); +}); + +test("connection and permission failures are not presented as an incorrect password", async () => { + for (const [code, status] of [["timeout", 504], ["forbidden", 403], ["screen_sharing_off", 428]] as const) { + const f = fixture(["allow", "retry"]); f.setSharing(); f.reject(code, status); + await expect(manageDesktop({ ...f, directory: "/unused", action: "enable", interactive: true })).rejects.toThrow(); + expect(f.counts().passwords).toBe(1); + expect(f.writes).toHaveLength(1); + expect(f.output.join("\n")).not.toContain("didn’t accept"); + } +}); + test("CLI opt-in guides Mac settings then saves credentials locally once", async () => { const f = fixture(["allow", "open"]); const result = await manageDesktop({ ...f, directory: "/unused", action: "enable", interactive: true }); diff --git a/packages/sdk/test/fixtures/computer-directory.ts b/packages/sdk/test/fixtures/computer-directory.ts index 033cbe4..434da65 100644 --- a/packages/sdk/test/fixtures/computer-directory.ts +++ b/packages/sdk/test/fixtures/computer-directory.ts @@ -149,7 +149,12 @@ export class ComputerDirectoryFixture { }); request.on("error", () => upstream.destroy()); response.on("close", () => upstream.destroy()); - request.pipe(upstream); + // Bun 1.3's ClientRequest can emit invalid chunk framing when an incoming + // stream is piped into it. Send each small fixture body in one write so + // Node receives the original bytes (and the backend still enforces limits). + const body: Buffer[] = []; + request.on("data", (chunk: Buffer) => body.push(chunk)); + request.on("end", () => upstream.end(Buffer.concat(body))); }); this.server.listen(0, "127.0.0.1"); await once(this.server, "listening");