diff --git a/.changeset/safe-download-filenames.md b/.changeset/safe-download-filenames.md new file mode 100644 index 0000000000..6b8812bf46 --- /dev/null +++ b/.changeset/safe-download-filenames.md @@ -0,0 +1,5 @@ +--- +"@moonshot-ai/kimi-code": patch +--- + +Sanitize filenames in session file download response headers. diff --git a/packages/kap-server/src/routes/fs.ts b/packages/kap-server/src/routes/fs.ts index 598da6d85a..d21bc5e5d4 100644 --- a/packages/kap-server/src/routes/fs.ts +++ b/packages/kap-server/src/routes/fs.ts @@ -963,8 +963,9 @@ function buildValidationEnvelope( }; } -function sanitizeFilename(rel: string): string { +export function sanitizeFilename(rel: string): string { const segs = rel.split('/'); - const base = segs[segs.length - 1] ?? rel; - return base.replace(/"/g, '\\"'); + const base = segs.at(-1) ?? rel; + const sanitized = base.replaceAll(/[\u0000-\u001F\u007F"\\]/g, '_'); + return sanitized.length > 0 ? sanitized : 'download'; } diff --git a/packages/kap-server/test/fs.test.ts b/packages/kap-server/test/fs.test.ts index de43d11250..526f915a02 100644 --- a/packages/kap-server/test/fs.test.ts +++ b/packages/kap-server/test/fs.test.ts @@ -8,6 +8,7 @@ import { FakeRuntime } from '@moonshot-ai/agent-core-v2/runtime/fakeRuntime'; import { ErrorCode } from '../src/protocol/error-codes'; import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it } from 'vitest'; +import { sanitizeFilename } from '../src/routes/fs'; import { type RunningServer, startServer } from '../src/start'; import { TEST_HOST_IDENTITY } from './helpers/hostIdentity'; import { authHeaders } from './helpers/auth'; @@ -354,6 +355,10 @@ describe('server-v2 /api/v1 fs routes', () => { } }); + it('sanitizeFilename strips control characters and path separators', async () => { + expect(sanitizeFilename('bad"name\\.txt')).toBe('bad_name_.txt'); + }); + async function postWorkspaceSearch(body: unknown): Promise> { const res = await fetch(`${base}/api/v1/workspace/fs:search`, { method: 'POST',