Skip to main content
Glama
urlValidator.test.js17.1 kB
import { describe, it, expect } from 'vitest'; import { validateImageUrl, createSecureAxiosConfig, ALLOWED_DOMAINS } from '../urlValidator.js'; describe('urlValidator', () => { describe('ALLOWED_DOMAINS', () => { it('should export an array of allowed domains', () => { expect(ALLOWED_DOMAINS).toBeInstanceOf(Array); expect(ALLOWED_DOMAINS.length).toBeGreaterThan(0); expect(ALLOWED_DOMAINS).toContain('imgur.com'); expect(ALLOWED_DOMAINS).toContain('github.com'); }); }); describe('isSafeHost (tested via validateImageUrl)', () => { describe('allowed domains', () => { it('should allow imgur.com', () => { const result = validateImageUrl('https://imgur.com/image.jpg'); expect(result.isValid).toBe(true); expect(result.sanitizedUrl).toBe('https://imgur.com/image.jpg'); }); it('should allow i.imgur.com subdomain', () => { const result = validateImageUrl('https://i.imgur.com/abc123.jpg'); expect(result.isValid).toBe(true); }); it('should allow github.com', () => { const result = validateImageUrl('https://github.com/user/repo/image.png'); expect(result.isValid).toBe(true); }); it('should allow githubusercontent.com', () => { const result = validateImageUrl('https://githubusercontent.com/image.jpg'); expect(result.isValid).toBe(true); }); it('should allow unsplash.com', () => { const result = validateImageUrl('https://unsplash.com/photo.jpg'); expect(result.isValid).toBe(true); }); it('should allow images.unsplash.com subdomain', () => { const result = validateImageUrl('https://images.unsplash.com/photo-123'); expect(result.isValid).toBe(true); }); it('should allow cloudinary.com', () => { const result = validateImageUrl('https://cloudinary.com/image.jpg'); expect(result.isValid).toBe(true); }); it('should allow res.cloudinary.com subdomain', () => { const result = validateImageUrl('https://res.cloudinary.com/demo/image.jpg'); expect(result.isValid).toBe(true); }); it('should allow amazonaws.com', () => { const result = validateImageUrl('https://amazonaws.com/bucket/image.jpg'); expect(result.isValid).toBe(true); }); it('should allow s3.amazonaws.com subdomain', () => { const result = validateImageUrl('https://s3.amazonaws.com/bucket/image.jpg'); expect(result.isValid).toBe(true); }); it('should allow deep subdomains of allowed domains', () => { const result = validateImageUrl('https://cdn.images.unsplash.com/photo.jpg'); expect(result.isValid).toBe(true); }); }); describe('blocked IP patterns - IPv4 localhost and private ranges', () => { it('should block 127.0.0.1 (localhost)', () => { const result = validateImageUrl('https://127.0.0.1/image.jpg'); expect(result.isValid).toBe(false); expect(result.error).toContain('not allowed for security reasons'); }); it('should block 127.0.0.2 (localhost range)', () => { const result = validateImageUrl('https://127.0.0.2/image.jpg'); expect(result.isValid).toBe(false); }); it('should block 127.255.255.255 (localhost range edge)', () => { const result = validateImageUrl('https://127.255.255.255/image.jpg'); expect(result.isValid).toBe(false); }); it('should block 10.0.0.1 (private network)', () => { const result = validateImageUrl('https://10.0.0.1/image.jpg'); expect(result.isValid).toBe(false); }); it('should block 10.255.255.255 (private network edge)', () => { const result = validateImageUrl('https://10.255.255.255/image.jpg'); expect(result.isValid).toBe(false); }); it('should block 192.168.0.1 (private network)', () => { const result = validateImageUrl('https://192.168.0.1/image.jpg'); expect(result.isValid).toBe(false); }); it('should block 192.168.255.255 (private network edge)', () => { const result = validateImageUrl('https://192.168.255.255/image.jpg'); expect(result.isValid).toBe(false); }); it('should block 172.16.0.1 (private network)', () => { const result = validateImageUrl('https://172.16.0.1/image.jpg'); expect(result.isValid).toBe(false); }); it('should block 172.31.255.255 (private network edge)', () => { const result = validateImageUrl('https://172.31.255.255/image.jpg'); expect(result.isValid).toBe(false); }); it('should block 169.254.0.1 (link local)', () => { const result = validateImageUrl('https://169.254.0.1/image.jpg'); expect(result.isValid).toBe(false); }); it('should block 0.0.0.0', () => { const result = validateImageUrl('https://0.0.0.0/image.jpg'); expect(result.isValid).toBe(false); }); it('should block 224.0.0.1 (multicast)', () => { const result = validateImageUrl('https://224.0.0.1/image.jpg'); expect(result.isValid).toBe(false); }); it('should block 240.0.0.1 (reserved)', () => { const result = validateImageUrl('https://240.0.0.1/image.jpg'); expect(result.isValid).toBe(false); }); it('should block 255.255.255.255 (broadcast)', () => { const result = validateImageUrl('https://255.255.255.255/image.jpg'); expect(result.isValid).toBe(false); }); }); describe('blocked IP patterns - IPv6', () => { it('should block ::1 (IPv6 localhost)', () => { const result = validateImageUrl('https://[::1]/image.jpg'); expect(result.isValid).toBe(false); }); it('should block :: (IPv6 unspecified)', () => { const result = validateImageUrl('https://[::]/image.jpg'); expect(result.isValid).toBe(false); }); it('should block fc00:: (IPv6 unique local)', () => { const result = validateImageUrl('https://[fc00::1]/image.jpg'); expect(result.isValid).toBe(false); }); it('should block fe80:: (IPv6 link local)', () => { const result = validateImageUrl('https://[fe80::1]/image.jpg'); expect(result.isValid).toBe(false); }); it('should block ff00:: (IPv6 multicast)', () => { const result = validateImageUrl('https://[ff00::1]/image.jpg'); expect(result.isValid).toBe(false); }); }); describe('subdomain matching', () => { it('should allow exact domain match', () => { const result = validateImageUrl('https://imgur.com/image.jpg'); expect(result.isValid).toBe(true); }); it('should allow subdomain with dot prefix', () => { const result = validateImageUrl('https://i.imgur.com/image.jpg'); expect(result.isValid).toBe(true); }); it('should allow multi-level subdomains', () => { const result = validateImageUrl('https://cdn.images.imgur.com/image.jpg'); expect(result.isValid).toBe(true); }); it('should reject domain that partially matches but is not a subdomain', () => { const result = validateImageUrl('https://fakeimgur.com/image.jpg'); expect(result.isValid).toBe(false); }); it('should reject domain with allowed domain as substring', () => { const result = validateImageUrl('https://notimgur.com/image.jpg'); expect(result.isValid).toBe(false); }); }); describe('hostname string checks for localhost/internal', () => { it('should block localhost hostname', () => { const result = validateImageUrl('https://localhost/image.jpg'); expect(result.isValid).toBe(false); expect(result.error).toContain('not allowed for security reasons'); }); it('should block localhost with port', () => { const result = validateImageUrl('https://localhost:3000/image.jpg'); expect(result.isValid).toBe(false); }); it('should block 0.0.0.0 hostname', () => { const result = validateImageUrl('https://0.0.0.0/image.jpg'); expect(result.isValid).toBe(false); }); it('should block 192.168.x.x hostname pattern', () => { const result = validateImageUrl('https://192.168.1.1/image.jpg'); expect(result.isValid).toBe(false); }); it('should block 10.x.x.x hostname pattern', () => { const result = validateImageUrl('https://10.1.1.1/image.jpg'); expect(result.isValid).toBe(false); }); it('should block .local domains', () => { const result = validateImageUrl('https://myserver.local/image.jpg'); expect(result.isValid).toBe(false); }); }); }); describe('validateImageUrl', () => { describe('valid HTTPS URLs', () => { it('should accept valid HTTPS URL from allowed domain', () => { const result = validateImageUrl('https://imgur.com/abc123.jpg'); expect(result.isValid).toBe(true); expect(result.sanitizedUrl).toBe('https://imgur.com/abc123.jpg'); expect(result.error).toBeUndefined(); }); it('should accept HTTPS URL with query parameters', () => { const result = validateImageUrl('https://imgur.com/image.jpg?size=large'); expect(result.isValid).toBe(true); }); it('should accept HTTPS URL with fragment', () => { const result = validateImageUrl('https://imgur.com/image.jpg#section'); expect(result.isValid).toBe(true); }); it('should accept HTTPS URL with path segments', () => { const result = validateImageUrl('https://github.com/user/repo/blob/main/image.png'); expect(result.isValid).toBe(true); }); it('should accept HTTP URL from allowed domain', () => { const result = validateImageUrl('http://imgur.com/image.jpg'); expect(result.isValid).toBe(true); }); }); describe('invalid protocols', () => { it('should reject file:// protocol', () => { const result = validateImageUrl('file:///etc/passwd'); expect(result.isValid).toBe(false); expect(result.error).toContain('Invalid URL format'); }); it('should reject ftp:// protocol', () => { const result = validateImageUrl('ftp://example.com/image.jpg'); expect(result.isValid).toBe(false); expect(result.error).toContain('Invalid URL format'); }); it('should reject data: protocol', () => { const result = validateImageUrl('data:image/png;base64,iVBORw0KGgo'); expect(result.isValid).toBe(false); }); it('should reject javascript: protocol', () => { const result = validateImageUrl('javascript:alert("xss")'); expect(result.isValid).toBe(false); }); it('should reject gopher:// protocol', () => { const result = validateImageUrl('gopher://example.com/image'); expect(result.isValid).toBe(false); }); }); describe('non-standard ports', () => { it('should allow port 80', () => { const result = validateImageUrl('http://imgur.com:80/image.jpg'); expect(result.isValid).toBe(true); }); it('should allow port 443', () => { const result = validateImageUrl('https://imgur.com:443/image.jpg'); expect(result.isValid).toBe(true); }); it('should allow port 8080', () => { const result = validateImageUrl('https://imgur.com:8080/image.jpg'); expect(result.isValid).toBe(true); }); it('should allow port 8443', () => { const result = validateImageUrl('https://imgur.com:8443/image.jpg'); expect(result.isValid).toBe(true); }); it('should reject port 22 (SSH)', () => { const result = validateImageUrl('https://imgur.com:22/image.jpg'); expect(result.isValid).toBe(false); expect(result.error).toContain('non-standard port'); }); it('should reject port 3000', () => { const result = validateImageUrl('https://imgur.com:3000/image.jpg'); expect(result.isValid).toBe(false); expect(result.error).toContain('non-standard port'); }); it('should reject port 5432 (PostgreSQL)', () => { const result = validateImageUrl('https://imgur.com:5432/image.jpg'); expect(result.isValid).toBe(false); }); it('should reject port 6379 (Redis)', () => { const result = validateImageUrl('https://imgur.com:6379/image.jpg'); expect(result.isValid).toBe(false); }); it('should reject port 9200 (Elasticsearch)', () => { const result = validateImageUrl('https://imgur.com:9200/image.jpg'); expect(result.isValid).toBe(false); }); }); describe('edge cases and error handling', () => { it('should reject empty string', () => { const result = validateImageUrl(''); expect(result.isValid).toBe(false); expect(result.error).toContain('Invalid URL format'); }); it('should reject malformed URL', () => { const result = validateImageUrl('not-a-url'); expect(result.isValid).toBe(false); }); it('should reject relative URLs', () => { const result = validateImageUrl('/path/to/image.jpg'); expect(result.isValid).toBe(false); }); it('should reject URL without protocol', () => { const result = validateImageUrl('imgur.com/image.jpg'); expect(result.isValid).toBe(false); }); it('should handle URLs with special characters in path', () => { const result = validateImageUrl('https://imgur.com/image%20with%20spaces.jpg'); expect(result.isValid).toBe(true); }); it('should handle URLs with authentication (though domain must be allowed)', () => { const result = validateImageUrl('https://user:pass@imgur.com/image.jpg'); expect(result.isValid).toBe(true); }); }); }); describe('createSecureAxiosConfig', () => { it('should return config object with all required fields', () => { const url = 'https://imgur.com/image.jpg'; const config = createSecureAxiosConfig(url); expect(config).toHaveProperty('url', url); expect(config).toHaveProperty('responseType', 'stream'); expect(config).toHaveProperty('timeout', 10000); expect(config).toHaveProperty('maxRedirects', 3); expect(config).toHaveProperty('maxContentLength', 50 * 1024 * 1024); expect(config).toHaveProperty('validateStatus'); expect(config).toHaveProperty('headers'); }); it('should set correct URL', () => { const url = 'https://github.com/user/repo/image.png'; const config = createSecureAxiosConfig(url); expect(config.url).toBe(url); }); it('should set response type to stream', () => { const config = createSecureAxiosConfig('https://imgur.com/image.jpg'); expect(config.responseType).toBe('stream'); }); it('should set 10 second timeout', () => { const config = createSecureAxiosConfig('https://imgur.com/image.jpg'); expect(config.timeout).toBe(10000); }); it('should limit redirects to 3', () => { const config = createSecureAxiosConfig('https://imgur.com/image.jpg'); expect(config.maxRedirects).toBe(3); }); it('should set max content length to 50MB', () => { const config = createSecureAxiosConfig('https://imgur.com/image.jpg'); expect(config.maxContentLength).toBe(50 * 1024 * 1024); }); it('should include User-Agent header', () => { const config = createSecureAxiosConfig('https://imgur.com/image.jpg'); expect(config.headers).toHaveProperty('User-Agent', 'Ghost-MCP-Server/1.0'); }); it('should have validateStatus function that accepts 2xx status codes', () => { const config = createSecureAxiosConfig('https://imgur.com/image.jpg'); expect(typeof config.validateStatus).toBe('function'); expect(config.validateStatus(200)).toBe(true); expect(config.validateStatus(204)).toBe(true); expect(config.validateStatus(299)).toBe(true); }); it('should have validateStatus function that rejects non-2xx status codes', () => { const config = createSecureAxiosConfig('https://imgur.com/image.jpg'); expect(config.validateStatus(199)).toBe(false); expect(config.validateStatus(300)).toBe(false); expect(config.validateStatus(404)).toBe(false); expect(config.validateStatus(500)).toBe(false); }); it('should handle different URLs independently', () => { const url1 = 'https://imgur.com/image1.jpg'; const url2 = 'https://github.com/image2.png'; const config1 = createSecureAxiosConfig(url1); const config2 = createSecureAxiosConfig(url2); expect(config1.url).toBe(url1); expect(config2.url).toBe(url2); expect(config1.url).not.toBe(config2.url); }); }); });

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/jgardner04/Ghost-MCP-Server'

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