198 lines
6.3 KiB
TypeScript
198 lines
6.3 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
import { Config } from './config';
|
|
|
|
// Mock the config and server modules
|
|
const mockConfig: Config = {
|
|
github: {
|
|
token: 'ghp_super_secret_token_123',
|
|
repositories: [
|
|
{ owner: 'test-owner', name: 'test-repo' },
|
|
{ owner: 'another-owner', name: 'another-repo' }
|
|
]
|
|
},
|
|
server: {
|
|
port: 3001,
|
|
host: '0.0.0.0'
|
|
},
|
|
cache: {
|
|
timeoutSeconds: 300
|
|
}
|
|
};
|
|
|
|
// Import the getPublicConfig function (we'll need to export it for testing)
|
|
// For now, let's simulate what the public config should look like
|
|
function getPublicConfig(config: Config) {
|
|
return {
|
|
repositories: config.github.repositories.map((repo) => ({
|
|
owner: repo.owner,
|
|
name: repo.name,
|
|
full_name: `${repo.owner}/${repo.name}`
|
|
})),
|
|
cache: {
|
|
timeoutSeconds: config.cache?.timeoutSeconds || 300
|
|
},
|
|
repositoryCount: config.github.repositories.length
|
|
};
|
|
}
|
|
|
|
describe('Security Tests', () => {
|
|
describe('Config API Security', () => {
|
|
it('should not expose sensitive information in public config', () => {
|
|
const publicConfig = getPublicConfig(mockConfig);
|
|
|
|
// Ensure sensitive data is not included
|
|
expect(publicConfig).not.toHaveProperty('github');
|
|
expect(publicConfig).not.toHaveProperty('server');
|
|
expect(publicConfig).not.toHaveProperty('token');
|
|
|
|
// Ensure no token is accidentally included anywhere
|
|
const configString = JSON.stringify(publicConfig);
|
|
expect(configString).not.toContain('ghp_');
|
|
expect(configString).not.toContain('token');
|
|
expect(configString).not.toContain('secret');
|
|
});
|
|
|
|
it('should only expose safe repository information', () => {
|
|
const publicConfig = getPublicConfig(mockConfig);
|
|
|
|
expect(publicConfig.repositories).toHaveLength(2);
|
|
expect(publicConfig.repositories[0]).toEqual({
|
|
owner: 'test-owner',
|
|
name: 'test-repo',
|
|
full_name: 'test-owner/test-repo'
|
|
});
|
|
expect(publicConfig.repositories[1]).toEqual({
|
|
owner: 'another-owner',
|
|
name: 'another-repo',
|
|
full_name: 'another-owner/another-repo'
|
|
});
|
|
});
|
|
|
|
it('should expose safe cache configuration', () => {
|
|
const publicConfig = getPublicConfig(mockConfig);
|
|
|
|
expect(publicConfig.cache).toEqual({
|
|
timeoutSeconds: 300
|
|
});
|
|
});
|
|
|
|
it('should include repository count for UI', () => {
|
|
const publicConfig = getPublicConfig(mockConfig);
|
|
|
|
expect(publicConfig.repositoryCount).toBe(2);
|
|
});
|
|
|
|
it('should handle missing cache configuration safely', () => {
|
|
const configWithoutCache = {
|
|
...mockConfig,
|
|
cache: undefined
|
|
};
|
|
|
|
const publicConfig = getPublicConfig(configWithoutCache);
|
|
|
|
expect(publicConfig.cache).toEqual({
|
|
timeoutSeconds: 300
|
|
});
|
|
});
|
|
|
|
it('should never expose server configuration', () => {
|
|
const publicConfig = getPublicConfig(mockConfig);
|
|
|
|
expect(publicConfig).not.toHaveProperty('port');
|
|
expect(publicConfig).not.toHaveProperty('host');
|
|
expect(publicConfig).not.toHaveProperty('server');
|
|
});
|
|
|
|
it('should never expose GitHub token', () => {
|
|
const publicConfig = getPublicConfig(mockConfig);
|
|
|
|
// Check that token is never exposed in any form
|
|
const configString = JSON.stringify(publicConfig);
|
|
expect(configString).not.toContain('ghp_super_secret_token_123');
|
|
expect(configString).not.toContain('token');
|
|
});
|
|
|
|
it('should handle repository with potential sensitive data', () => {
|
|
const configWithSensitiveRepo = {
|
|
...mockConfig,
|
|
github: {
|
|
...mockConfig.github,
|
|
repositories: [
|
|
{
|
|
owner: 'test-owner',
|
|
name: 'test-repo',
|
|
// @ts-ignore - testing potential sensitive data
|
|
token: 'per-repo-token-123'
|
|
}
|
|
]
|
|
}
|
|
};
|
|
|
|
const publicConfig = getPublicConfig(configWithSensitiveRepo);
|
|
|
|
expect(publicConfig.repositories[0]).toEqual({
|
|
owner: 'test-owner',
|
|
name: 'test-repo',
|
|
full_name: 'test-owner/test-repo'
|
|
});
|
|
|
|
// Ensure repository-specific sensitive data is not exposed
|
|
expect(publicConfig.repositories[0]).not.toHaveProperty('token');
|
|
});
|
|
});
|
|
|
|
describe('Safe Logging', () => {
|
|
it('should log config changes without sensitive data', () => {
|
|
const consoleSpy = vi.spyOn(console, 'log');
|
|
|
|
// Simulate the logConfigChange function
|
|
function logConfigChange(config: Config) {
|
|
console.log(`📊 Configuration loaded: ${config.github.repositories.length} repositories`);
|
|
console.log(`💾 Cache timeout: ${config.cache?.timeoutSeconds || 300} seconds`);
|
|
}
|
|
|
|
logConfigChange(mockConfig);
|
|
|
|
const logCalls = consoleSpy.mock.calls.flat();
|
|
const allLogs = logCalls.join(' ');
|
|
|
|
// Ensure no sensitive data is logged
|
|
expect(allLogs).not.toContain('ghp_super_secret_token_123');
|
|
expect(allLogs).not.toContain('token');
|
|
expect(allLogs).not.toContain('3001'); // port
|
|
expect(allLogs).not.toContain('0.0.0.0'); // host
|
|
|
|
// Ensure safe data is logged
|
|
expect(allLogs).toContain('2 repositories');
|
|
expect(allLogs).toContain('300 seconds');
|
|
|
|
consoleSpy.mockRestore();
|
|
});
|
|
});
|
|
|
|
describe('Data Sanitization', () => {
|
|
it('should sanitize any potential sensitive data in repository names', () => {
|
|
const configWithSensitiveNames = {
|
|
...mockConfig,
|
|
github: {
|
|
...mockConfig.github,
|
|
repositories: [
|
|
{ owner: 'test-owner', name: 'repo-with-token-ghp123' },
|
|
{ owner: 'user', name: 'secret-project' }
|
|
]
|
|
}
|
|
};
|
|
|
|
const publicConfig = getPublicConfig(configWithSensitiveNames);
|
|
|
|
// Repository names should be preserved as-is (they're not sensitive themselves)
|
|
// but we should ensure the filtering process doesn't accidentally expose other data
|
|
expect(publicConfig.repositories[0].name).toBe('repo-with-token-ghp123');
|
|
expect(publicConfig.repositories[1].name).toBe('secret-project');
|
|
|
|
// But the actual token should never be exposed
|
|
const configString = JSON.stringify(publicConfig);
|
|
expect(configString).not.toContain('ghp_super_secret_token_123');
|
|
});
|
|
});
|
|
}); |