vibe-coder-mcp
Version:
Production-ready MCP server with complete agent integration, multi-transport support, and comprehensive development automation tools for AI-assisted workflows.
158 lines (157 loc) • 6.22 kB
JavaScript
import * as path from 'path';
import logger from '../../../logger.js';
import { getPathResolver } from '../utils/path-resolver.js';
export class VibeTaskManagerSecurityValidator {
static instance;
normalizedReadDir;
normalizedWriteDir;
constructor(readDir, writeDir) {
const pathResolver = getPathResolver();
const allowedReadDir = readDir || pathResolver.getReadDirectory();
const allowedWriteDir = writeDir || pathResolver.getOutputDirectory();
this.normalizedReadDir = path.resolve(allowedReadDir);
this.normalizedWriteDir = path.resolve(allowedWriteDir);
logger.debug({
readDir: this.normalizedReadDir,
writeDir: this.normalizedWriteDir
}, 'VibeTaskManagerSecurityValidator initialized');
}
static getInstance(readDir, writeDir) {
if (!VibeTaskManagerSecurityValidator.instance) {
VibeTaskManagerSecurityValidator.instance = new VibeTaskManagerSecurityValidator(readDir, writeDir);
}
return VibeTaskManagerSecurityValidator.instance;
}
isPathWithinReadDirectory(filePath) {
try {
const normalizedPath = path.resolve(filePath);
return this.isPathWithin(normalizedPath, this.normalizedReadDir);
}
catch (error) {
logger.error({ err: error, filePath }, 'Error checking if path is within read directory');
return false;
}
}
isPathWithinWriteDirectory(filePath) {
try {
const normalizedPath = path.resolve(filePath);
return this.isPathWithin(normalizedPath, this.normalizedWriteDir);
}
catch (error) {
logger.error({ err: error, filePath }, 'Error checking if path is within write directory');
return false;
}
}
createSecureReadPath(filePath) {
if (!this.isPathWithinReadDirectory(filePath)) {
const error = `Security violation: Path '${filePath}' is outside the allowed read directory '${this.normalizedReadDir}'`;
logger.error({ filePath, readDir: this.normalizedReadDir }, error);
throw new Error(error);
}
return path.resolve(filePath);
}
createSecureWritePath(filePath) {
if (!this.isPathWithinWriteDirectory(filePath)) {
const error = `Security violation: Path '${filePath}' is outside the allowed write directory '${this.normalizedWriteDir}'`;
logger.error({ filePath, writeDir: this.normalizedWriteDir }, error);
throw new Error(error);
}
return path.resolve(filePath);
}
validateReadPath(filePath) {
try {
if (!filePath || typeof filePath !== 'string') {
return {
isValid: false,
error: 'Invalid path: path must be a non-empty string',
violationType: 'invalid_path'
};
}
if (filePath.includes('..')) {
return {
isValid: false,
error: 'Path traversal detected in read path',
violationType: 'path_traversal'
};
}
if (!this.isPathWithinReadDirectory(filePath)) {
return {
isValid: false,
error: `Path is outside allowed read directory: ${this.normalizedReadDir}`,
violationType: 'outside_boundary'
};
}
return {
isValid: true,
securePath: path.resolve(filePath)
};
}
catch (error) {
return {
isValid: false,
error: `Path validation failed: ${error instanceof Error ? error.message : 'Unknown error'}`,
violationType: 'invalid_path'
};
}
}
validateWritePath(filePath) {
try {
if (!filePath || typeof filePath !== 'string') {
return {
isValid: false,
error: 'Invalid path: path must be a non-empty string',
violationType: 'invalid_path'
};
}
if (filePath.includes('..')) {
return {
isValid: false,
error: 'Path traversal detected in write path',
violationType: 'path_traversal'
};
}
if (!this.isPathWithinWriteDirectory(filePath)) {
return {
isValid: false,
error: `Path is outside allowed write directory: ${this.normalizedWriteDir}`,
violationType: 'outside_boundary'
};
}
return {
isValid: true,
securePath: path.resolve(filePath)
};
}
catch (error) {
return {
isValid: false,
error: `Path validation failed: ${error instanceof Error ? error.message : 'Unknown error'}`,
violationType: 'invalid_path'
};
}
}
getReadDirectory() {
return this.normalizedReadDir;
}
getWriteDirectory() {
return this.normalizedWriteDir;
}
isPathWithin(childPath, parentPath) {
const normalizedChild = path.normalize(childPath);
const normalizedParent = path.normalize(parentPath);
return normalizedChild === normalizedParent ||
(normalizedChild.startsWith(normalizedParent) &&
normalizedChild.substring(normalizedParent.length, normalizedParent.length + 1) === path.sep);
}
}
export function getVibeTaskManagerSecurityValidator(readDir, writeDir) {
return VibeTaskManagerSecurityValidator.getInstance(readDir, writeDir);
}
export function createSecureReadPath(filePath) {
const validator = getVibeTaskManagerSecurityValidator();
return validator.createSecureReadPath(filePath);
}
export function createSecureWritePath(filePath) {
const validator = getVibeTaskManagerSecurityValidator();
return validator.createSecureWritePath(filePath);
}