fix(app): swallow URIError in assistant file link parser on bare '%'

decodeURIComponent throws on '%' not followed by two hex digits. The two
calls inside parseAssistantFileLink and normalizeFileUrlPath had no
try/catch, so any timeline message containing strings like '100% packet
loss' (ping output), '%PATH%' or '0% off' could escape into the React
tree and unmount the agent route, leaving the renderer blank.

Wrap both calls in a local safeDecodeURIComponent that returns the input
unchanged when decoding fails, mirroring the helper already used in
host-routes.ts.

Closes #1148
This commit is contained in:
ElliotWu
2026-05-25 10:05:00 +00:00
committed by GitHub
parent 178708a44e
commit 8a2e5c786e
2 changed files with 29 additions and 2 deletions
@@ -360,6 +360,25 @@ describe("parseAssistantFileLink", () => {
}),
).toBeNull();
});
it("does not throw when the input contains a literal '%' that is not a valid percent-escape", () => {
// Regressions for tool output strings like ping's "100% packet loss",
// Windows "%PATH%" references, and percentages such as "0% off".
// decodeURIComponent throws URIError on these; the parser must swallow
// it and return null rather than crash the renderer.
const cases = [
"/tmp/100% packet loss",
"/Users/test/project/0% off",
"/var/log/%PATH%/x.log",
"file:///tmp/100% packet loss",
];
for (const value of cases) {
expect(() =>
parseAssistantFileLink(value, { workspaceRoot: "/Users/test/project" }),
).not.toThrow();
}
expect(() => parseFileProtocolUrl("file:///tmp/100% packet loss")).not.toThrow();
});
});
describe("normalizeInlinePathTarget", () => {
+10 -2
View File
@@ -1,5 +1,13 @@
import { isAbsolutePath } from "@/utils/path";
function safeDecodeURIComponent(value: string): string {
try {
return decodeURIComponent(value);
} catch {
return value;
}
}
export interface InlinePathTarget {
raw: string;
path: string;
@@ -350,7 +358,7 @@ export function parseAssistantFileLink(
return null;
}
const normalizedPath = normalizePathToken(decodeURIComponent(parsedUrl.pathname));
const normalizedPath = normalizePathToken(safeDecodeURIComponent(parsedUrl.pathname));
if (!normalizedPath || !isAbsolutePath(normalizedPath)) {
return null;
}
@@ -659,7 +667,7 @@ function normalizeFileUrlPath(pathname: string): string | null {
return null;
}
const decoded = decodeURIComponent(pathname).replace(/\\/g, "/");
const decoded = safeDecodeURIComponent(pathname).replace(/\\/g, "/");
if (!decoded) {
return null;
}