From 02f7c3c9d02e1869789e1c2a3c52ab44f65ada9d Mon Sep 17 00:00:00 2001 From: Kamran Khalid <167863443+kamran-khalid-v9@users.noreply.github.com> Date: Mon, 16 Feb 2026 05:29:08 +0000 Subject: [PATCH] fix(security): validate and restrict /api/instructions operation and topic params (CWE-22, CWE-1321) (#986) --- src/services/server/Server.ts | 17 ++++++++++++++++- src/services/server/allowed-constants.ts | 15 +++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 src/services/server/allowed-constants.ts diff --git a/src/services/server/Server.ts b/src/services/server/Server.ts index b5f68016..e81c8b68 100644 --- a/src/services/server/Server.ts +++ b/src/services/server/Server.ts @@ -13,6 +13,7 @@ import express, { Request, Response, Application } from 'express'; import http from 'http'; import * as fs from 'fs'; import path from 'path'; +import { ALLOWED_OPERATIONS, ALLOWED_TOPICS } from './allowed-constants.js'; import { logger } from '../../utils/logger.js'; import { createMiddleware, summarizeRequestBody, requireLocalhost } from './Middleware.js'; import { errorHandler, notFoundHandler } from './ErrorHandler.js'; @@ -199,11 +200,25 @@ export class Server { const topic = (req.query.topic as string) || 'all'; const operation = req.query.operation as string | undefined; + // Validate topic + if (topic && !ALLOWED_TOPICS.includes(topic)) { + return res.status(400).json({ error: 'Invalid topic' }); + } + try { let content: string; if (operation) { - const operationPath = path.join(__dirname, '../skills/mem-search/operations', `${operation}.md`); + // Validate operation + if (!ALLOWED_OPERATIONS.includes(operation)) { + return res.status(400).json({ error: 'Invalid operation' }); + } + // Path boundary check + const OPERATIONS_BASE_DIR = path.resolve(__dirname, '../skills/mem-search/operations'); + const operationPath = path.resolve(OPERATIONS_BASE_DIR, `${operation}.md`); + if (!operationPath.startsWith(OPERATIONS_BASE_DIR + path.sep)) { + return res.status(400).json({ error: 'Invalid request' }); + } content = await fs.promises.readFile(operationPath, 'utf-8'); } else { const skillPath = path.join(__dirname, '../skills/mem-search/SKILL.md'); diff --git a/src/services/server/allowed-constants.ts b/src/services/server/allowed-constants.ts new file mode 100644 index 00000000..6be29a1d --- /dev/null +++ b/src/services/server/allowed-constants.ts @@ -0,0 +1,15 @@ +// Allowed values for /api/instructions security +export const ALLOWED_OPERATIONS = [ + 'search', + 'context', + 'summarize', + 'import', + 'export' +]; + +export const ALLOWED_TOPICS = [ + 'workflow', + 'search_params', + 'examples', + 'all' +];