From 50600786f755b6602989f3b0f88cd090ff948196 Mon Sep 17 00:00:00 2001 From: Dotta Date: Mon, 2 Nov 2026 23:56:13 -0600 Subject: [PATCH] fix(ui): bound review projection fields before trimming Co-Authored-By: Paperclip --- .../IssueThreadInteractionCard.test.tsx | 20 +++++++++++++++++++ .../components/IssueThreadInteractionCard.tsx | 6 +++--- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/ui/src/components/IssueThreadInteractionCard.test.tsx b/ui/src/components/IssueThreadInteractionCard.test.tsx index db29c76ec83..25f6849efe3 100644 --- a/ui/src/components/IssueThreadInteractionCard.test.tsx +++ b/ui/src/components/IssueThreadInteractionCard.test.tsx @@ -1308,6 +1308,26 @@ describe("IssueThreadInteractionCard tool-action card", () => { expect(Array.from(host.querySelectorAll("button")).find(button => button.textContent === "Approve & run")?.disabled).toBe(true); }); + it.each(["argumentsSummaryJson", "argumentsHash"] as const)("rejects oversized whitespace before trimming %s", field => { + const interaction = structuredClone(pendingToolActionWriteInteraction); + const oversized = " ".repeat(field === "argumentsSummaryJson" ? 20_001 : 256); + interaction.payload.toolAction![field] = oversized; + const trim = String.prototype.trim; + let oversizedTrimCalls = 0; + const spy = vi.spyOn(String.prototype, "trim").mockImplementation(function (this: string) { + if (String(this) === oversized) oversizedTrimCalls++; + return trim.call(this); + }); + try { + const host = renderCard({ interaction }); + expect(host.querySelector("details")).toBeNull(); + expect(host.textContent).toContain("Request arguments are unavailable."); + expect(oversizedTrimCalls).toBe(0); + } finally { + spy.mockRestore(); + } + }); + it("keeps the destructive request warning and approval styling", () => { const host = renderCard({ interaction: pendingToolActionDestructiveInteraction, onAcceptInteraction: vi.fn(), onRejectInteraction: vi.fn() }); expect(host.textContent).toContain("cannot be undone"); diff --git a/ui/src/components/IssueThreadInteractionCard.tsx b/ui/src/components/IssueThreadInteractionCard.tsx index e95d891f27c..38281d787d1 100644 --- a/ui/src/components/IssueThreadInteractionCard.tsx +++ b/ui/src/components/IssueThreadInteractionCard.tsx @@ -1801,9 +1801,9 @@ function RequestToolActionCard({ const variant = payload.risk === "destructive" ? "destructive" : "cta"; // This server projection is already redacted and bounded by the interaction schema. const inspectableArguments = typeof payload.argumentsSummaryJson === "string" - && payload.argumentsSummaryJson.trim().length > 0 && payload.argumentsSummaryJson.length <= 20_000 - && typeof payload.argumentsHash === "string" && payload.argumentsHash.trim().length > 0 - && payload.argumentsHash.length <= 255; + && payload.argumentsSummaryJson.length <= 20_000 && payload.argumentsSummaryJson.trim().length > 0 + && typeof payload.argumentsHash === "string" && payload.argumentsHash.length <= 255 + && payload.argumentsHash.trim().length > 0; useEffect(() => {