diff --git a/.changeset/encode-token-fragments.md b/.changeset/encode-token-fragments.md new file mode 100644 index 000000000..3f7fcbef9 --- /dev/null +++ b/.changeset/encode-token-fragments.md @@ -0,0 +1,5 @@ +--- +"@moonshot-ai/kimi-code": patch +--- + +Encode server Web UI token fragments before adding them to openable URLs. diff --git a/apps/kimi-code/src/cli/sub/server/access-urls.ts b/apps/kimi-code/src/cli/sub/server/access-urls.ts index 0c6233fe8..a2c3d3581 100644 --- a/apps/kimi-code/src/cli/sub/server/access-urls.ts +++ b/apps/kimi-code/src/cli/sub/server/access-urls.ts @@ -16,7 +16,7 @@ import { formatHostForUrl, listNetworkAddresses, type NetworkAddress } from './n */ export function buildOpenableUrl(bareOrigin: string, token: string | undefined): string { const base = bareOrigin.endsWith('/') ? bareOrigin.slice(0, -1) : bareOrigin; - return token === undefined ? `${base}/` : `${base}/#token=${token}`; + return token === undefined ? `${base}/` : `${base}/#token=${encodeURIComponent(token)}`; } /** diff --git a/apps/kimi-code/src/tui/commands/web.ts b/apps/kimi-code/src/tui/commands/web.ts index 366860016..8b92fbb08 100644 --- a/apps/kimi-code/src/tui/commands/web.ts +++ b/apps/kimi-code/src/tui/commands/web.ts @@ -88,5 +88,5 @@ export async function handleWebCommand(host: SlashCommandHost): Promise { */ export function webSessionUrl(origin: string, sessionId: string, token?: string): string { const base = `${origin.replace(/\/+$/, '')}/sessions/${encodeURIComponent(sessionId)}`; - return token === undefined ? base : `${base}#token=${token}`; + return token === undefined ? base : `${base}#token=${encodeURIComponent(token)}`; } diff --git a/apps/kimi-code/test/cli/server/server.test.ts b/apps/kimi-code/test/cli/server/server.test.ts index b6ee71f95..add824aa8 100644 --- a/apps/kimi-code/test/cli/server/server.test.ts +++ b/apps/kimi-code/test/cli/server/server.test.ts @@ -1585,6 +1585,14 @@ describe('buildWebUrl', () => { expect(parsed.search).not.toContain('abc123'); }); + it('encodes token fragment values before Web UI parsing', async () => { + const { buildWebUrl } = await import('#/cli/sub/server/run'); + const token = 'a&b a%20b #token'; + const url = buildWebUrl('http://127.0.0.1:58627', token); + expect(url).toBe(`http://127.0.0.1:58627/#token=${encodeURIComponent(token)}`); + expect(new URLSearchParams(new URL(url).hash.slice(1)).get('token')).toBe(token); + }); + it('normalizes a trailing slash', async () => { const { buildWebUrl } = await import('#/cli/sub/server/run'); expect(buildWebUrl('http://127.0.0.1:58627/', 't')).toBe( @@ -1613,6 +1621,18 @@ describe('accessUrlLines', () => { ]); }); + it('encodes token fragments in access URL lines', async () => { + const { accessUrlLines } = await import('#/cli/sub/server/access-urls'); + const token = 'a&b%20c'; + const lines = accessUrlLines('127.0.0.1', 58627, token); + expect(lines).toEqual([ + { + label: 'Local: ', + url: `http://127.0.0.1:58627/#token=${encodeURIComponent(token)}`, + }, + ]); + }); + it('returns a single URL line for a specific host (no token)', async () => { const { accessUrlLines } = await import('#/cli/sub/server/access-urls'); const lines = accessUrlLines('192.168.1.5', 58627, undefined); diff --git a/apps/kimi-code/test/tui/commands/web.test.ts b/apps/kimi-code/test/tui/commands/web.test.ts index cbd6e5eec..3c157766b 100644 --- a/apps/kimi-code/test/tui/commands/web.test.ts +++ b/apps/kimi-code/test/tui/commands/web.test.ts @@ -146,6 +146,15 @@ describe('webSessionUrl', () => { ); }); + it('encodes bearer token fragments so URLSearchParams can read them back', () => { + const token = 'a&b a%20b #token'; + const url = webSessionUrl('http://127.0.0.1:58627', 'abc123', token); + expect(url).toBe( + `http://127.0.0.1:58627/sessions/abc123#token=${encodeURIComponent(token)}`, + ); + expect(new URLSearchParams(new URL(url).hash.slice(1)).get('token')).toBe(token); + }); + it('omits the fragment when no token is available', () => { expect(webSessionUrl('http://127.0.0.1:58627', 'abc123', undefined)).toBe( 'http://127.0.0.1:58627/sessions/abc123',