Skip to content

Commit d6cb0d2

Browse files
Sanitize replay diagnostic control characters
Co-authored-by: Shri Sukhani <shrisukhani@users.noreply.github.com>
1 parent 40f9f6d commit d6cb0d2

2 files changed

Lines changed: 32 additions & 1 deletion

File tree

src/agent/__tests__/run-from-action-cache.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1316,6 +1316,31 @@ describe("runFromActionCache hardening", () => {
13161316
expect(replay.steps[0]?.message).toContain("[truncated");
13171317
});
13181318

1319+
it("sanitizes control characters in cached-step read diagnostics", async () => {
1320+
const agent = new HyperAgent({
1321+
llm: createMockLLM(),
1322+
cdpActions: false,
1323+
});
1324+
const page = {} as import("@/types/agent/types").HyperPage;
1325+
const cache = {
1326+
taskId: "cache-task",
1327+
createdAt: new Date().toISOString(),
1328+
status: TaskStatus.COMPLETED,
1329+
get steps(): unknown[] {
1330+
throw new Error(`steps\u0000\n${"x".repeat(2_000)}`);
1331+
},
1332+
} as unknown as ActionCacheOutput;
1333+
1334+
const replay = await agent.runFromActionCache(cache, page);
1335+
1336+
expect(replay.status).toBe(TaskStatus.FAILED);
1337+
const message = replay.steps[0]?.message ?? "";
1338+
expect(message).toContain("Failed to read cached steps");
1339+
expect(message).toContain("[truncated");
1340+
expect(message).not.toContain("\u0000");
1341+
expect(message).not.toContain("\n");
1342+
});
1343+
13191344
it("fails replay step cleanly when page getter throws", async () => {
13201345
const agent = new HyperAgent({
13211346
llm: createMockLLM(),

src/agent/index.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1949,7 +1949,13 @@ export class HyperAgent<T extends BrowserProviders = "Local"> {
19491949
const shouldWriteReplayDebug = (): boolean =>
19501950
debug && this.isTaskLifecycleGenerationActive(replayLifecycleGeneration);
19511951
const formatReplayDiagnostic = (value: unknown): string => {
1952-
const normalized = formatUnknownError(value).replace(/\s+/g, " ").trim();
1952+
const normalized = Array.from(formatUnknownError(value), (char) => {
1953+
const code = char.charCodeAt(0);
1954+
return (code >= 0 && code < 32) || code === 127 ? " " : char;
1955+
})
1956+
.join("")
1957+
.replace(/\s+/g, " ")
1958+
.trim();
19531959
const fallback = normalized.length > 0 ? normalized : "unknown error";
19541960
if (fallback.length <= HyperAgent.MAX_REPLAY_DIAGNOSTIC_CHARS) {
19551961
return fallback;

0 commit comments

Comments
 (0)