Skip to main content
Glama
security.ts3.48 kB
/** * Security utilities for repository access control and validation */ export class RepositoryValidator { private static allowedRepos: string[] | null = null; /** * Initialize the validator with allowed repositories from environment */ static initialize(): void { const allowList = process.env.JULES_ALLOWED_REPOS; if (allowList) { this.allowedRepos = allowList .split(',') .map((r) => r.trim()) .filter((r) => r.length > 0); } } /** * Validates that a repository is allowed to be accessed * @param source - The source repository string in the format "sources/github/owner/repo" * @throws Error if repository is not in allowlist */ static validateRepository(source: string): void { // If no allowlist configured, allow all if (!this.allowedRepos) { return; } // Extract owner/repo from source format: sources/github/owner/repo const match = source.match(/^sources\/github\/(.+)$/); if (!match) { throw new Error( `Invalid source format: ${source}. Expected sources/github/owner/repo` ); } const repoPath = match[1]; if (!this.allowedRepos.includes(repoPath)) { throw new Error( `Repository "${repoPath}" is not in the allowed repositories list. ` + `Allowed: ${this.allowedRepos.join(', ')}. ` + `Set JULES_ALLOWED_REPOS environment variable to modify this list.` ); } } /** * Check if allowlist is configured * @returns True if an allowlist is configured, false otherwise */ static isAllowlistEnabled(): boolean { return this.allowedRepos !== null && this.allowedRepos.length > 0; } /** * Get the list of allowed repositories * @returns The list of allowed repositories, or null if no allowlist is configured */ static getAllowedRepositories(): string[] | null { return this.allowedRepos; } } /** * Utility for safe string truncation at word boundaries * @param text - The text to truncate * @param maxLength - The maximum length of the string * @returns The truncated string, with "..." appended if it was truncated */ export function smartTruncate(text: string, maxLength: number): string { if (text.length <= maxLength) { return text; } // Try to break at a word boundary let truncated = text.substring(0, maxLength); const lastSpace = truncated.lastIndexOf(' '); if (lastSpace > maxLength * 0.8) { // If we can break at a word within 80% of max length, do it truncated = truncated.substring(0, lastSpace); } return truncated.trim() + '...'; } /** * Retry an async operation with exponential backoff * @param fn - The async function to retry * @param maxRetries - The maximum number of retries (default: 3) * @param baseDelay - The base delay in milliseconds (default: 1000) * @returns A promise that resolves with the result of the function * @throws The last error encountered if all retries fail */ export async function retryWithBackoff<T>( fn: () => Promise<T>, maxRetries = 3, baseDelay = 1000 ): Promise<T> { let lastError: Error; for (let attempt = 0; attempt < maxRetries; attempt++) { try { return await fn(); } catch (error) { lastError = error as Error; if (attempt < maxRetries - 1) { const delay = baseDelay * Math.pow(2, attempt); await new Promise((resolve) => setTimeout(resolve, delay)); } } } throw lastError!; }

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/savethepolarbears/jules-mcp-server'

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