Skip to main content
Glama

DollhouseMCP

by DollhouseMCP
RateLimiterSecurity.test.tsโ€ข6.28 kB
/** * Security tests for RateLimiter implementation */ import { RateLimiter, RateLimiterFactory } from '../../../src/utils/RateLimiter.js'; import { describe, expect, it, beforeEach, jest } from '@jest/globals'; describe('RateLimiter Security Tests', () => { describe('Token Bucket Algorithm', () => { it('should enforce rate limits correctly', () => { const limiter = new RateLimiter({ maxRequests: 5, windowMs: 1000 // 1 second, no minDelay for this test }); // First 5 requests should be allowed for (let i = 0; i < 5; i++) { const status = limiter.checkLimit(); expect(status.allowed).toBe(true); expect(status.remainingTokens).toBe(5 - i); limiter.consumeToken(); } // 6th request should be denied const deniedStatus = limiter.checkLimit(); expect(deniedStatus.allowed).toBe(false); expect(deniedStatus.remainingTokens).toBe(0); expect(deniedStatus.retryAfterMs).toBeGreaterThan(0); }); it('should prevent division by zero with invalid config', () => { expect(() => { new RateLimiter({ maxRequests: 0, windowMs: 1000 }); }).toThrow('maxRequests must be positive'); expect(() => { new RateLimiter({ maxRequests: 10, windowMs: 0 }); }).toThrow('windowMs must be positive'); }); it('should handle extreme configurations safely', () => { // Very low rate limit const strictLimiter = new RateLimiter({ maxRequests: 1, windowMs: 60 * 60 * 1000, // 1 hour minDelayMs: 60 * 1000 // 1 minute }); const status1 = strictLimiter.checkLimit(); expect(status1.allowed).toBe(true); strictLimiter.consumeToken(); const status2 = strictLimiter.checkLimit(); expect(status2.allowed).toBe(false); expect(status2.retryAfterMs).toBeGreaterThan(59000); // Close to 1 minute }); it('should enforce minimum delay between requests', (done) => { const limiter = new RateLimiter({ maxRequests: 100, windowMs: 1000, minDelayMs: 50 }); // First request should be allowed const status1 = limiter.checkLimit(); expect(status1.allowed).toBe(true); limiter.consumeToken(); // Immediate second request should be denied const status2 = limiter.checkLimit(); expect(status2.allowed).toBe(false); expect(status2.retryAfterMs).toBeLessThanOrEqual(50); // After delay, should be allowed setTimeout(() => { const status3 = limiter.checkLimit(); expect(status3.allowed).toBe(true); done(); }, 60); }); }); describe('Factory Methods', () => { it('should create GitHub rate limiter with proper limits', () => { const githubLimiter = RateLimiterFactory.createGitHubLimiter(); const status = githubLimiter.getStatus(); expect(status.remainingTokens).toBe(60); // GitHub's unauthenticated limit expect(status.allowed).toBe(true); }); it('should create update check limiter with conservative limits', () => { const updateLimiter = RateLimiterFactory.createUpdateCheckLimiter(); const status = updateLimiter.getStatus(); expect(status.remainingTokens).toBe(10); // 10 checks per hour expect(status.allowed).toBe(true); }); it('should create strict limiter for sensitive operations', () => { const strictLimiter = RateLimiterFactory.createStrictLimiter(); const status = strictLimiter.getStatus(); expect(status.remainingTokens).toBe(5); // Only 5 requests per hour expect(status.allowed).toBe(true); }); }); describe('PersonaSharer Rate Limiting Integration', () => { it.skip('should use appropriate rate limits for authenticated vs unauthenticated', async () => { // SKIPPED: PersonaSharer has been disabled as export_persona tools were removed // This test was for the PersonaSharer rate limiting integration // When GITHUB_TOKEN exists: 100 requests/hour // When no token: 30 requests/hour (conservative) // The PersonaSharer module no longer exists after disabling export tools // Rate limiting is still tested in other tests in this file }); }); describe('Rate Limit Reset', () => { it('should properly reset rate limits', () => { const limiter = new RateLimiter({ maxRequests: 5, windowMs: 1000 }); // Use all tokens for (let i = 0; i < 5; i++) { limiter.consumeToken(); } expect(limiter.getStatus().allowed).toBe(false); // Reset should restore all tokens limiter.reset(); const status = limiter.getStatus(); expect(status.allowed).toBe(true); expect(status.remainingTokens).toBe(5); }); }); describe('Token Refill', () => { it('should refill tokens over time', (done) => { const limiter = new RateLimiter({ maxRequests: 10, windowMs: 100 // 100ms window for faster test }); // Use 5 tokens for (let i = 0; i < 5; i++) { limiter.consumeToken(); } expect(limiter.getStatus().remainingTokens).toBe(5); // Wait for half the window setTimeout(() => { const status = limiter.getStatus(); // Should have refilled approximately 5 tokens expect(status.remainingTokens).toBeGreaterThan(7); expect(status.remainingTokens).toBeLessThanOrEqual(10); done(); }, 60); }); }); describe('Concurrent Request Protection', () => { it('should handle concurrent requests safely', () => { const limiter = new RateLimiter({ maxRequests: 3, windowMs: 1000 }); // Simulate concurrent checks const results = []; for (let i = 0; i < 5; i++) { const status = limiter.checkLimit(); if (status.allowed) { limiter.consumeToken(); results.push(true); } else { results.push(false); } } // Should allow exactly 3 requests const allowedCount = results.filter(r => r).length; expect(allowedCount).toBe(3); }); }); });

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/DollhouseMCP/DollhouseMCP'

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