diff --git a/packages/opencode/src/server/routes/instance/httpapi/handlers/pty.ts b/packages/opencode/src/server/routes/instance/httpapi/handlers/pty.ts index 8558ee793c..a5092315b5 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/handlers/pty.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/handlers/pty.ts @@ -9,6 +9,29 @@ import * as Socket from "effect/unstable/socket/Socket" import { InstanceHttpApi } from "../api" import { CursorQuery, Params, PtyPaths } from "../groups/pty" +const inputDecoder = new TextDecoder("utf-8", { fatal: true }) + +export function handlePtyInput( + handler: { onMessage: (message: string | ArrayBuffer) => void }, + message: string | Uint8Array, +) { + if (typeof message === "string") { + handler.onMessage(message) + return Effect.void + } + return Effect.try({ + try: () => inputDecoder.decode(message), + catch: () => new Error("invalid PTY websocket input"), + }).pipe( + Effect.catch(() => Effect.succeed(undefined)), + Effect.flatMap((decoded) => { + if (decoded === undefined) return Effect.void + handler.onMessage(decoded) + return Effect.void + }), + ) +} + export const ptyHandlers = HttpApiBuilder.group(InstanceHttpApi, "pty", (handlers) => Effect.gen(function* () { const pty = yield* Pty.Service @@ -102,9 +125,7 @@ export const ptyConnectRoute = HttpRouter.add( if (!handler) return HttpServerResponse.empty() yield* socket - .runRaw((message) => { - handler.onMessage(typeof message === "string" ? message : message.slice().buffer) - }) + .runRaw((message) => handlePtyInput(handler, message)) .pipe( Effect.catchReason("SocketError", "SocketCloseError", () => Effect.void), Effect.ensuring( diff --git a/packages/opencode/test/server/httpapi-pty-websocket.test.ts b/packages/opencode/test/server/httpapi-pty-websocket.test.ts new file mode 100644 index 0000000000..7615e1beac --- /dev/null +++ b/packages/opencode/test/server/httpapi-pty-websocket.test.ts @@ -0,0 +1,16 @@ +import { describe, expect, test } from "bun:test" +import { Effect } from "effect" +import { handlePtyInput } from "../../src/server/routes/instance/httpapi/handlers/pty" + +describe("pty HttpApi websocket input", () => { + test("does not forward invalid binary frames to the PTY handler", async () => { + const messages: Array = [] + const handler = { onMessage: (message: string | ArrayBuffer) => messages.push(message) } + + await Effect.runPromise(handlePtyInput(handler, "ready")) + await Effect.runPromise(handlePtyInput(handler, new Uint8Array([0xff, 0xfe, 0xfd]))) + await Effect.runPromise(handlePtyInput(handler, new TextEncoder().encode("hello"))) + + expect(messages).toEqual(["ready", "hello"]) + }) +})