Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion packages/loopover-engine/src/miner/chat-grounding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,10 @@ function* foldToolResultMessage(message: Record<string, unknown>): Generator<Cha
const block = asRecord(rawBlock);
if (!block || block.type !== "tool_result") continue;
const tool = typeof block.tool_use_id === "string" ? block.tool_use_id : "";
yield { type: "tool_result", tool, output: block.content };
// Same PUBLIC_FIELD_BLOCKLIST backstop as foldAssistantMessage — MCP tool output is untrusted and must
// not leak blocklisted fields into the public chat-grounding stream (#8869).
const output = typeof block.content === "string" ? redactBlockedText(block.content) : block.content;
yield { type: "tool_result", tool, output };
}
}

Expand Down
18 changes: 18 additions & 0 deletions packages/loopover-engine/test/chat-grounding.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,24 @@ test("a blocked term in a text chunk is redacted, a clean chunk is forwarded ver
assert.deepEqual(events, [{ type: "text", text: CHAT_REDACTED_TEXT }, { type: "done" }]);
});

test("a blocked term in a tool_result string is redacted (#8869)", async () => {
const events = await collect(
runChatGrounding(USER_ONLY, {
env: AGENT_SDK_ENV,
query: queryYielding([
{
type: "user",
message: { content: [{ type: "tool_result", tool_use_id: "loopover_miner_status", content: "your trust score is 9" }] },
},
]),
}),
);
assert.deepEqual(events, [
{ type: "tool_result", tool: "loopover_miner_status", output: CHAT_REDACTED_TEXT },
{ type: "done" },
]);
});

test("a thrown session becomes an error event still followed by done", async () => {
const events = await collect(
runChatGrounding(USER_ONLY, {
Expand Down
37 changes: 37 additions & 0 deletions test/unit/chat-grounding-engine.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,43 @@ describe("chat grounding privacy backstop (#6517)", () => {
);
expect(events).toEqual([{ type: "text", text: "your run state is idle" }, { type: "done" }]);
});

it("redacts a tool_result string containing a blocked term (#8869)", async () => {
const events = await collect(
runChatGrounding(USER_ONLY, {
env: AGENT_SDK_ENV,
query: queryYielding([
{
type: "user",
message: { content: [{ type: "tool_result", tool_use_id: "loopover_miner_status", content: "your trust score is 9" }] },
},
]),
}),
);
expect(events).toEqual([
{ type: "tool_result", tool: "loopover_miner_status", output: CHAT_REDACTED_TEXT },
{ type: "done" },
]);
});

it("forwards a non-string tool_result payload unchanged (#8869)", async () => {
const payload = { ok: true, state: "idle" };
const events = await collect(
runChatGrounding(USER_ONLY, {
env: AGENT_SDK_ENV,
query: queryYielding([
{
type: "user",
message: { content: [{ type: "tool_result", tool_use_id: "loopover_miner_status", content: payload }] },
},
]),
}),
);
expect(events).toEqual([
{ type: "tool_result", tool: "loopover_miner_status", output: payload },
{ type: "done" },
]);
});
});

describe("chat message validation + prompt building (#6517)", () => {
Expand Down
Loading