Skip to main content
Glama
MUSE-CODE-SPACE

Vibe Coding Documentation MCP (MUSE)

security.ts4.7 kB
/** * Security utilities for path validation, network requests, and URL validation */ import * as path from 'path'; import { ToolError } from './errors.js'; /** * Validates that a file path stays within the allowed directory (prevents path traversal) */ export function validatePathWithinDirectory(filePath: string, allowedDir: string): string { const resolvedPath = path.resolve(filePath); const resolvedAllowedDir = path.resolve(allowedDir); if (!resolvedPath.startsWith(resolvedAllowedDir + path.sep) && resolvedPath !== resolvedAllowedDir) { throw new ToolError( 'Path traversal detected: file path escapes allowed directory', 'VALIDATION_ERROR', { filePath, allowedDir } ); } return resolvedPath; } /** * Sanitizes a filename to prevent path traversal and invalid characters */ export function sanitizeFilename(filename: string, maxLength: number = 200): string { return filename .replace(/\.\./g, '') // Remove parent directory references .replace(/[<>:"/\\|?*\x00-\x1f]/g, '-') // Remove invalid characters .replace(/\s+/g, '-') // Replace whitespace with dashes .replace(/-+/g, '-') // Collapse multiple dashes .replace(/^-|-$/g, '') // Remove leading/trailing dashes .slice(0, maxLength); } /** * Validates webhook URL for Slack or Discord (SSRF prevention) */ export function validateWebhookUrl(url: string, allowedHosts: string[]): void { try { const parsed = new URL(url); // Must be HTTPS if (parsed.protocol !== 'https:') { throw new ToolError( 'Webhook URL must use HTTPS protocol', 'VALIDATION_ERROR', { url: url.slice(0, 50) } ); } // Must match allowed hosts const isAllowed = allowedHosts.some(host => parsed.hostname === host || parsed.hostname.endsWith('.' + host) ); if (!isAllowed) { throw new ToolError( `Webhook URL must be from allowed hosts: ${allowedHosts.join(', ')}`, 'VALIDATION_ERROR', { hostname: parsed.hostname } ); } } catch (error) { if (error instanceof ToolError) throw error; throw new ToolError( 'Invalid webhook URL format', 'VALIDATION_ERROR', { url: url.slice(0, 50) } ); } } // Allowed webhook hosts export const SLACK_ALLOWED_HOSTS = ['hooks.slack.com']; export const DISCORD_ALLOWED_HOSTS = ['discord.com', 'discordapp.com']; /** * Fetch with timeout and retry support */ export interface FetchWithRetryOptions { timeout?: number; // Timeout in ms (default: 30000) maxRetries?: number; // Max retry attempts (default: 3) retryDelay?: number; // Initial retry delay in ms (default: 1000) retryStatusCodes?: number[]; // Status codes to retry (default: [429, 500, 502, 503, 504]) } export async function fetchWithRetry( url: string, options: RequestInit = {}, retryOptions: FetchWithRetryOptions = {} ): Promise<Response> { const { timeout = 30000, maxRetries = 3, retryDelay = 1000, retryStatusCodes = [429, 500, 502, 503, 504] } = retryOptions; let lastError: Error | null = null; for (let attempt = 0; attempt <= maxRetries; attempt++) { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), timeout); try { const response = await fetch(url, { ...options, signal: controller.signal }); clearTimeout(timeoutId); // Check if we should retry if (retryStatusCodes.includes(response.status) && attempt < maxRetries) { // Get retry-after header if available const retryAfter = response.headers.get('retry-after'); const delay = retryAfter ? parseInt(retryAfter) * 1000 : retryDelay * Math.pow(2, attempt); // Exponential backoff await sleep(delay); continue; } return response; } catch (error) { clearTimeout(timeoutId); if (error instanceof Error) { if (error.name === 'AbortError') { lastError = new ToolError( `Request timed out after ${timeout}ms`, 'TIMEOUT', { url: url.slice(0, 100), attempt } ); } else { lastError = error; } } // Retry on network errors if (attempt < maxRetries) { await sleep(retryDelay * Math.pow(2, attempt)); continue; } } } throw lastError || new ToolError('Request failed after retries', 'NETWORK_ERROR'); } /** * Sleep utility for retry delays */ function sleep(ms: number): Promise<void> { return new Promise(resolve => setTimeout(resolve, ms)); }

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/MUSE-CODE-SPACE/vibe-coding-mcp'

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