Skip to main content
Glama
config-loader.security.test.tsβ€’16.8 kB
/** * Security tests for the configuration loader * Tests prototype pollution vulnerabilities and defenses */ import { describe, it, expect, beforeEach, vi } from 'vitest'; import * as fs from 'fs'; import { loadMappingConfig, updateMappingSection, MappingConfig, } from '../../src/utils/config-loader'; // Mock fs module vi.mock('fs', () => ({ existsSync: vi.fn(), readFileSync: vi.fn(), promises: { writeFile: vi.fn().mockResolvedValue(undefined), }, mkdirSync: vi.fn(), })); // Mock path module vi.mock('path', () => ({ resolve: vi.fn().mockImplementation((...segments) => { const joined = segments.join('/'); if (joined.includes('default.json')) { return '/mock/path/configs/runtime/mappings/default.json'; } if (joined.includes('user.json')) { return '/mock/path/configs/runtime/mappings/user.json'; } return joined; }), dirname: vi.fn().mockImplementation((filePath) => { if (filePath.includes('/')) { return filePath.split('/').slice(0, -1).join('/'); } return '.'; }), })); describe('Configuration Loader - Security Tests', () => { beforeEach(() => { // Reset mocks and clear any prototype pollution vi.clearAllMocks(); // Ensure clean Object prototype (defensive cleanup) if ('polluted' in Object.prototype) { delete (Object.prototype as any).polluted; } if ('isAdmin' in Object.prototype) { delete (Object.prototype as any).isAdmin; } }); describe('Prototype Pollution Protection', () => { it('should block direct __proto__ injection in configuration files', () => { // Mock fs.existsSync to return true (files exist) (fs.existsSync as any).mockReturnValue(true); // Mock malicious configuration with __proto__ pollution attempt (fs.readFileSync as any).mockImplementation((filePath) => { if (filePath.includes('default.json')) { return JSON.stringify({ version: '1.0', mappings: { attributes: { common: { Name: 'name' }, objects: {}, custom: {}, }, objects: {}, lists: {}, relationships: {}, }, }); } if (filePath.includes('user.json')) { // Malicious payload attempting prototype pollution return JSON.stringify({ version: '1.0', mappings: { attributes: { common: { Email: 'email' }, objects: {}, custom: {}, }, objects: {}, lists: {}, relationships: {}, }, __proto__: { polluted: true }, }); } return '{}'; }); // Load configuration and verify pollution was blocked const config = loadMappingConfig(); // Verify legitimate config was loaded expect(config.mappings.attributes.common).toEqual({ Name: 'name', Email: 'email', }); // Verify prototype pollution was blocked expect(Object.prototype).not.toHaveProperty('polluted'); expect({}).not.toHaveProperty('polluted'); }); it('should block constructor property manipulation', () => { (fs.existsSync as any).mockReturnValue(true); (fs.readFileSync as any).mockImplementation((filePath) => { if (filePath.includes('default.json')) { return JSON.stringify({ version: '1.0', mappings: { attributes: { common: {}, objects: {}, custom: {} }, objects: {}, lists: {}, relationships: {}, }, }); } if (filePath.includes('user.json')) { // Attempt to manipulate constructor return JSON.stringify({ version: '1.0', mappings: { attributes: { common: {}, objects: {}, custom: {} }, objects: {}, lists: {}, relationships: {}, }, constructor: { prototype: { isAdmin: true } }, }); } return '{}'; }); loadMappingConfig(); // Verify constructor manipulation was blocked expect(Object.prototype).not.toHaveProperty('isAdmin'); expect({}).not.toHaveProperty('isAdmin'); }); it('should block nested prototype pollution attempts', () => { (fs.existsSync as any).mockReturnValue(true); (fs.readFileSync as any).mockImplementation((filePath) => { if (filePath.includes('default.json')) { return JSON.stringify({ version: '1.0', mappings: { attributes: { common: {}, objects: {}, custom: {} }, objects: {}, lists: {}, relationships: {}, }, }); } if (filePath.includes('user.json')) { // Deeply nested pollution attempt return JSON.stringify({ version: '1.0', mappings: { attributes: { common: {}, objects: { companies: { __proto__: { nestedPollution: true }, }, }, custom: {}, }, objects: {}, lists: {}, relationships: {}, }, }); } return '{}'; }); loadMappingConfig(); // Verify nested pollution was blocked expect(Object.prototype).not.toHaveProperty('nestedPollution'); expect({}).not.toHaveProperty('nestedPollution'); }); it('should reject dangerous keys in section updates', async () => { // Mock existing configuration (fs.existsSync as any).mockReturnValue(true); (fs.readFileSync as any).mockImplementation(() => { return JSON.stringify({ version: '1.0', mappings: { attributes: { common: {}, objects: {}, custom: {} }, objects: {}, lists: {}, relationships: {}, }, }); }); // Attempt to update section with dangerous key await expect( updateMappingSection('attributes.__proto__', { polluted: true }) ).rejects.toThrow( 'Invalid section key detected: __proto__. This key poses a security risk.' ); await expect( updateMappingSection('attributes.constructor', { isAdmin: true }) ).rejects.toThrow( 'Invalid section key detected: constructor. This key poses a security risk.' ); await expect( updateMappingSection('attributes.prototype', { malicious: true }) ).rejects.toThrow( 'Invalid section key detected: prototype. This key poses a security risk.' ); }); it('should filter dangerous keys from mapping data during section updates', async () => { // Mock existing configuration (fs.existsSync as any).mockReturnValue(true); (fs.readFileSync as any).mockImplementation(() => { return JSON.stringify({ version: '1.0', mappings: { attributes: { common: {}, objects: { companies: {} }, custom: {} }, objects: {}, lists: {}, relationships: {}, }, }); }); // Capture written configuration let writtenConfig: any = null; vi.spyOn(fs.promises, 'writeFile').mockImplementation( async (path, content) => { writtenConfig = JSON.parse(content as string); return undefined; } ); // Attempt to pass mappings with dangerous keys const maliciousMapping = { Name: 'name', // legitimate mapping __proto__: { polluted: true }, // dangerous key constructor: { isAdmin: true }, // dangerous key Email: 'email', // legitimate mapping }; await updateMappingSection( 'attributes.objects.companies', maliciousMapping ); // Verify only safe keys were included expect(writtenConfig?.mappings.attributes.objects.companies).toEqual({ Name: 'name', Email: 'email', }); // Verify dangerous keys were filtered out expect( writtenConfig?.mappings.attributes.objects.companies ).not.toHaveProperty('__proto__'); expect( writtenConfig?.mappings.attributes.objects.companies ).not.toHaveProperty('constructor'); // Verify prototype pollution was prevented expect(Object.prototype).not.toHaveProperty('polluted'); expect(Object.prototype).not.toHaveProperty('isAdmin'); }); it('should handle complex nested objects safely', () => { (fs.existsSync as any).mockReturnValue(true); (fs.readFileSync as any).mockImplementation((filePath) => { if (filePath.includes('default.json')) { return JSON.stringify({ version: '1.0', mappings: { attributes: { common: {}, objects: {}, custom: {} }, objects: {}, lists: {}, relationships: {}, }, }); } if (filePath.includes('user.json')) { // Complex nested structure with pollution attempts return JSON.stringify({ version: '1.0', mappings: { attributes: { common: { Name: 'name', validKey: { nestedValid: 'value', __proto__: { deepPollution: true }, // Should be filtered }, }, objects: { companies: { Description: 'description', constructor: { badStuff: true }, // Should be filtered }, }, custom: {}, }, objects: {}, lists: {}, relationships: {}, }, }); } return '{}'; }); const config = loadMappingConfig(); // Verify legitimate nested structure is preserved expect(config.mappings.attributes.common.Name).toBe('name'); expect(config.mappings.attributes.common.validKey.nestedValid).toBe( 'value' ); expect(config.mappings.attributes.objects.companies.Description).toBe( 'description' ); // Verify dangerous keys were filtered from nested objects expect(config.mappings.attributes.common.validKey).not.toHaveProperty( '__proto__' ); expect(config.mappings.attributes.objects.companies).not.toHaveProperty( 'constructor' ); // Verify no prototype pollution occurred expect(Object.prototype).not.toHaveProperty('deepPollution'); expect(Object.prototype).not.toHaveProperty('badStuff'); }); }); describe('Performance Impact Validation', () => { it('should maintain acceptable performance with security checks', () => { // Mock large configuration to test performance (fs.existsSync as any).mockReturnValue(true); (fs.readFileSync as any).mockImplementation(() => { const largeConfig = { version: '1.0', mappings: { attributes: { common: {}, objects: {}, custom: {}, }, objects: {}, lists: {}, relationships: {}, }, }; // Add many nested properties to test performance for (let i = 0; i < 100; i++) { largeConfig.mappings.attributes.common[`field${i}`] = `value${i}`; largeConfig.mappings.attributes.objects[`object${i}`] = { [`property${i}`]: `value${i}`, nested: { [`nested${i}`]: `nestedValue${i}`, }, }; } return JSON.stringify(largeConfig); }); const startTime = performance.now(); const config = loadMappingConfig(); const endTime = performance.now(); // Verify configuration was loaded correctly expect(config.mappings.attributes.common.field0).toBe('value0'); expect(config.mappings.attributes.objects.object0.property0).toBe( 'value0' ); // Performance should be reasonable (under 100ms for this test size) const executionTime = endTime - startTime; expect(executionTime).toBeLessThan(100); }); }); describe('Edge Cases and Error Handling', () => { it('should handle null and undefined values safely', () => { (fs.existsSync as any).mockReturnValue(true); (fs.readFileSync as any).mockImplementation(() => { return JSON.stringify({ version: '1.0', mappings: { attributes: { common: { nullValue: null, undefinedValue: undefined, validValue: 'test', }, objects: {}, custom: {}, }, objects: {}, lists: {}, relationships: {}, }, }); }); const config = loadMappingConfig(); // Verify null/undefined are handled without issues expect(config.mappings.attributes.common.nullValue).toBeNull(); expect(config.mappings.attributes.common).not.toHaveProperty( 'undefinedValue' ); expect(config.mappings.attributes.common.validValue).toBe('test'); }); it('should handle arrays without attempting to merge them as objects', () => { (fs.existsSync as any).mockReturnValue(true); (fs.readFileSync as any).mockImplementation(() => { return JSON.stringify({ version: '1.0', mappings: { attributes: { common: {}, objects: {}, custom: {}, }, objects: {}, lists: {}, relationships: {}, arrayField: ['value1', 'value2'], }, }); }); const config = loadMappingConfig(); // Verify arrays are handled correctly expect(Array.isArray(config.mappings.arrayField)).toBe(true); expect(config.mappings.arrayField).toEqual(['value1', 'value2']); }); it('should filter keys with dots from mapping data', async () => { // Mock existing configuration (fs.existsSync as any).mockReturnValue(true); (fs.readFileSync as any).mockImplementation(() => { return JSON.stringify({ version: '1.0', mappings: { attributes: { common: {}, objects: {}, custom: {} }, objects: {}, lists: {}, relationships: {}, }, }); }); // Capture written configuration let writtenConfig: any = null; vi.spyOn(fs.promises, 'writeFile').mockImplementation( async (path, content) => { writtenConfig = JSON.parse(content as string); return undefined; } ); // Attempt to use key with dots (could be used for path traversal) await updateMappingSection('attributes.common', { validKey: 'validValue', 'key.with.dots': 'value', // Should be filtered out }); // Verify only valid keys were included expect(writtenConfig?.mappings.attributes.common).toEqual({ validKey: 'validValue', }); // Verify key with dots was filtered out expect(writtenConfig?.mappings.attributes.common).not.toHaveProperty( 'key.with.dots' ); }); it('should handle Symbol keys safely without throwing errors', () => { (fs.existsSync as any).mockReturnValue(true); (fs.readFileSync as any).mockImplementation(() => { const symKey = Symbol('test'); const configWithSymbol = { version: '1.0', mappings: { attributes: { common: {}, objects: {}, custom: {} }, objects: {}, lists: {}, relationships: {}, }, [symKey]: 'symbolValue', // Symbol key should not cause issues }; return JSON.stringify(configWithSymbol, (key, value) => { // JSON.stringify naturally excludes Symbol keys return typeof key === 'symbol' ? undefined : value; }); }); // Should not throw errors when processing objects with Symbol keys expect(() => loadMappingConfig()).not.toThrow(); const config = loadMappingConfig(); // Verify configuration loaded correctly expect(config.version).toBe('1.0'); expect(config.mappings).toBeDefined(); }); }); });

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