UNPKG

@codacy/codacy-mcp

Version:

Codacy MCP server

89 lines (88 loc) 3.35 kB
export const CODACY_FOLDER_NAME = '.codacy'; import { exec } from 'child_process'; import * as path from 'path'; // Set a larger buffer size (10MB) const MAX_BUFFER_SIZE = 1024 * 1024 * 10; export class CodacyCli { _cliCommand = ''; _accountToken = process.env.CODACY_ACCOUNT_TOKEN; _cliVersion = process.env.CODACY_CLI_VERSION; rootPath; provider; organization; repository; constructor(rootPath, provider, organization, repository) { this.rootPath = rootPath; this.provider = provider; this.organization = organization; this.repository = repository; } getCliCommand() { return this._cliCommand; } setCliCommand(command) { this._cliCommand = command; } isPathSafe(filePath) { // Reject null bytes (always a security risk) if (filePath.includes('\0')) { return false; } // Reject all control characters (including newline, tab, carriage return) // as they are very unusual for file names // eslint-disable-next-line no-control-regex -- Intentionally checking for control chars to reject them for security const hasUnsafeControlChars = /[\x00-\x1F\x7F]/.test(filePath); if (hasUnsafeControlChars) { return false; } // Resolve the path to check for path traversal attempts const resolvedPath = path.resolve(this.rootPath, filePath); const normalizedRoot = path.normalize(this.rootPath); // Check if the resolved path is within the workspace if (!resolvedPath.startsWith(normalizedRoot)) { return false; } return true; } preparePathForExec(path) { // Validate path security before escaping if (!this.isPathSafe(path)) { throw new Error(`Unsafe file path rejected: ${path}`); } // Escape special characters for shell execution return path.replace(/([\s'"\\;&|`$()[\]{}*?~<>])/g, '\\$1'); } execAsync(command, args) { // stringyfy the args const argsString = Object.entries(args || {}) .map(([key, value]) => { // Validate argument key (should be alphanumeric and hyphens only) if (!/^[a-zA-Z0-9-]+$/.test(key)) { throw new Error(`Invalid argument key: ${key}`); } // Escape the value to prevent injection const escapedValue = value.replace(/([\s'"\\;&|`$()[\]{}*?~<>])/g, '\\$1'); return `--${key} ${escapedValue}`; }) .join(' '); // Build the command - no need to strip characters since we've already escaped them properly const cmd = `${command} ${argsString}`.trim(); return new Promise((resolve, reject) => { exec(cmd, { cwd: this.rootPath, maxBuffer: MAX_BUFFER_SIZE, // To solve: stdout maxBuffer exceeded encoding: 'utf-8', }, (error, stdout, stderr) => { if (error) { reject(error); return; } if (stderr && !stdout) { reject(new Error(stderr)); return; } resolve({ stdout, stderr }); }); }); } }