Skip to main content
Glama

1MCP Server

scopeValidation.test.ts13.6 kB
import { describe, expect, it } from 'vitest'; import { auditScopeOperation, extractTagFromScope, hasRequiredScopes, isValidTagName, isValidTagScope, parseScope, SCOPE_VALIDATION_CONFIG, scopesToTags, tagsToScopes, validateScopes, validateScopesAgainstAvailableTags, } from './scopeValidation.js'; describe('Scope Validation', () => { describe('isValidTagName', () => { it('should accept valid tag names', () => { expect(isValidTagName('web')).toBe(true); expect(isValidTagName('db')).toBe(true); expect(isValidTagName('api-gateway')).toBe(true); expect(isValidTagName('user_service')).toBe(true); expect(isValidTagName('a123')).toBe(true); expect(isValidTagName('A_B_C')).toBe(true); }); it('should reject invalid tag names', () => { expect(isValidTagName('')).toBe(false); expect(isValidTagName('web.service')).toBe(false); expect(isValidTagName('web/service')).toBe(false); expect(isValidTagName('web service')).toBe(false); expect(isValidTagName('web@service')).toBe(false); expect(isValidTagName('web#service')).toBe(false); expect(isValidTagName('web:service')).toBe(false); expect(isValidTagName('*')).toBe(false); expect(isValidTagName('..')).toBe(false); expect(isValidTagName('../admin')).toBe(false); }); it('should enforce length constraints', () => { expect(isValidTagName('a'.repeat(SCOPE_VALIDATION_CONFIG.MAX_TAG_LENGTH))).toBe(true); expect(isValidTagName('a'.repeat(SCOPE_VALIDATION_CONFIG.MAX_TAG_LENGTH + 1))).toBe(false); expect(isValidTagName('')).toBe(false); // Below minimum length }); it('should handle non-string inputs', () => { expect(isValidTagName(null as any)).toBe(false); expect(isValidTagName(undefined as any)).toBe(false); expect(isValidTagName(123 as any)).toBe(false); expect(isValidTagName({} as any)).toBe(false); }); }); describe('isValidTagScope', () => { it('should accept valid tag scopes', () => { expect(isValidTagScope('tag:web')).toBe(true); expect(isValidTagScope('tag:db')).toBe(true); expect(isValidTagScope('tag:api-gateway')).toBe(true); expect(isValidTagScope('tag:user_service')).toBe(true); }); it('should reject invalid scope formats', () => { expect(isValidTagScope('web')).toBe(false); expect(isValidTagScope('scope:web')).toBe(false); expect(isValidTagScope('tag:')).toBe(false); expect(isValidTagScope(':web')).toBe(false); expect(isValidTagScope('tag:web:extra')).toBe(false); expect(isValidTagScope('tag:web.service')).toBe(false); expect(isValidTagScope('tag:web service')).toBe(false); }); it('should enforce length constraints', () => { const longTag = 'a'.repeat(SCOPE_VALIDATION_CONFIG.MAX_TAG_LENGTH); expect(isValidTagScope(`tag:${longTag}`)).toBe(true); const tooLongScope = 'a'.repeat(SCOPE_VALIDATION_CONFIG.MAX_SCOPE_LENGTH + 1); expect(isValidTagScope(tooLongScope)).toBe(false); }); it('should handle non-string inputs', () => { expect(isValidTagScope(null as any)).toBe(false); expect(isValidTagScope(undefined as any)).toBe(false); expect(isValidTagScope(123 as any)).toBe(false); }); }); describe('extractTagFromScope', () => { it('should extract valid tags from scopes', () => { expect(extractTagFromScope('tag:web')).toBe('web'); expect(extractTagFromScope('tag:db')).toBe('db'); expect(extractTagFromScope('tag:api-gateway')).toBe('api-gateway'); }); it('should return null for invalid scopes', () => { expect(extractTagFromScope('web')).toBe(null); expect(extractTagFromScope('scope:web')).toBe(null); expect(extractTagFromScope('tag:')).toBe(null); expect(extractTagFromScope('tag:web.service')).toBe(null); }); }); describe('parseScope', () => { it('should parse valid tag scopes', () => { expect(parseScope('tag:web')).toEqual({ type: 'tag', value: 'web' }); expect(parseScope('tag:db')).toEqual({ type: 'tag', value: 'db' }); }); it('should return null for invalid scopes', () => { expect(parseScope('web')).toBe(null); expect(parseScope('scope:web')).toBe(null); expect(parseScope('tag:')).toBe(null); expect(parseScope('')).toBe(null); expect(parseScope(null as any)).toBe(null); }); }); describe('validateScopes', () => { it('should validate valid scope arrays', () => { const result = validateScopes(['tag:web', 'tag:db']); expect(result.isValid).toBe(true); expect(result.errors).toEqual([]); expect(result.validScopes).toEqual(['tag:web', 'tag:db']); expect(result.invalidScopes).toEqual([]); }); it('should detect invalid scopes', () => { const result = validateScopes(['tag:web', 'invalid', 'tag:db.bad']); expect(result.isValid).toBe(false); expect(result.errors).toContain('Invalid scope format: invalid'); expect(result.errors).toContain('Invalid scope format: tag:db.bad'); expect(result.validScopes).toEqual(['tag:web']); expect(result.invalidScopes).toEqual(['invalid', 'tag:db.bad']); }); it('should detect duplicate scopes', () => { const result = validateScopes(['tag:web', 'tag:web', 'tag:db']); expect(result.isValid).toBe(false); expect(result.errors).toContain('Duplicate scope: tag:web'); expect(result.validScopes).toEqual(['tag:web', 'tag:db']); expect(result.invalidScopes).toEqual(['tag:web']); }); it('should enforce count limits', () => { const tooManyScopes = Array(SCOPE_VALIDATION_CONFIG.MAX_SCOPES_COUNT + 1) .fill(0) .map((_, i) => `tag:scope${i}`); const result = validateScopes(tooManyScopes); expect(result.isValid).toBe(false); expect(result.errors[0]).toContain('Too many scopes'); }); it('should handle invalid input types', () => { expect(validateScopes(null as any).isValid).toBe(false); expect(validateScopes(undefined as any).isValid).toBe(false); expect(validateScopes('not-array' as any).isValid).toBe(false); }); }); describe('validateScopesAgainstAvailableTags', () => { const availableTags = ['web', 'db', 'api-gateway']; it('should validate scopes against available tags', () => { const result = validateScopesAgainstAvailableTags(['tag:web', 'tag:db'], availableTags); expect(result.isValid).toBe(true); expect(result.validScopes).toEqual(['tag:web', 'tag:db']); }); it('should reject scopes for unavailable tags', () => { const result = validateScopesAgainstAvailableTags(['tag:web', 'tag:unavailable'], availableTags); expect(result.isValid).toBe(false); expect(result.errors).toContain('Scope not available: tag:unavailable (tag: unavailable)'); expect(result.validScopes).toEqual(['tag:web']); expect(result.invalidScopes).toEqual(['tag:unavailable']); }); it('should combine format and availability validation', () => { const result = validateScopesAgainstAvailableTags(['tag:web', 'invalid', 'tag:unavailable'], availableTags); expect(result.isValid).toBe(false); expect(result.errors).toContain('Invalid scope format: invalid'); // Note: invalid scopes are caught by format validation first expect(result.errors.length).toBeGreaterThan(0); }); }); describe('hasRequiredScopes', () => { it('should allow access when all required tags are granted', () => { const grantedScopes = ['tag:web', 'tag:db', 'tag:cache']; const requestedTags = ['web', 'db']; expect(hasRequiredScopes(grantedScopes, requestedTags)).toBe(true); }); it('should deny access when some required tags are missing', () => { const grantedScopes = ['tag:web']; const requestedTags = ['web', 'db']; expect(hasRequiredScopes(grantedScopes, requestedTags)).toBe(false); }); it('should allow access when no tags are requested', () => { const grantedScopes = ['tag:web']; const requestedTags: string[] = []; expect(hasRequiredScopes(grantedScopes, requestedTags)).toBe(true); }); it('should deny access when no scopes are granted but tags are requested', () => { const grantedScopes: string[] = []; const requestedTags = ['web']; expect(hasRequiredScopes(grantedScopes, requestedTags)).toBe(false); }); it('should handle invalid inputs securely (fail-secure)', () => { expect(hasRequiredScopes(null as any, ['web'])).toBe(false); expect(hasRequiredScopes(['tag:web'], null as any)).toBe(false); expect(hasRequiredScopes('not-array' as any, ['web'])).toBe(false); expect(hasRequiredScopes(['tag:web'], 'not-array' as any)).toBe(false); }); it('should ignore non-tag scopes', () => { const grantedScopes = ['tag:web', 'other:something', 'invalid']; const requestedTags = ['web']; expect(hasRequiredScopes(grantedScopes, requestedTags)).toBe(true); }); }); describe('tagsToScopes', () => { it('should convert valid tags to scopes', () => { expect(tagsToScopes(['web', 'db'])).toEqual(['tag:web', 'tag:db']); }); it('should filter out invalid tag names', () => { expect(tagsToScopes(['web', 'invalid.tag', 'db'])).toEqual(['tag:web', 'tag:db']); }); it('should handle empty arrays and invalid inputs', () => { expect(tagsToScopes([])).toEqual([]); expect(tagsToScopes(null as any)).toEqual([]); expect(tagsToScopes(undefined as any)).toEqual([]); }); }); describe('scopesToTags', () => { it('should extract tags from valid scopes', () => { expect(scopesToTags(['tag:web', 'tag:db'])).toEqual(['web', 'db']); }); it('should filter out invalid scopes', () => { expect(scopesToTags(['tag:web', 'invalid', 'tag:db'])).toEqual(['web', 'db']); }); it('should handle empty arrays and invalid inputs', () => { expect(scopesToTags([])).toEqual([]); expect(scopesToTags(null as any)).toEqual([]); expect(scopesToTags(undefined as any)).toEqual([]); }); }); describe('auditScopeOperation', () => { it('should call auditScopeOperation without throwing', () => { const context = { clientId: 'test-client', requestedScopes: ['tag:web'], grantedScopes: ['tag:web', 'tag:db'], success: true, }; expect(() => auditScopeOperation('test_operation', context)).not.toThrow(); }); it('should handle partial context information', () => { expect(() => auditScopeOperation('minimal_operation', { success: false })).not.toThrow(); }); }); describe('Security Edge Cases', () => { it('should prevent scope injection attacks', () => { const maliciousScopes = [ 'tag:../admin', 'tag:..\\admin', 'tag:admin*', 'tag:admin?', 'tag:admin[0-9]', 'tag:admin$(whoami)', 'tag:admin`ls`', 'tag:admin;rm -rf /', 'tag:admin||echo hack', 'tag:admin<script>alert(1)</script>', ]; maliciousScopes.forEach((scope) => { expect(isValidTagScope(scope)).toBe(false); expect(extractTagFromScope(scope)).toBe(null); }); }); it('should prevent buffer overflow attempts', () => { const longScope = 'tag:' + 'a'.repeat(10000); expect(isValidTagScope(longScope)).toBe(false); }); it('should prevent null byte injection', () => { expect(isValidTagScope('tag:admin\0')).toBe(false); expect(isValidTagScope('tag:admin\x00')).toBe(false); }); it('should handle unicode and special characters securely', () => { const unicodeScopes = ['tag:admin\u0000', 'tag:admin\uFEFF', 'tag:admin\u200B', 'tag:admin™', 'tag:admin🚀']; unicodeScopes.forEach((scope) => { expect(isValidTagScope(scope)).toBe(false); }); }); it('should prevent privilege escalation via scope manipulation', () => { const grantedScopes = ['tag:user']; // Attacker tries to escalate to admin by manipulating input const adminRequests = [ ['user', 'admin'], ['user', '../admin'], ['user', '*'], ['user', 'admin*'], ]; adminRequests.forEach((requestedTags) => { expect(hasRequiredScopes(grantedScopes, requestedTags)).toBe(false); }); }); }); describe('Performance and Resource Usage', () => { it('should handle large numbers of scopes efficiently', () => { const largeValidScopeList = Array(50) .fill(0) .map((_, i) => `tag:service${i}`); const availableTags = Array(50) .fill(0) .map((_, i) => `service${i}`); const start = Date.now(); const result = validateScopesAgainstAvailableTags(largeValidScopeList, availableTags); const duration = Date.now() - start; expect(result.isValid).toBe(true); expect(duration).toBeLessThan(1000); // Should complete within 1 second }); it('should handle scope validation under memory pressure', () => { // Test with many duplicate and invalid scopes (respects count limit) const massiveInvalidList = Array(SCOPE_VALIDATION_CONFIG.MAX_SCOPES_COUNT - 1).fill('invalid-scope'); const result = validateScopes(massiveInvalidList); expect(result.isValid).toBe(false); expect(result.invalidScopes.length).toBeGreaterThan(0); }); }); });

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/1mcp-app/agent'

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