From 50055ddfced66452161bb955137a7c40cf68c981 Mon Sep 17 00:00:00 2001
From: 7723qqq <7723qqq@users.noreply.github.com>
Date: Fri, 2 Oct 2026 02:07:04 +0800
Subject: [PATCH 1/7] fix: clear the error-severity CodeQL backlog and dismiss
the false positives
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Two real defects, both now covered:
- The remote-control page prefix was embedded in an injected `` closed the block early and the rest was parsed as
markup. Today's only caller percent-encodes both halves, so this was hardening
rather than a live hole — but the function takes an arbitrary string and the
escaping it relied on did not make that true.
- A provider error body is parsed for its HTML `
` with a lazy span that
re-scans the tail from every opening tag: measured against the old form, 40k
opening tags cost over a second, and the body arrives over the network. The
title is now found by locating the open tag and then the next close tag.
Two smaller changes: the MCP tool-name decoder states its `indexOf` not-found
case instead of folding it into `<= 0` next to a length comparison, and the
private-key redaction rule in the prompt-metadata sanitizer — the one that could
actually leak a credential — finally has a test.
The remaining 30 alerts were triaged rather than code-changed: fourteen
`/\/+$/`-shaped findings are linear (a single-quantifier anchored pattern has
nothing to backtrack into), one `/<[^>]*>/g` likewise, and three are documented
won't-fixes. Each dismissal carries the reason.
---
...rol-prefix-escaped-for-script-embedding.md | 5 ++
.../src/cli/sub/web/remote-control.ts | 14 +++-
apps/kimi-code/src/tui/utils/mcp-tool-name.ts | 7 +-
.../test/cli/web/remote-control.test.ts | 24 +++++++
.../test/tui/utils/mcp-tool-name.test.ts | 44 +++++++++++++
architecture.json | 4 +-
.../node-sdk/src/error-protocol/serialize.ts | 17 ++++-
.../test/error-status-message.test.ts | 65 +++++++++++++++++++
.../test/session-prompt-events.test.ts | 47 ++++++++++++++
9 files changed, 221 insertions(+), 6 deletions(-)
create mode 100644 .changeset/remote-control-prefix-escaped-for-script-embedding.md
create mode 100644 apps/kimi-code/test/tui/utils/mcp-tool-name.test.ts
create mode 100644 packages/node-sdk/test/error-status-message.test.ts
diff --git a/.changeset/remote-control-prefix-escaped-for-script-embedding.md b/.changeset/remote-control-prefix-escaped-for-script-embedding.md
new file mode 100644
index 00000000000..044f31196b1
--- /dev/null
+++ b/.changeset/remote-control-prefix-escaped-for-script-embedding.md
@@ -0,0 +1,5 @@
+---
+"@moonshot-ai/kimi-code": patch
+---
+
+The remote-control page prefix is now escaped for embedding in a script block, so a prefix containing a closing tag can no longer break out of it.
diff --git a/apps/kimi-code/src/cli/sub/web/remote-control.ts b/apps/kimi-code/src/cli/sub/web/remote-control.ts
index ef9eb693088..81f6b3dd19e 100644
--- a/apps/kimi-code/src/cli/sub/web/remote-control.ts
+++ b/apps/kimi-code/src/cli/sub/web/remote-control.ts
@@ -271,7 +271,19 @@ export function rewriteRemoteControlResponse(
): Buffer {
const normalizedPrefix = publicPrefix.replace(/\/+$/, '');
if (contentType.toLowerCase().includes('text/html')) {
- const prefixLiteral = JSON.stringify(normalizedPrefix);
+ // `JSON.stringify` escapes quotes and backslashes but **not** `<`, so a
+ // prefix containing `` closes the block early and everything after
+ // it is parsed as markup. Escaping `<` keeps the literal valid JavaScript
+ // and inert to the HTML parser; U+2028 / U+2029 are line terminators to a
+ // pre-ES2019 parser and would break the statement on their own.
+ //
+ // Today's only caller percent-encodes both halves of the prefix, so this is
+ // hardening rather than a live hole — but the function takes an arbitrary
+ // string, and the escaping it relied on did not make that true.
+ const prefixLiteral = JSON.stringify(normalizedPrefix)
+ .replaceAll('<', '\\u003c')
+ .replaceAll('\u2028', '\\u2028')
+ .replaceAll('\u2029', '\\u2029');
const injected = ``;
let text = body.toString('utf8');
const headMatch = /]*)?>/i.exec(text);
diff --git a/apps/kimi-code/src/tui/utils/mcp-tool-name.ts b/apps/kimi-code/src/tui/utils/mcp-tool-name.ts
index dd5998a82ae..7f8df1bd80b 100644
--- a/apps/kimi-code/src/tui/utils/mcp-tool-name.ts
+++ b/apps/kimi-code/src/tui/utils/mcp-tool-name.ts
@@ -8,7 +8,12 @@ export function decodeMcpToolName(
if (!name.startsWith(PREFIX)) return null;
const rest = name.slice(PREFIX.length);
const sep = rest.indexOf('__');
- if (sep <= 0 || sep === rest.length - 2) return null;
+ // `sep < 0` is the not-found case, `sep === 0` an empty server name, and
+ // `sep + 2 === rest.length` an empty tool name. The not-found check is spelled
+ // out rather than left to the `<= 0` fold because a bare `indexOf` result is
+ // compared against a length below, and `-1` silently satisfying that is the
+ // kind of thing a reader has to stop and re-derive.
+ if (sep < 0 || sep === 0 || sep + 2 === rest.length) return null;
return {
serverName: rest.slice(0, sep),
toolName: rest.slice(sep + 2),
diff --git a/apps/kimi-code/test/cli/web/remote-control.test.ts b/apps/kimi-code/test/cli/web/remote-control.test.ts
index 330cf55e1ca..7d0fc7ecb1c 100644
--- a/apps/kimi-code/test/cli/web/remote-control.test.ts
+++ b/apps/kimi-code/test/cli/web/remote-control.test.ts
@@ -200,6 +200,30 @@ describe('Remote Control HTTP forwarding', () => {
).toString();
expect(css).toBe(`.x{background:url(${prefix}/assets/x.png)}`);
});
+
+ it('keeps a prefix that contains a script-closing tag from breaking out of the block', () => {
+ // `JSON.stringify` escapes quotes and backslashes but not `<`, so a prefix
+ // holding `` used to end the block early and leave the rest of the
+ // literal to be parsed as markup. The one caller percent-encodes its halves
+ // today, but this function takes an arbitrary string and the escaping it
+ // relied on did not make that safe.
+ const html = rewriteRemoteControlResponse(
+ 'text/html; charset=utf-8',
+ Buffer.from(''),
+ '/relay/devices/d1
',
+ ).toString();
+
+ // Exactly the one script block we inject: every `<` the payload carried is
+ // escaped, so it stays inside the literal instead of closing the block and
+ // letting the rest be parsed as markup. The text itself is still there — it
+ // is inert data in a JS string, which is where it belongs. Only `<` matters:
+ // a bare `>` cannot open a tag.
+ expect(html.match(/