@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.
96 lines • 10.9 kB
JavaScript
/**
* Security utility functions for preventing prototype pollution and other vulnerabilities
*/
/**
* List of property names that should never be used as object keys
* to prevent prototype pollution attacks
*/
export const FORBIDDEN_KEYS = ['__proto__', 'constructor', 'prototype'];
/**
* Validates that a property key is safe to use (not a prototype pollution vector)
* @param key The property key to validate
* @param context Optional context for the error message (e.g., "path" or "section")
* @throws Error if the key is forbidden
*/
export function validatePropertyKey(key, context = 'property') {
if (FORBIDDEN_KEYS.includes(key)) {
throw new Error(`Forbidden property in ${context}: ${key}`);
}
}
/**
* Validates all keys in a dot-notation path
* @param path Dot-notation path (e.g., "user.settings.theme")
* @param context Optional context for the error message (e.g., "path" or "section")
* @throws Error if any key in the path is forbidden
*/
export function validatePropertyPath(path, context = 'path') {
const keys = path.split('.');
for (const key of keys) {
validatePropertyKey(key, context);
}
}
/**
* Safely sets a property on an object using Object.defineProperty
* to prevent prototype pollution
* @param target The target object
* @param key The property key
* @param value The value to set
*/
export function safeSetProperty(target, key, value) {
validatePropertyKey(key);
Object.defineProperty(target, key, {
value: value,
writable: true,
enumerable: true,
configurable: true
});
}
/**
* Creates a new object without prototype chain (using Object.create(null))
* This prevents prototype pollution attacks on newly created objects
* @returns A new object with no prototype
*/
export function createSafeObject() {
return Object.create(null);
}
/**
* Safely checks if an object has a property without traversing the prototype chain
* @param target The target object
* @param key The property key to check
* @returns true if the object has the property, false otherwise
*/
export function safeHasOwnProperty(target, key) {
return Object.prototype.hasOwnProperty.call(target, key);
}
/**
* Safely navigates an object path, creating intermediate objects as needed
* All created objects are prototype-less to prevent pollution
* @param root The root object to navigate
* @param path Dot-notation path (e.g., "user.settings.theme")
* @returns The final object in the path where the value should be set
*/
export function safeNavigateObject(root, path) {
validatePropertyPath(path);
const keys = path.split('.');
let current = root;
// Navigate to the parent object, creating safe objects as needed
for (let i = 0; i < keys.length - 1; i++) {
const key = keys[i];
if (!safeHasOwnProperty(current, key)) {
current[key] = createSafeObject();
}
current = current[key];
}
return { parent: current, lastKey: keys[keys.length - 1] };
}
/**
* Safely sets a value at a dot-notation path in an object
* @param root The root object
* @param path Dot-notation path (e.g., "user.settings.theme")
* @param value The value to set
*/
export function safeSetPath(root, path, value) {
const { parent, lastKey } = safeNavigateObject(root, path);
safeSetProperty(parent, lastKey, value);
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VjdXJpdHlVdGlscy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy91dGlscy9zZWN1cml0eVV0aWxzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBRUg7OztHQUdHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sY0FBYyxHQUFHLENBQUMsV0FBVyxFQUFFLGFBQWEsRUFBRSxXQUFXLENBQVUsQ0FBQztBQUVqRjs7Ozs7R0FLRztBQUNILE1BQU0sVUFBVSxtQkFBbUIsQ0FBQyxHQUFXLEVBQUUsVUFBa0IsVUFBVTtJQUMzRSxJQUFJLGNBQWMsQ0FBQyxRQUFRLENBQUMsR0FBVSxDQUFDLEVBQUUsQ0FBQztRQUN4QyxNQUFNLElBQUksS0FBSyxDQUFDLHlCQUF5QixPQUFPLEtBQUssR0FBRyxFQUFFLENBQUMsQ0FBQztJQUM5RCxDQUFDO0FBQ0gsQ0FBQztBQUVEOzs7OztHQUtHO0FBQ0gsTUFBTSxVQUFVLG9CQUFvQixDQUFDLElBQVksRUFBRSxVQUFrQixNQUFNO0lBQ3pFLE1BQU0sSUFBSSxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUM7SUFDN0IsS0FBSyxNQUFNLEdBQUcsSUFBSSxJQUFJLEVBQUUsQ0FBQztRQUN2QixtQkFBbUIsQ0FBQyxHQUFHLEVBQUUsT0FBTyxDQUFDLENBQUM7SUFDcEMsQ0FBQztBQUNILENBQUM7QUFFRDs7Ozs7O0dBTUc7QUFDSCxNQUFNLFVBQVUsZUFBZSxDQUFDLE1BQVcsRUFBRSxHQUFXLEVBQUUsS0FBVTtJQUNsRSxtQkFBbUIsQ0FBQyxHQUFHLENBQUMsQ0FBQztJQUV6QixNQUFNLENBQUMsY0FBYyxDQUFDLE1BQU0sRUFBRSxHQUFHLEVBQUU7UUFDakMsS0FBSyxFQUFFLEtBQUs7UUFDWixRQUFRLEVBQUUsSUFBSTtRQUNkLFVBQVUsRUFBRSxJQUFJO1FBQ2hCLFlBQVksRUFBRSxJQUFJO0tBQ25CLENBQUMsQ0FBQztBQUNMLENBQUM7QUFFRDs7OztHQUlHO0FBQ0gsTUFBTSxVQUFVLGdCQUFnQjtJQUM5QixPQUFPLE1BQU0sQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLENBQUM7QUFDN0IsQ0FBQztBQUVEOzs7OztHQUtHO0FBQ0gsTUFBTSxVQUFVLGtCQUFrQixDQUFDLE1BQVcsRUFBRSxHQUFXO0lBQ3pELE9BQU8sTUFBTSxDQUFDLFNBQVMsQ0FBQyxjQUFjLENBQUMsSUFBSSxDQUFDLE1BQU0sRUFBRSxHQUFHLENBQUMsQ0FBQztBQUMzRCxDQUFDO0FBRUQ7Ozs7OztHQU1HO0FBQ0gsTUFBTSxVQUFVLGtCQUFrQixDQUFDLElBQVMsRUFBRSxJQUFZO0lBQ3hELG9CQUFvQixDQUFDLElBQUksQ0FBQyxDQUFDO0lBRTNCLE1BQU0sSUFBSSxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUM7SUFDN0IsSUFBSSxPQUFPLEdBQUcsSUFBSSxDQUFDO0lBRW5CLGlFQUFpRTtJQUNqRSxLQUFLLElBQUksQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLEdBQUcsSUFBSSxDQUFDLE1BQU0sR0FBRyxDQUFDLEVBQUUsQ0FBQyxFQUFFLEVBQUUsQ0FBQztRQUN6QyxNQUFNLEdBQUcsR0FBRyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUM7UUFDcEIsSUFBSSxDQUFDLGtCQUFrQixDQUFDLE9BQU8sRUFBRSxHQUFHLENBQUMsRUFBRSxDQUFDO1lBQ3RDLE9BQU8sQ0FBQyxHQUFHLENBQUMsR0FBRyxnQkFBZ0IsRUFBRSxDQUFDO1FBQ3BDLENBQUM7UUFDRCxPQUFPLEdBQUcsT0FBTyxDQUFDLEdBQUcsQ0FBQyxDQUFDO0lBQ3pCLENBQUM7SUFFRCxPQUFPLEVBQUUsTUFBTSxFQUFFLE9BQU8sRUFBRSxPQUFPLEVBQUUsSUFBSSxDQUFDLElBQUksQ0FBQyxNQUFNLEdBQUcsQ0FBQyxDQUFDLEVBQUUsQ0FBQztBQUM3RCxDQUFDO0FBRUQ7Ozs7O0dBS0c7QUFDSCxNQUFNLFVBQVUsV0FBVyxDQUFDLElBQVMsRUFBRSxJQUFZLEVBQUUsS0FBVTtJQUM3RCxNQUFNLEVBQUUsTUFBTSxFQUFFLE9BQU8sRUFBRSxHQUFHLGtCQUFrQixDQUFDLElBQUksRUFBRSxJQUFJLENBQUMsQ0FBQztJQUMzRCxlQUFlLENBQUMsTUFBTSxFQUFFLE9BQU8sRUFBRSxLQUFLLENBQUMsQ0FBQztBQUMxQyxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBTZWN1cml0eSB1dGlsaXR5IGZ1bmN0aW9ucyBmb3IgcHJldmVudGluZyBwcm90b3R5cGUgcG9sbHV0aW9uIGFuZCBvdGhlciB2dWxuZXJhYmlsaXRpZXNcbiAqL1xuXG4vKipcbiAqIExpc3Qgb2YgcHJvcGVydHkgbmFtZXMgdGhhdCBzaG91bGQgbmV2ZXIgYmUgdXNlZCBhcyBvYmplY3Qga2V5c1xuICogdG8gcHJldmVudCBwcm90b3R5cGUgcG9sbHV0aW9uIGF0dGFja3NcbiAqL1xuZXhwb3J0IGNvbnN0IEZPUkJJRERFTl9LRVlTID0gWydfX3Byb3RvX18nLCAnY29uc3RydWN0b3InLCAncHJvdG90eXBlJ10gYXMgY29uc3Q7XG5cbi8qKlxuICogVmFsaWRhdGVzIHRoYXQgYSBwcm9wZXJ0eSBrZXkgaXMgc2FmZSB0byB1c2UgKG5vdCBhIHByb3RvdHlwZSBwb2xsdXRpb24gdmVjdG9yKVxuICogQHBhcmFtIGtleSBUaGUgcHJvcGVydHkga2V5IHRvIHZhbGlkYXRlXG4gKiBAcGFyYW0gY29udGV4dCBPcHRpb25hbCBjb250ZXh0IGZvciB0aGUgZXJyb3IgbWVzc2FnZSAoZS5nLiwgXCJwYXRoXCIgb3IgXCJzZWN0aW9uXCIpXG4gKiBAdGhyb3dzIEVycm9yIGlmIHRoZSBrZXkgaXMgZm9yYmlkZGVuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiB2YWxpZGF0ZVByb3BlcnR5S2V5KGtleTogc3RyaW5nLCBjb250ZXh0OiBzdHJpbmcgPSAncHJvcGVydHknKTogdm9pZCB7XG4gIGlmIChGT1JCSURERU5fS0VZUy5pbmNsdWRlcyhrZXkgYXMgYW55KSkge1xuICAgIHRocm93IG5ldyBFcnJvcihgRm9yYmlkZGVuIHByb3BlcnR5IGluICR7Y29udGV4dH06ICR7a2V5fWApO1xuICB9XG59XG5cbi8qKlxuICogVmFsaWRhdGVzIGFsbCBrZXlzIGluIGEgZG90LW5vdGF0aW9uIHBhdGhcbiAqIEBwYXJhbSBwYXRoIERvdC1ub3RhdGlvbiBwYXRoIChlLmcuLCBcInVzZXIuc2V0dGluZ3MudGhlbWVcIilcbiAqIEBwYXJhbSBjb250ZXh0IE9wdGlvbmFsIGNvbnRleHQgZm9yIHRoZSBlcnJvciBtZXNzYWdlIChlLmcuLCBcInBhdGhcIiBvciBcInNlY3Rpb25cIilcbiAqIEB0aHJvd3MgRXJyb3IgaWYgYW55IGtleSBpbiB0aGUgcGF0aCBpcyBmb3JiaWRkZW5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHZhbGlkYXRlUHJvcGVydHlQYXRoKHBhdGg6IHN0cmluZywgY29udGV4dDogc3RyaW5nID0gJ3BhdGgnKTogdm9pZCB7XG4gIGNvbnN0IGtleXMgPSBwYXRoLnNwbGl0KCcuJyk7XG4gIGZvciAoY29uc3Qga2V5IG9mIGtleXMpIHtcbiAgICB2YWxpZGF0ZVByb3BlcnR5S2V5KGtleSwgY29udGV4dCk7XG4gIH1cbn1cblxuLyoqXG4gKiBTYWZlbHkgc2V0cyBhIHByb3BlcnR5IG9uIGFuIG9iamVjdCB1c2luZyBPYmplY3QuZGVmaW5lUHJvcGVydHlcbiAqIHRvIHByZXZlbnQgcHJvdG90eXBlIHBvbGx1dGlvblxuICogQHBhcmFtIHRhcmdldCBUaGUgdGFyZ2V0IG9iamVjdFxuICogQHBhcmFtIGtleSBUaGUgcHJvcGVydHkga2V5XG4gKiBAcGFyYW0gdmFsdWUgVGhlIHZhbHVlIHRvIHNldFxuICovXG5leHBvcnQgZnVuY3Rpb24gc2FmZVNldFByb3BlcnR5KHRhcmdldDogYW55LCBrZXk6IHN0cmluZywgdmFsdWU6IGFueSk6IHZvaWQge1xuICB2YWxpZGF0ZVByb3BlcnR5S2V5KGtleSk7XG5cbiAgT2JqZWN0LmRlZmluZVByb3BlcnR5KHRhcmdldCwga2V5LCB7XG4gICAgdmFsdWU6IHZhbHVlLFxuICAgIHdyaXRhYmxlOiB0cnVlLFxuICAgIGVudW1lcmFibGU6IHRydWUsXG4gICAgY29uZmlndXJhYmxlOiB0cnVlXG4gIH0pO1xufVxuXG4vKipcbiAqIENyZWF0ZXMgYSBuZXcgb2JqZWN0IHdpdGhvdXQgcHJvdG90eXBlIGNoYWluICh1c2luZyBPYmplY3QuY3JlYXRlKG51bGwpKVxuICogVGhpcyBwcmV2ZW50cyBwcm90b3R5cGUgcG9sbHV0aW9uIGF0dGFja3Mgb24gbmV3bHkgY3JlYXRlZCBvYmplY3RzXG4gKiBAcmV0dXJucyBBIG5ldyBvYmplY3Qgd2l0aCBubyBwcm90b3R5cGVcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGNyZWF0ZVNhZmVPYmplY3QoKTogUmVjb3JkPHN0cmluZywgYW55PiB7XG4gIHJldHVybiBPYmplY3QuY3JlYXRlKG51bGwpO1xufVxuXG4vKipcbiAqIFNhZmVseSBjaGVja3MgaWYgYW4gb2JqZWN0IGhhcyBhIHByb3BlcnR5IHdpdGhvdXQgdHJhdmVyc2luZyB0aGUgcHJvdG90eXBlIGNoYWluXG4gKiBAcGFyYW0gdGFyZ2V0IFRoZSB0YXJnZXQgb2JqZWN0XG4gKiBAcGFyYW0ga2V5IFRoZSBwcm9wZXJ0eSBrZXkgdG8gY2hlY2tcbiAqIEByZXR1cm5zIHRydWUgaWYgdGhlIG9iamVjdCBoYXMgdGhlIHByb3BlcnR5LCBmYWxzZSBvdGhlcndpc2VcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHNhZmVIYXNPd25Qcm9wZXJ0eSh0YXJnZXQ6IGFueSwga2V5OiBzdHJpbmcpOiBib29sZWFuIHtcbiAgcmV0dXJuIE9iamVjdC5wcm90b3R5cGUuaGFzT3duUHJvcGVydHkuY2FsbCh0YXJnZXQsIGtleSk7XG59XG5cbi8qKlxuICogU2FmZWx5IG5hdmlnYXRlcyBhbiBvYmplY3QgcGF0aCwgY3JlYXRpbmcgaW50ZXJtZWRpYXRlIG9iamVjdHMgYXMgbmVlZGVkXG4gKiBBbGwgY3JlYXRlZCBvYmplY3RzIGFyZSBwcm90b3R5cGUtbGVzcyB0byBwcmV2ZW50IHBvbGx1dGlvblxuICogQHBhcmFtIHJvb3QgVGhlIHJvb3Qgb2JqZWN0IHRvIG5hdmlnYXRlXG4gKiBAcGFyYW0gcGF0aCBEb3Qtbm90YXRpb24gcGF0aCAoZS5nLiwgXCJ1c2VyLnNldHRpbmdzLnRoZW1lXCIpXG4gKiBAcmV0dXJucyBUaGUgZmluYWwgb2JqZWN0IGluIHRoZSBwYXRoIHdoZXJlIHRoZSB2YWx1ZSBzaG91bGQgYmUgc2V0XG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBzYWZlTmF2aWdhdGVPYmplY3Qocm9vdDogYW55LCBwYXRoOiBzdHJpbmcpOiBhbnkge1xuICB2YWxpZGF0ZVByb3BlcnR5UGF0aChwYXRoKTtcblxuICBjb25zdCBrZXlzID0gcGF0aC5zcGxpdCgnLicpO1xuICBsZXQgY3VycmVudCA9IHJvb3Q7XG5cbiAgLy8gTmF2aWdhdGUgdG8gdGhlIHBhcmVudCBvYmplY3QsIGNyZWF0aW5nIHNhZmUgb2JqZWN0cyBhcyBuZWVkZWRcbiAgZm9yIChsZXQgaSA9IDA7IGkgPCBrZXlzLmxlbmd0aCAtIDE7IGkrKykge1xuICAgIGNvbnN0IGtleSA9IGtleXNbaV07XG4gICAgaWYgKCFzYWZlSGFzT3duUHJvcGVydHkoY3VycmVudCwga2V5KSkge1xuICAgICAgY3VycmVudFtrZXldID0gY3JlYXRlU2FmZU9iamVjdCgpO1xuICAgIH1cbiAgICBjdXJyZW50ID0gY3VycmVudFtrZXldO1xuICB9XG5cbiAgcmV0dXJuIHsgcGFyZW50OiBjdXJyZW50LCBsYXN0S2V5OiBrZXlzW2tleXMubGVuZ3RoIC0gMV0gfTtcbn1cblxuLyoqXG4gKiBTYWZlbHkgc2V0cyBhIHZhbHVlIGF0IGEgZG90LW5vdGF0aW9uIHBhdGggaW4gYW4gb2JqZWN0XG4gKiBAcGFyYW0gcm9vdCBUaGUgcm9vdCBvYmplY3RcbiAqIEBwYXJhbSBwYXRoIERvdC1ub3RhdGlvbiBwYXRoIChlLmcuLCBcInVzZXIuc2V0dGluZ3MudGhlbWVcIilcbiAqIEBwYXJhbSB2YWx1ZSBUaGUgdmFsdWUgdG8gc2V0XG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBzYWZlU2V0UGF0aChyb290OiBhbnksIHBhdGg6IHN0cmluZywgdmFsdWU6IGFueSk6IHZvaWQge1xuICBjb25zdCB7IHBhcmVudCwgbGFzdEtleSB9ID0gc2FmZU5hdmlnYXRlT2JqZWN0KHJvb3QsIHBhdGgpO1xuICBzYWZlU2V0UHJvcGVydHkocGFyZW50LCBsYXN0S2V5LCB2YWx1ZSk7XG59Il19