Skip to content

Commit e70d2b2

Browse files
committed
fix(app): terminal issues
1 parent b16f7b4 commit e70d2b2

3 files changed

Lines changed: 54 additions & 5 deletions

File tree

packages/opencode/src/pty/index.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,9 @@ export namespace Pty {
3939
return next
4040
}
4141

42-
const token = (ws: Socket) => {
43-
const data = ws.data
42+
const token = (ws: unknown) => {
43+
if (!ws || typeof ws !== "object") return ws
44+
const data = (ws as { data?: unknown }).data
4445
if (data === undefined) return
4546
if (data === null) return
4647
if (typeof data !== "object") return data
@@ -317,7 +318,7 @@ export namespace Pty {
317318
}
318319
}
319320

320-
export function connect(id: string, ws: Socket, cursor?: number) {
321+
export function connect(id: string, ws: Socket, cursor?: number, identity?: unknown) {
321322
const session = state().get(id)
322323
if (!session) {
323324
ws.close()
@@ -337,7 +338,7 @@ export namespace Pty {
337338
}
338339

339340
owners.set(ws, id)
340-
session.subscribers.set(ws, { id: socketId, token: token(ws) })
341+
session.subscribers.set(ws, { id: socketId, token: token(identity ?? ws) })
341342

342343
const cleanup = () => {
343344
session.subscribers.delete(ws)

packages/opencode/src/server/routes/pty.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ export const PtyRoutes = lazy(() =>
182182
ws.close()
183183
return
184184
}
185-
handler = Pty.connect(id, socket, cursor)
185+
handler = Pty.connect(id, socket, cursor, ws)
186186
},
187187
onMessage(event) {
188188
if (typeof event.data !== "string") return

packages/opencode/test/pty/pty-output-isolation.test.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,54 @@ describe("pty", () => {
9898
})
9999
})
100100

101+
test("does not leak when identity token is only on websocket wrapper", async () => {
102+
await using dir = await tmpdir({ git: true })
103+
104+
await Instance.provide({
105+
directory: dir.path,
106+
fn: async () => {
107+
const a = await Pty.create({ command: "cat", title: "a" })
108+
try {
109+
const outA: string[] = []
110+
const outB: string[] = []
111+
const text = (data: string | Uint8Array | ArrayBuffer) => {
112+
if (typeof data === "string") return data
113+
if (data instanceof ArrayBuffer) return Buffer.from(new Uint8Array(data)).toString("utf8")
114+
return Buffer.from(data).toString("utf8")
115+
}
116+
117+
const raw: Parameters<typeof Pty.connect>[1] = {
118+
readyState: 1,
119+
send: (data) => {
120+
outA.push(text(data))
121+
},
122+
close: () => {
123+
// no-op
124+
},
125+
}
126+
127+
const wrap = { data: { events: { connection: "a" } } }
128+
129+
Pty.connect(a.id, raw, undefined, wrap)
130+
outA.length = 0
131+
132+
// Simulate Bun reusing the raw socket object before the next onOpen,
133+
// while the connection token only exists on the wrapper socket.
134+
raw.send = (data) => {
135+
outB.push(text(data))
136+
}
137+
138+
Pty.write(a.id, "AAA\n")
139+
await Bun.sleep(100)
140+
141+
expect(outB.join("")).not.toContain("AAA")
142+
} finally {
143+
await Pty.remove(a.id)
144+
}
145+
},
146+
})
147+
})
148+
101149
test("does not leak output when socket data mutates in-place", async () => {
102150
await using dir = await tmpdir({ git: true })
103151

0 commit comments

Comments
 (0)