@dollhousemcp/mcp-server
Version:
DollhouseMCP - A Model Context Protocol (MCP) server that enables dynamic AI persona management from markdown files, allowing Claude and other compatible AI assistants to activate and switch between different behavioral personas.
94 lines • 14.2 kB
JavaScript
import * as child_process from 'child_process';
import { RegexValidator } from './regexValidator.js';
import { SECURITY_LIMITS } from './constants.js';
const ALLOWED_COMMANDS = {
// SECURITY FIX: Added 'clone' command to git allowlist
// Previously: npm installation feature would fail with "Argument not allowed: clone"
// Now: git clone command is allowed for npm installation functionality
git: ['pull', 'status', 'log', 'rev-parse', 'branch', 'checkout', 'fetch', 'clone', '--abbrev-ref', 'HEAD', '--porcelain'],
npm: ['install', 'run', 'audit', 'ci', '--version', 'build'],
node: ['--version'],
npx: ['--version']
};
export class CommandValidator {
static sanitizeCommand(cmd, args) {
if (!ALLOWED_COMMANDS[cmd]) {
throw new Error(`Command not allowed: ${cmd}`);
}
const allowedArgs = ALLOWED_COMMANDS[cmd];
for (const arg of args) {
// Check if it's in allowed list or matches safe pattern
if (!allowedArgs.includes(arg) && !this.isSafeArgument(arg)) {
throw new Error(`Argument not allowed: ${arg}`);
}
}
}
static isSafeArgument(arg) {
// Allow alphanumeric, dash, underscore, dot, and forward slash
return RegexValidator.validate(arg, /^[a-zA-Z0-9\-_./]+$/, { maxLength: SECURITY_LIMITS.MAX_COMMAND_ARG_LENGTH });
}
static async secureExec(command, args, options) {
this.sanitizeCommand(command, args);
const safeOptions = {
...options,
stdio: ['ignore', 'pipe', 'pipe'],
env: {
// SEC-03: Explicit allowlist instead of spreading full process.env
PATH: '/usr/bin:/bin:/usr/local/bin',
HOME: process.env.HOME,
USER: process.env.USER,
LANG: process.env.LANG,
TERM: process.env.TERM,
// Git operations need these
GIT_AUTHOR_NAME: process.env.GIT_AUTHOR_NAME,
GIT_AUTHOR_EMAIL: process.env.GIT_AUTHOR_EMAIL,
GIT_COMMITTER_NAME: process.env.GIT_COMMITTER_NAME,
GIT_COMMITTER_EMAIL: process.env.GIT_COMMITTER_EMAIL,
// npm operations
NODE_PATH: process.env.NODE_PATH,
npm_config_cache: process.env.npm_config_cache,
},
cwd: options?.cwd || process.cwd(),
timeout: options?.timeout || 30000 // 30 second default
};
return new Promise((resolve, reject) => {
const proc = child_process.spawn(command, args, safeOptions);
let stdout = '';
let stderr = '';
let timeoutHandle;
let isCompleted = false;
// Helper to safely resolve/reject only once
const complete = (fn) => {
if (!isCompleted) {
isCompleted = true;
if (timeoutHandle) {
clearTimeout(timeoutHandle);
}
fn();
}
};
// Handle timeout
if (options?.timeout) {
timeoutHandle = setTimeout(() => {
proc.kill('SIGTERM');
complete(() => reject(new Error(`Command timed out after ${options.timeout}ms`)));
}, options.timeout);
timeoutHandle.unref();
}
proc.stdout?.on('data', (data) => stdout += data);
proc.stderr?.on('data', (data) => stderr += data);
proc.on('exit', (code) => {
if (code === 0) {
complete(() => resolve(stdout.trim()));
}
else {
complete(() => reject(new Error(`Command failed (${code}): ${stderr}`)));
}
});
proc.on('error', (error) => {
complete(() => reject(error instanceof Error ? error : new Error(String(error))));
});
});
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29tbWFuZFZhbGlkYXRvci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9zZWN1cml0eS9jb21tYW5kVmFsaWRhdG9yLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxhQUFhLE1BQU0sZUFBZSxDQUFDO0FBQy9DLE9BQU8sRUFBRSxjQUFjLEVBQUUsTUFBTSxxQkFBcUIsQ0FBQztBQUNyRCxPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0sZ0JBQWdCLENBQUM7QUFFakQsTUFBTSxnQkFBZ0IsR0FBNkI7SUFDakQsdURBQXVEO0lBQ3ZELHFGQUFxRjtJQUNyRix1RUFBdUU7SUFDdkUsR0FBRyxFQUFFLENBQUMsTUFBTSxFQUFFLFFBQVEsRUFBRSxLQUFLLEVBQUUsV0FBVyxFQUFFLFFBQVEsRUFBRSxVQUFVLEVBQUUsT0FBTyxFQUFFLE9BQU8sRUFBRSxjQUFjLEVBQUUsTUFBTSxFQUFFLGFBQWEsQ0FBQztJQUMxSCxHQUFHLEVBQUUsQ0FBQyxTQUFTLEVBQUUsS0FBSyxFQUFFLE9BQU8sRUFBRSxJQUFJLEVBQUUsV0FBVyxFQUFFLE9BQU8sQ0FBQztJQUM1RCxJQUFJLEVBQUUsQ0FBQyxXQUFXLENBQUM7SUFDbkIsR0FBRyxFQUFFLENBQUMsV0FBVyxDQUFDO0NBQ25CLENBQUM7QUFFRixNQUFNLE9BQU8sZ0JBQWdCO0lBQzNCLE1BQU0sQ0FBQyxlQUFlLENBQUMsR0FBVyxFQUFFLElBQWM7UUFDaEQsSUFBSSxDQUFDLGdCQUFnQixDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUM7WUFDM0IsTUFBTSxJQUFJLEtBQUssQ0FBQyx3QkFBd0IsR0FBRyxFQUFFLENBQUMsQ0FBQztRQUNqRCxDQUFDO1FBRUQsTUFBTSxXQUFXLEdBQUcsZ0JBQWdCLENBQUMsR0FBRyxDQUFDLENBQUM7UUFDMUMsS0FBSyxNQUFNLEdBQUcsSUFBSSxJQUFJLEVBQUUsQ0FBQztZQUN2Qix3REFBd0Q7WUFDeEQsSUFBSSxDQUFDLFdBQVcs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Q0FBQyxDQUFDO0lBQ0wsQ0FBQztDQUNGIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0ICogYXMgY2hpbGRfcHJvY2VzcyBmcm9tICdjaGlsZF9wcm9jZXNzJztcbmltcG9ydCB7IFJlZ2V4VmFsaWRhdG9yIH0gZnJvbSAnLi9yZWdleFZhbGlkYXRvci5qcyc7XG5pbXBvcnQgeyBTRUNVUklUWV9MSU1JVFMgfSBmcm9tICcuL2NvbnN0YW50cy5qcyc7XG5cbmNvbnN0IEFMTE9XRURfQ09NTUFORFM6IFJlY29yZDxzdHJpbmcsIHN0cmluZ1tdPiA9IHtcbiAgLy8gU0VDVVJJVFkgRklYOiBBZGRlZCAnY2xvbmUnIGNvbW1hbmQgdG8gZ2l0IGFsbG93bGlzdFxuICAvLyBQcmV2aW91c2x5OiBucG0gaW5zdGFsbGF0aW9uIGZlYXR1cmUgd291bGQgZmFpbCB3aXRoIFwiQXJndW1lbnQgbm90IGFsbG93ZWQ6IGNsb25lXCJcbiAgLy8gTm93OiBnaXQgY2xvbmUgY29tbWFuZCBpcyBhbGxvd2VkIGZvciBucG0gaW5zdGFsbGF0aW9uIGZ1bmN0aW9uYWxpdHlcbiAgZ2l0OiBbJ3B1bGwnLCAnc3RhdHVzJywgJ2xvZycsICdyZXYtcGFyc2UnLCAnYnJhbmNoJywgJ2NoZWNrb3V0JywgJ2ZldGNoJywgJ2Nsb25lJywgJy0tYWJicmV2LXJlZicsICdIRUFEJywgJy0tcG9yY2VsYWluJ10sXG4gIG5wbTogWydpbnN0YWxsJywgJ3J1bicsICdhdWRpdCcsICdjaScsICctLXZlcnNpb24nLCAnYnVpbGQnXSxcbiAgbm9kZTogWyctLXZlcnNpb24nXSxcbiAgbnB4OiBbJy0tdmVyc2lvbiddXG59O1xuXG5leHBvcnQgY2xhc3MgQ29tbWFuZFZhbGlkYXRvciB7XG4gIHN0YXRpYyBzYW5pdGl6ZUNvbW1hbmQoY21kOiBzdHJpbmcsIGFyZ3M6IHN0cmluZ1tdKTogdm9pZCB7XG4gICAgaWYgKCFBTExPV0VEX0NPTU1BTkRTW2NtZF0pIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcihgQ29tbWFuZCBub3QgYWxsb3dlZDogJHtjbWR9YCk7XG4gICAgfVxuICAgIFxuICAgIGNvbnN0IGFsbG93ZWRBcmdzID0gQUxMT1dFRF9DT01NQU5EU1tjbWRdO1xuICAgIGZvciAoY29uc3QgYXJnIG9mIGFyZ3MpIHtcbiAgICAgIC8vIENoZWNrIGlmIGl0J3MgaW4gYWxsb3dlZCBsaXN0IG9yIG1hdGNoZXMgc2FmZSBwYXR0ZXJuXG4gICAgICBpZiAoIWFsbG93ZWRBcmdzLmluY2x1ZGVzKGFyZykgJiYgIXRoaXMuaXNTYWZlQXJndW1lbnQoYXJnKSkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYEFyZ3VtZW50IG5vdCBhbGxvd2VkOiAke2FyZ31gKTtcbiAgICAgIH1cbiAgICB9XG4gIH1cblxuICBwcml2YXRlIHN0YXRpYyBpc1NhZmVBcmd1bWVudChhcmc6IHN0cmluZyk6IGJvb2xlYW4ge1xuICAgIC8vIEFsbG93IGFscGhhbnVtZXJpYywgZGFzaCwgdW5kZXJzY29yZSwgZG90LCBhbmQgZm9yd2FyZCBzbGFzaFxuICAgIHJldHVybiBSZWdleFZhbGlkYXRvci52YWxpZGF0ZShhcmcsIC9eW2EtekEtWjAtOVxcLV8uL10rJC8sIHsgbWF4TGVuZ3RoOiBTRUNVUklUWV9MSU1JVFMuTUFYX0NPTU1BTkRfQVJHX0xFTkdUSCB9KTtcbiAgfVxuXG4gIHN0YXRpYyBhc3luYyBzZWN1cmVFeGVjKGNvbW1hbmQ6IHN0cmluZywgYXJnczogc3RyaW5nW10sIG9wdGlvbnM/OiBjaGlsZF9wcm9jZXNzLlNwYXduT3B0aW9ucyk6IFByb21pc2U8c3RyaW5nPiB7XG4gICAgdGhpcy5zYW5pdGl6ZUNvbW1hbmQoY29tbWFuZCwgYXJncyk7XG4gICAgXG4gICAgY29uc3Qgc2FmZU9wdGlvbnM6IGNoaWxkX3Byb2Nlc3MuU3Bhd25PcHRpb25zID0ge1xuICAgICAgLi4ub3B0aW9ucyxcbiAgICAgIHN0ZGlvOiBbJ2lnbm9yZScsICdwaXBlJywgJ3BpcGUnXSxcbiAgICAgIGVudjoge1xuICAgICAgICAvLyBTRUMtMDM6IEV4cGxpY2l0IGFsbG93bGlzdCBpbnN0ZWFkIG9mIHNwcmVhZGluZyBmdWxsIHByb2Nlc3MuZW52XG4gICAgICAgIFBBVEg6ICcvdXNyL2JpbjovYmluOi91c3IvbG9jYWwvYmluJyxcbiAgICAgICAgSE9NRTogcHJvY2Vzcy5lbnYuSE9NRSxcbiAgICAgICAgVVNFUjogcHJvY2Vzcy5lbnYuVVNFUixcbiAgICAgICAgTEFORzogcHJvY2Vzcy5lbnYuTEFORyxcbiAgICAgICAgVEVSTTogcHJvY2Vzcy5lbnYuVEVSTSxcbiAgICAgICAgLy8gR2l0IG9wZXJhdGlvbnMgbmVlZCB0aGVzZVxuICAgICAgICBHSVRfQVVUSE9SX05BTUU6IHByb2Nlc3MuZW52LkdJVF9BVVRIT1JfTkFNRSxcbiAgICAgICAgR0lUX0FVVEhPUl9FTUFJTDogcHJvY2Vzcy5lbnYuR0lUX0FVVEhPUl9FTUFJTCxcbiAgICAgICAgR0lUX0NPTU1JVFRFUl9OQU1FOiBwcm9jZXNzLmVudi5HSVRfQ09NTUlUVEVSX05BTUUsXG4gICAgICAgIEdJVF9DT01NSVRURVJfRU1BSUw6IHByb2Nlc3MuZW52LkdJVF9DT01NSVRURVJfRU1BSUwsXG4gICAgICAgIC8vIG5wbSBvcGVyYXRpb25zXG4gICAgICAgIE5PREVfUEFUSDogcHJvY2Vzcy5lbnYuTk9ERV9QQVRILFxuICAgICAgICBucG1fY29uZmlnX2NhY2hlOiBwcm9jZXNzLmVudi5ucG1fY29uZmlnX2NhY2hlLFxuICAgICAgfSxcbiAgICAgIGN3ZDogb3B0aW9ucz8uY3dkIHx8IHByb2Nlc3MuY3dkKCksXG4gICAgICB0aW1lb3V0OiBvcHRpb25zPy50aW1lb3V0IHx8IDMwMDAwIC8vIDMwIHNlY29uZCBkZWZhdWx0XG4gICAgfTtcbiAgICBcbiAgICByZXR1cm4gbmV3IFByb21pc2UoKHJlc29sdmUsIHJlamVjdCkgPT4ge1xuICAgICAgY29uc3QgcHJvYyA9IGNoaWxkX3Byb2Nlc3Muc3Bhd24oY29tbWFuZCwgYXJncywgc2FmZU9wdGlvbnMpO1xuICAgICAgXG4gICAgICBsZXQgc3Rkb3V0ID0gJyc7XG4gICAgICBsZXQgc3RkZXJyID0gJyc7XG4gICAgICBsZXQgdGltZW91dEhhbmRsZTogTm9kZUpTLlRpbWVvdXQgfCB1bmRlZmluZWQ7XG4gICAgICBsZXQgaXNDb21wbGV0ZWQgPSBmYWxzZTtcbiAgICAgIFxuICAgICAgLy8gSGVscGVyIHRvIHNhZmVseSByZXNvbHZlL3JlamVjdCBvbmx5IG9uY2VcbiAgICAgIGNvbnN0IGNvbXBsZXRlID0gKGZuOiAoKSA9PiB2b2lkKSA9PiB7XG4gICAgICAgIGlmICghaXNDb21wbGV0ZWQpIHtcbiAgICAgICAgICBpc0NvbXBsZXRlZCA9IHRydWU7XG4gICAgICAgICAgaWYgKHRpbWVvdXRIYW5kbGUpIHtcbiAgICAgICAgICAgIGNsZWFyVGltZW91dCh0aW1lb3V0SGFuZGxlKTtcbiAgICAgICAgICB9XG4gICAgICAgICAgZm4oKTtcbiAgICAgICAgfVxuICAgICAgfTtcbiAgICAgIFxuICAgICAgLy8gSGFuZGxlIHRpbWVvdXRcbiAgICAgIGlmIChvcHRpb25zPy50aW1lb3V0KSB7XG4gICAgICAgIHRpbWVvdXRIYW5kbGUgPSBzZXRUaW1lb3V0KCgpID0+IHtcbiAgICAgICAgICBwcm9jLmtpbGwoJ1NJR1RFUk0nKTtcbiAgICAgICAgICBjb21wbGV0ZSgoKSA9PiByZWplY3QobmV3IEVycm9yKGBDb21tYW5kIHRpbWVkIG91dCBhZnRlciAke29wdGlvbnMudGltZW91dH1tc2ApKSk7XG4gICAgICAgIH0sIG9wdGlvbnMudGltZW91dCk7XG4gICAgICAgIHRpbWVvdXRIYW5kbGUudW5yZWYoKTtcbiAgICAgIH1cbiAgICAgIFxuICAgICAgcHJvYy5zdGRvdXQ/Lm9uKCdkYXRhJywgKGRhdGEpID0+IHN0ZG91dCArPSBkYXRhKTtcbiAgICAgIHByb2Muc3RkZXJyPy5vbignZGF0YScsIChkYXRhKSA9PiBzdGRlcnIgKz0gZGF0YSk7XG4gICAgICBcbiAgICAgIHByb2Mub24oJ2V4aXQnLCAoY29kZSkgPT4ge1xuICAgICAgICBpZiAoY29kZSA9PT0gMCkge1xuICAgICAgICAgIGNvbXBsZXRlKCgpID0+IHJlc29sdmUoc3Rkb3V0LnRyaW0oKSkpO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgIGNvbXBsZXRlKCgpID0+IHJlamVjdChuZXcgRXJyb3IoYENvbW1hbmQgZmFpbGVkICgke2NvZGV9KTogJHtzdGRlcnJ9YCkpKTtcbiAgICAgICAgfVxuICAgICAgfSk7XG4gICAgICBcbiAgICAgIHByb2Mub24oJ2Vycm9yJywgKGVycm9yKSA9PiB7XG4gICAgICAgIGNvbXBsZXRlKCgpID0+IHJlamVjdChlcnJvciBpbnN0YW5jZW9mIEVycm9yID8gZXJyb3IgOiBuZXcgRXJyb3IoU3RyaW5nKGVycm9yKSkpKTtcbiAgICAgIH0pO1xuICAgIH0pO1xuICB9XG59Il19