Skip to main content
Glama
secure-error-handler.integration.test.tsβ€’12 kB
/** * Integration tests for secure error handler - validates actual sanitization behavior * These tests use REAL implementations (no mocks) to ensure security requirements are met */ import { describe, it, expect } from 'vitest'; import { SecureApiError, createSecureErrorResponse, createSecureToolErrorResult, } from '@/utils/secure-error-handler.js'; import type { ErrorContext } from '@/utils/secure-error-handler.js'; describe('Secure Error Handler - Integration Tests (No Mocks)', () => { const mockContext: ErrorContext = { operation: 'test-operation', module: 'test-module', resourceType: 'test-resource', correlationId: 'corr-123', requestId: 'req-456', }; describe('XSS Prevention - Issue #836 Acceptance Criteria', () => { const xssPayloads = [ '<script>alert("xss")</script>', '<img src=x onerror=alert("xss")>', 'javascript:alert("xss")', '<svg onload=alert("xss")>', '"><script>alert("xss")</script>', '<iframe src="javascript:alert(\'xss\')">', '<body onload=alert("xss")>', ]; it.each(xssPayloads)( 'should sanitize XSS payload in error message: %s', (payload) => { const error = new Error(payload); const response = createSecureErrorResponse(error, mockContext); // Error message should not contain any HTML/script tags expect(response.error.message).not.toContain('<script'); expect(response.error.message).not.toContain('</script'); expect(response.error.message).not.toContain('<img'); expect(response.error.message).not.toContain('onerror'); expect(response.error.message).not.toContain('javascript:'); expect(response.error.message).not.toContain('<svg'); expect(response.error.message).not.toContain('onload'); expect(response.error.message).not.toContain('<iframe'); expect(response.error.message).not.toContain('<body'); expect(response.error.message).not.toContain('alert('); } ); it('should sanitize XSS in SecureApiError', () => { const xssMessage = '<script>alert("xss")</script>'; const error = new SecureApiError( xssMessage, 400, 'validation_error', mockContext ); expect(error.message).not.toContain('<script'); expect(error.message).not.toContain('alert('); }); it('should sanitize XSS in tool error results', () => { const xssError = new Error('<script>alert("xss")</script>'); const result = createSecureToolErrorResult(xssError, mockContext); expect(result.content[0].text).not.toContain('<script'); expect(result.content[0].text).not.toContain('alert('); expect(result.isError).toBe(true); }); }); describe('Stack Trace Exposure Prevention - Issue #838 Acceptance Criteria', () => { it('should never include stack traces in error response', () => { const error = new Error('Test error with stack'); const response = createSecureErrorResponse(error, mockContext); // Check response object doesn't have stack property expect(response.error).not.toHaveProperty('stack'); // Check serialized response doesn't contain stack trace patterns const serialized = JSON.stringify(response); expect(serialized).not.toContain('at '); expect(serialized).not.toContain('Error:'); expect(serialized).not.toMatch(/\.ts:\d+/); // File:line patterns }); it('should not expose stack traces in SecureApiError', () => { const originalError = new Error('Test error'); const secureError = new SecureApiError( originalError.message, 500, 'internal_error', mockContext, originalError ); const json = secureError.toJSON(); expect(json).not.toHaveProperty('stack'); expect(JSON.stringify(json)).not.toContain('at '); }); it('should not expose stack traces in tool error results', () => { const error = new Error('Test error'); const result = createSecureToolErrorResult(error, mockContext); expect(result.error).not.toHaveProperty('stack'); expect(JSON.stringify(result)).not.toContain('at '); }); }); describe('Information Disclosure Prevention - Issue #838 Acceptance Criteria', () => { const sensitivePatterns = [ '/src/handlers/prompts/handlers.ts:42', '/Users/developer/project/file.ts', 'C:\\Users\\developer\\project\\file.ts', 'at Object.<anonymous> (/src/file.ts:10:5)', 'file:///Users/project/src/index.ts', ]; it.each(sensitivePatterns)( 'should not expose file paths in error message: %s', (pattern) => { const error = new Error(pattern); const response = createSecureErrorResponse(error, mockContext); // Should not contain file path patterns expect(response.error.message).not.toContain('/src/'); expect(response.error.message).not.toContain('.ts'); expect(response.error.message).not.toContain('file://'); expect(response.error.message).not.toContain('\\Users\\'); expect(response.error.message).not.toMatch(/:\d+:\d+/); // line:col } ); it('should not expose internal error details', () => { const internalError = new Error( 'Database connection failed: host=localhost port=5432 user=admin' ); const response = createSecureErrorResponse(internalError, mockContext); // Should provide generic message, not internal details expect(response.error.message).not.toContain('localhost'); expect(response.error.message).not.toContain('5432'); expect(response.error.message).not.toContain('admin'); }); it('should not expose sensitive context in error responses', () => { const sensitiveContext: ErrorContext = { ...mockContext, userId: 'user-secret-id', apiKey: 'sk_live_1234567890', password: 'supersecret', }; const error = new Error('Operation failed'); const response = createSecureErrorResponse(error, sensitiveContext); // Should not include sensitive context fields expect(response.error).not.toHaveProperty('apiKey'); expect(response.error).not.toHaveProperty('password'); expect(JSON.stringify(response)).not.toContain('sk_live'); expect(JSON.stringify(response)).not.toContain('supersecret'); }); }); describe('Status Code Resolution - Real Implementation', () => { it('should resolve status codes from Axios-like errors', () => { const axiosError = { response: { status: 401 }, message: 'Unauthorized', }; const response = createSecureErrorResponse(axiosError, mockContext); expect(response.error.statusCode).toBe(401); expect(response.error.type).toBe('authentication_error'); }); it('should resolve status codes from StructuredHttpError', () => { const httpError = { statusCode: 429, message: 'Rate limit exceeded', }; const response = createSecureErrorResponse(httpError, mockContext); expect(response.error.statusCode).toBe(429); expect(response.error.type).toBe('rate_limit'); }); it('should use fallback for unknown errors', () => { const unknownError = { foo: 'bar' }; const response = createSecureErrorResponse(unknownError, mockContext); // Unknown errors get classified as server_error when statusCode >= 500 expect(response.error.statusCode).toBe(500); expect(response.error.type).toBe('server_error'); expect(response.error.suggestion).toContain('Retry shortly'); }); }); describe('Correlation ID and Request Tracking', () => { it('should include correlation ID in error responses', () => { const error = new Error('Test error'); const response = createSecureErrorResponse(error, { ...mockContext, correlationId: 'test-correlation-id', }); expect(response.error.correlationId).toBe('test-correlation-id'); }); it('should include request ID in error responses', () => { const error = new Error('Test error'); const response = createSecureErrorResponse(error, { ...mockContext, requestId: 'test-request-id', }); expect(response.error.requestId).toBe('test-request-id'); }); it('should include both IDs in tool error results', () => { const error = new Error('Test error'); const result = createSecureToolErrorResult(error, { ...mockContext, correlationId: 'corr-123', requestId: 'req-456', }); expect(result.error).toMatchObject({ correlationId: 'corr-123', requestId: 'req-456', }); expect(result.content[0].text).toContain('Reference ID: corr-123'); }); }); describe('Error Suggestions and Guidance', () => { it('should provide appropriate suggestions for authentication errors', () => { // Error with message property is properly classified const error = { response: { status: 401 }, message: 'Unauthorized' }; const response = createSecureErrorResponse(error, mockContext); expect(response.error.type).toBe('authentication_error'); expect(response.error.suggestion).toContain('credentials'); }); it('should provide appropriate suggestions for rate limit errors', () => { const error = { statusCode: 429 }; const response = createSecureErrorResponse(error, mockContext); expect(response.error.suggestion).toContain('Wait'); }); it('should include suggestions in tool error results', () => { const error = { statusCode: 404 }; const result = createSecureToolErrorResult(error, mockContext); expect(result.error).toHaveProperty('suggestion'); expect(result.content[0].text).toContain('Next steps:'); }); }); describe('Error Type Consistency', () => { it('should produce consistent error types across response and tool result', () => { const error = { statusCode: 401, message: 'Unauthorized' }; const context: ErrorContext = { module: 'test-module', operation: 'test-operation', correlationId: 'test-corr-id', requestId: 'test-req-id', }; const response = createSecureErrorResponse(error, context); const toolResult = createSecureToolErrorResult(error, context); // Both should have the same error type expect(response.error.type).toBe( (toolResult.error as Record<string, unknown>).type ); // Status codes should match (note: toolResult uses 'code' property) expect(response.error.statusCode).toBe( (toolResult.error as Record<string, unknown>).code ); // Both should have correlation IDs expect(response.error.correlationId).toBe('test-corr-id'); expect((toolResult.error as Record<string, unknown>).correlationId).toBe( 'test-corr-id' ); }); it('should maintain consistency for various error types', () => { const testCases = [ { statusCode: 400, expectedType: 'validation_error' }, { statusCode: 401, expectedType: 'authentication_error' }, { statusCode: 403, expectedType: 'authorization_error' }, { statusCode: 404, expectedType: 'not_found' }, { statusCode: 429, expectedType: 'rate_limit' }, { statusCode: 500, expectedType: 'server_error' }, ]; testCases.forEach(({ statusCode, expectedType }) => { const error = { statusCode, message: 'Test error' }; const response = createSecureErrorResponse(error, mockContext); const toolResult = createSecureToolErrorResult(error, mockContext); expect(response.error.type).toBe(expectedType); expect((toolResult.error as Record<string, unknown>).type).toBe( expectedType ); }); }); }); });

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/kesslerio/attio-mcp-server'

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