Skip to main content
Glama
MUSE-CODE-SPACE

Vibe Coding Documentation MCP (MUSE)

security.js4.3 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, allowedDir) { 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, maxLength = 200) { 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, allowedHosts) { 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']; export async function fetchWithRetry(url, options = {}, retryOptions = {}) { const { timeout = 30000, maxRetries = 3, retryDelay = 1000, retryStatusCodes = [429, 500, 502, 503, 504] } = retryOptions; let lastError = 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) { return new Promise(resolve => setTimeout(resolve, ms)); } //# sourceMappingURL=security.js.map

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