Skip to main content
Glama
reuvenaor

Shadcn Registry manager

by reuvenaor
security.ts5.64 kB
import path from "path" import { homedir } from "os" import fs from "fs" /** * Determines the safe working directory, prioritizing /workspace for Docker conventions * and falling back to WORKSPACE_DIR for local development (e.g., via npx). */ export function getSafeWorkspaceCwd(rawCwd?: string): string { const defaultCwd = "/workspace" const baseCwd = rawCwd || defaultCwd if (fs.existsSync(defaultCwd)) { if (baseCwd !== defaultCwd) { console.warn( `[MCP] Overriding cwd from '${baseCwd}' to '${defaultCwd}' (MCP Docker convention)` ) } return defaultCwd } if (process.env.WORKSPACE_DIR) { console.warn( `[MCP] /workspace not found. Using WORKSPACE_DIR: ${process.env.WORKSPACE_DIR}` ) return process.env.WORKSPACE_DIR } if (rawCwd) { console.warn( `[MCP] /workspace and WORKSPACE_DIR not found. Using provided cwd: '${rawCwd}'` ) return rawCwd } throw new Error("Could not determine a safe working directory. /workspace not found and WORKSPACE_DIR is not set.") } /** * Validates that a file path is within the allowed workspace directory * and doesn't contain dangerous patterns that could lead to path traversal attacks. */ export function validateWorkspacePath(inputPath: string, workspaceDir: string): string { // Reject null, undefined, or empty paths if (!inputPath || typeof inputPath !== 'string') { throw new Error("Invalid path: path must be a non-empty string") } // Reject paths with null bytes (common in path traversal attacks) if (inputPath.includes('\0')) { throw new Error("Invalid path: null bytes not allowed") } // Handle home directory expansion securely let processedPath = inputPath if (inputPath.startsWith("~/")) { // Only allow home directory expansion within workspace processedPath = inputPath.replace("~/", "") if (processedPath.includes("..") || processedPath.startsWith("/")) { throw new Error("Invalid home directory path: path traversal detected") } } // Resolve the path relative to workspace directory const resolvedPath = path.resolve(workspaceDir, processedPath) const normalizedPath = path.normalize(resolvedPath) const normalizedWorkspace = path.normalize(workspaceDir) // Ensure the resolved path is within the workspace if (!normalizedPath.startsWith(normalizedWorkspace + path.sep) && normalizedPath !== normalizedWorkspace) { throw new Error(`Path outside workspace not allowed: ${inputPath}`) } // Additional checks for dangerous patterns const dangerousPatterns = [ /\.\./, // Parent directory references /\/\.\./, // Unix parent directory /\\\.\./, // Windows parent directory /\.\.$/, // Ending with parent directory /\/$/, // Ending with slash (potential directory traversal) /^\/+/, // Starting with absolute path indicators /^[a-zA-Z]:\\/, // Windows absolute path ] for (const pattern of dangerousPatterns) { if (pattern.test(processedPath)) { throw new Error(`Dangerous path pattern detected: ${inputPath}`) } } return normalizedPath } /** * Validates a component or registry item name to prevent injection */ export function validateComponentName(name: string): string { if (!name || typeof name !== 'string') { throw new Error("Invalid component name: must be a non-empty string") } // Allow only alphanumeric, hyphens, underscores, slashes, and dots const validNamePattern = /^[a-zA-Z0-9\-_\/\.@]+$/ if (!validNamePattern.test(name)) { throw new Error(`Invalid component name: contains illegal characters: ${name}`) } // Prevent excessively long names (DoS protection) if (name.length > 200) { throw new Error("Component name too long") } // Prevent certain dangerous patterns const dangerousPatterns = [ /\.\./, /\/\//, /^\./, /\/$/, /\0/ ] for (const pattern of dangerousPatterns) { if (pattern.test(name)) { throw new Error(`Dangerous pattern in component name: ${name}`) } } return name } /** * Validates file content to prevent malicious content injection */ export function validateFileContent(content: string, maxSize: number = 10 * 1024 * 1024): string { if (typeof content !== 'string') { throw new Error("File content must be a string") } // Size check (default 10MB) if (content.length > maxSize) { throw new Error(`File content too large: ${content.length} bytes (max: ${maxSize})`) } // Check for null bytes if (content.includes('\0')) { throw new Error("File content contains null bytes") } return content } /** * Safely resolves a working directory, ensuring it's valid and secure */ export function validateWorkingDirectory(cwd: string): string { if (!cwd || typeof cwd !== 'string') { throw new Error("Working directory must be a non-empty string") } // Handle special workspace paths if (cwd === "/workspace") { return "/workspace" } // Resolve and normalize the path const resolvedCwd = path.resolve(cwd) const normalizedCwd = path.normalize(resolvedCwd) // Prevent access to sensitive system directories const forbiddenPaths = [ "/etc", "/proc", "/sys", "/dev", "/boot", "/root", path.join(homedir(), ".ssh"), path.join(homedir(), ".aws"), path.join(homedir(), ".config") ] for (const forbidden of forbiddenPaths) { if (normalizedCwd.startsWith(forbidden)) { throw new Error(`Access to system directory not allowed: ${cwd}`) } } return normalizedCwd }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/reuvenaor/shadcn-registry-manager'

If you have feedback or need assistance with the MCP directory API, please join our Discord server