diff --git a/apps/kimi-code/src/cli/sub/plugin-run-node.ts b/apps/kimi-code/src/cli/sub/plugin-run-node.ts index 3926a04dc1..57d53b0249 100644 --- a/apps/kimi-code/src/cli/sub/plugin-run-node.ts +++ b/apps/kimi-code/src/cli/sub/plugin-run-node.ts @@ -2,6 +2,8 @@ import { realpath } from 'node:fs/promises'; import path from 'node:path'; import { pathToFileURL } from 'node:url'; +const ALLOWED_EXTENSIONS = ['.js', '.mjs', '.cjs']; + export async function runPluginNodeEntry(entry: string, args: readonly string[]): Promise { const pluginRoot = process.env['KIMI_PLUGIN_ROOT']; if (pluginRoot === undefined || pluginRoot.trim().length === 0) { @@ -16,11 +18,21 @@ export async function runPluginNodeEntry(entry: string, args: readonly string[]) throw new Error(`Plugin node entry must be inside KIMI_PLUGIN_ROOT: ${entry}`); } + const ext = path.extname(entryReal).toLowerCase(); + if (!ALLOWED_EXTENSIONS.includes(ext)) { + throw new Error(`Plugin node entry must have a valid extension (${ALLOWED_EXTENSIONS.join(', ')}): ${entry}`); + } + process.argv = [process.argv[0] ?? process.execPath, entryReal, ...args]; await import(pathToFileURL(entryReal).href); } function isWithin(candidate: string, root: string): boolean { const relative = path.relative(root, candidate); - return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative)); + const isRelativeWithin = relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative)); + if (!isRelativeWithin) { + return false; + } + const normalizedRoot = path.normalize(root + path.sep); + return candidate.startsWith(normalizedRoot); } diff --git a/apps/kimi-code/src/feedback/archive.ts b/apps/kimi-code/src/feedback/archive.ts index 439f68d1be..c1de50a340 100644 --- a/apps/kimi-code/src/feedback/archive.ts +++ b/apps/kimi-code/src/feedback/archive.ts @@ -1,5 +1,5 @@ import { mkdir, mkdtemp, readdir, rm, stat } from 'node:fs/promises'; -import { dirname, join } from 'node:path'; +import { basename, dirname, join } from 'node:path'; import { getCacheDir } from '../utils/paths'; @@ -64,7 +64,8 @@ async function createArchivePath(filename: string): Promise { const root = join(getCacheDir(), 'feedback-uploads'); await mkdir(root, { recursive: true }); const dir = await mkdtemp(join(root, 'upload-')); - return join(dir, filename); + const safeFilename = basename(filename).replace(/[^a-zA-Z0-9._-]/g, ''); + return join(dir, safeFilename); } function archivePathCleanupDir(archivePath: string): string {