From 84115038b344527ed482fb0ef447e9e333180a1d Mon Sep 17 00:00:00 2001 From: Arvid Berndtsson <103070833+arvid-berndtsson@users.noreply.github.com> Date: Sat, 3 Oct 2026 12:48:02 +0200 Subject: [PATCH] fix: sanitize terminal controls in agent output --- src/output.test.ts | 29 +++++++++++++++++++++++++++++ src/output.ts | 6 +++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/output.test.ts b/src/output.test.ts index 64b1f95..94c46c6 100644 --- a/src/output.test.ts +++ b/src/output.test.ts @@ -25,4 +25,33 @@ describe("printResponse", () => { totalCount: 1, }); }); + + it("removes terminal control bytes from agent-mode output", () => { + let out = ""; + printResponse( + { message: "\u001b]0;PWNED\u0007\u001b[31mred\u001b[0m" }, + { pretty: false, agentMode: true }, + (s) => { + out += s; + }, + ); + + assert.equal(/[\u0000-\u001f\u007f-\u009f]/u.test(out.slice(0, -1)), false); + assert.match(out, /message: ".*PWNED.*red/); + assert.equal(out.endsWith("\n"), true); + }); + + it("keeps TOON structural newlines and escaped string controls", () => { + let out = ""; + printResponse( + { message: "line1\nline2\r\n\tindented" }, + { pretty: false, agentMode: true }, + (s) => { + out += s; + }, + ); + + assert.equal(out.split("\n").length, 2); + assert.match(out, /line1\\nline2\\r\\n\\tindented/); + }); }); diff --git a/src/output.ts b/src/output.ts index 9f77f2b..54f57c4 100644 --- a/src/output.ts +++ b/src/output.ts @@ -39,6 +39,10 @@ function unwrapResultsData(value: unknown): unknown { return normalized; } +function sanitizeTerminalControls(value: string): string { + return value.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f-\u009f]/g, ""); +} + export function printResponse( value: unknown, options: OutputOptions, @@ -52,7 +56,7 @@ export function printResponse( const normalized = unwrapResultsData(value); if (agentModeEnabled(options.agentMode)) { - write(`${encode(normalized)}\n`); + write(`${sanitizeTerminalControls(encode(normalized))}\n`); return; }