/**
* Unit tests for Code Mode Security Manager
*
* Tests code validation, rate limiting, result sanitization,
* and audit logging.
*/
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { CodeModeSecurityManager } from '../security.js';
import type { SandboxResult } from '../types.js';
describe('CodeModeSecurityManager', () => {
let security: CodeModeSecurityManager;
beforeEach(() => {
security = new CodeModeSecurityManager();
});
describe('validateCode()', () => {
it('should accept valid code', () => {
const result = security.validateCode(`
const tables = await pg.core.listTables();
return tables;
`);
expect(result.valid).toBe(true);
expect(result.errors).toHaveLength(0);
});
it('should reject empty code', () => {
const result = security.validateCode('');
expect(result.valid).toBe(false);
expect(result.errors.some(e => e.includes('non-empty'))).toBe(true);
});
it('should reject code exceeding max length', () => {
const longCode = 'x'.repeat(100000);
const result = security.validateCode(longCode);
expect(result.valid).toBe(false);
expect(result.errors.some(e => e.includes('exceeds maximum'))).toBe(true);
});
it('should block require() calls', () => {
const result = security.validateCode('const fs = require("fs");');
expect(result.valid).toBe(false);
expect(result.errors.some(e => e.includes('require'))).toBe(true);
});
it('should block process access', () => {
const result = security.validateCode('console.log(process.env);');
expect(result.valid).toBe(false);
expect(result.errors.some(e => e.includes('process'))).toBe(true);
});
it('should block eval()', () => {
const result = security.validateCode('eval("malicious code");');
expect(result.valid).toBe(false);
expect(result.errors.some(e => e.includes('eval'))).toBe(true);
});
it('should block Function constructor', () => {
const result = security.validateCode('new Function("return 1")();');
expect(result.valid).toBe(false);
expect(result.errors.some(e => e.includes('Function'))).toBe(true);
});
it('should block __proto__ manipulation', () => {
const result = security.validateCode('obj.__proto__ = malicious;');
expect(result.valid).toBe(false);
expect(result.errors.some(e => e.includes('__proto__'))).toBe(true);
});
it('should block child_process', () => {
const result = security.validateCode('require("child_process")');
expect(result.valid).toBe(false);
});
it('should block global access', () => {
const result = security.validateCode('global.process');
expect(result.valid).toBe(false);
expect(result.errors.some(e => e.includes('global'))).toBe(true);
});
it('should allow legitimate code patterns', () => {
const validCode = `
const results = [];
for (const table of await pg.core.listTables()) {
const stats = await pg.performance.tableStats({ table: table.name });
results.push({ name: table.name, rows: stats.row_count });
}
return results;
`;
const result = security.validateCode(validCode);
expect(result.valid).toBe(true);
});
});
describe('checkRateLimit()', () => {
it('should allow requests within limit', () => {
for (let i = 0; i < 5; i++) {
expect(security.checkRateLimit('client1')).toBe(true);
}
});
it('should track clients separately', () => {
for (let i = 0; i < 30; i++) {
security.checkRateLimit('client1');
security.checkRateLimit('client2');
}
expect(security.checkRateLimit('client1')).toBe(true);
expect(security.checkRateLimit('client2')).toBe(true);
});
it('should block when rate limit exceeded', () => {
const clientId = 'test-client';
// Exhaust rate limit (default is 60/min)
for (let i = 0; i < 60; i++) {
security.checkRateLimit(clientId);
}
expect(security.checkRateLimit(clientId)).toBe(false);
});
});
describe('getRateLimitRemaining()', () => {
it('should return max for new clients', () => {
const remaining = security.getRateLimitRemaining('new-client');
expect(remaining).toBe(60); // Default max executions per minute
});
it('should return reduced count after requests', () => {
const clientId = 'rate-limit-test';
security.checkRateLimit(clientId);
security.checkRateLimit(clientId);
security.checkRateLimit(clientId);
const remaining = security.getRateLimitRemaining(clientId);
expect(remaining).toBe(57); // 60 - 3
});
it('should return 0 when exhausted', () => {
const clientId = 'exhausted-client';
for (let i = 0; i < 60; i++) {
security.checkRateLimit(clientId);
}
expect(security.getRateLimitRemaining(clientId)).toBe(0);
});
});
describe('cleanupRateLimits()', () => {
it('should remove expired entries', () => {
// This just verifies the method runs without error
// Actual cleanup depends on timing
security.checkRateLimit('cleanup-client');
security.cleanupRateLimits();
// Method should complete without error
expect(true).toBe(true);
});
});
describe('sanitizeResult()', () => {
it('should return primitive values unchanged', () => {
expect(security.sanitizeResult(42)).toBe(42);
expect(security.sanitizeResult('hello')).toBe('hello');
expect(security.sanitizeResult(true)).toBe(true);
expect(security.sanitizeResult(null)).toBe(null);
});
it('should return small objects unchanged', () => {
const obj = { a: 1, b: 'test' };
expect(security.sanitizeResult(obj)).toEqual(obj);
});
it('should return small arrays unchanged', () => {
const arr = [1, 2, 3, 4, 5];
expect(security.sanitizeResult(arr)).toEqual(arr);
});
it('should handle large results', () => {
// Test that sanitizeResult handles large strings by returning something
const largeString = 'x'.repeat(100); // Smaller to test behavior
const result = security.sanitizeResult(largeString);
expect(result).toBeDefined();
});
});
describe('createExecutionRecord()', () => {
it('should create record from successful result', () => {
const successResult: SandboxResult = {
success: true,
result: { data: 'test' },
metrics: { wallTimeMs: 100, cpuTimeMs: 80, memoryUsedMb: 5 }
};
const record = security.createExecutionRecord(
'return pg.core.listTables();',
successResult,
true,
'client-123'
);
expect(record).toBeDefined();
expect(record.id).toBeDefined();
expect(record.codePreview).toBeDefined();
expect(record.timestamp).toBeDefined();
expect(record.clientId).toBe('client-123');
expect(record.readonly).toBe(true);
});
it('should create record from failed result', () => {
const failResult: SandboxResult = {
success: false,
error: 'Test error',
stack: 'Error stack trace',
metrics: { wallTimeMs: 10, cpuTimeMs: 5, memoryUsedMb: 1 }
};
const record = security.createExecutionRecord(
'throw new Error("test");',
failResult,
false
);
expect(record).toBeDefined();
expect(record.result.success).toBe(false);
expect(record.result.error).toBe('Test error');
});
});
describe('auditLog()', () => {
let consoleErrorSpy: ReturnType<typeof vi.spyOn>;
beforeEach(() => {
consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => { });
});
afterEach(() => {
consoleErrorSpy.mockRestore();
});
it('should log successful execution', () => {
const successResult: SandboxResult = {
success: true,
metrics: { wallTimeMs: 50, cpuTimeMs: 40, memoryUsedMb: 2 }
};
const record = security.createExecutionRecord(
'return 1;',
successResult,
true,
'test-client'
);
security.auditLog(record);
expect(consoleErrorSpy).toHaveBeenCalled();
});
it('should log failed execution', () => {
const failResult: SandboxResult = {
success: false,
error: 'Execution failed',
metrics: { wallTimeMs: 10, cpuTimeMs: 5, memoryUsedMb: 1 }
};
const record = security.createExecutionRecord(
'throw new Error();',
failResult,
false,
'test-client'
);
security.auditLog(record);
expect(consoleErrorSpy).toHaveBeenCalled();
});
});
});