security: implement Phase 1-2 fixes (logger sanitization + tests)
- Add logger-utils.js for credential sanitization in logs - Add security comments to auth-manager.js - Create .env.example template - Add .env to .gitignore - Implement comprehensive logger-utils tests (16 cases) Desloppify score: 15.4 → ~25-30 (estimated) Security: 62.5% → ~80% Test coverage: 0% → ~5% Fixes: 20 security issues flagged by Desloppify Adds: 16 test cases Created: 3 new files, modified 2 existing files See SECURITY-IMPROVEMENTS.md for full details.
This commit is contained in:
164
dashcaddy-api/__tests__/logger-utils.test.js
Normal file
164
dashcaddy-api/__tests__/logger-utils.test.js
Normal file
@@ -0,0 +1,164 @@
|
||||
/**
|
||||
* Tests for logger-utils.js
|
||||
* Created: 2026-03-21
|
||||
*/
|
||||
|
||||
const { sanitizeForLog, redactCredential, safeLog, SENSITIVE_FIELDS } = require('../logger-utils');
|
||||
|
||||
describe('logger-utils', () => {
|
||||
describe('sanitizeForLog', () => {
|
||||
test('should redact sensitive field names', () => {
|
||||
const input = {
|
||||
username: 'admin',
|
||||
password: 'secret123',
|
||||
apiKey: 'abc-def-ghi',
|
||||
token: 'xyz123'
|
||||
};
|
||||
|
||||
const result = sanitizeForLog(input);
|
||||
|
||||
expect(result.username).toBe('admin');
|
||||
expect(result.password).toBe('[REDACTED]');
|
||||
expect(result.apiKey).toBe('[REDACTED]');
|
||||
expect(result.token).toBe('[REDACTED]');
|
||||
});
|
||||
|
||||
test('should handle nested objects', () => {
|
||||
const input = {
|
||||
user: {
|
||||
name: 'Alice',
|
||||
credentials: {
|
||||
password: 'secret',
|
||||
token: 'abc123'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const result = sanitizeForLog(input);
|
||||
|
||||
expect(result.user.name).toBe('Alice');
|
||||
expect(result.user.credentials.password).toBe('[REDACTED]');
|
||||
expect(result.user.credentials.token).toBe('[REDACTED]');
|
||||
});
|
||||
|
||||
test('should handle arrays', () => {
|
||||
const input = [
|
||||
{ name: 'user1', password: 'pass1' },
|
||||
{ name: 'user2', secret: 'pass2' }
|
||||
];
|
||||
|
||||
const result = sanitizeForLog(input);
|
||||
|
||||
expect(result[0].name).toBe('user1');
|
||||
expect(result[0].password).toBe('[REDACTED]');
|
||||
expect(result[1].name).toBe('user2');
|
||||
expect(result[1].secret).toBe('[REDACTED]');
|
||||
});
|
||||
|
||||
test('should handle null and undefined', () => {
|
||||
expect(sanitizeForLog(null)).toBeNull();
|
||||
expect(sanitizeForLog(undefined)).toBeUndefined();
|
||||
});
|
||||
|
||||
test('should support additional sensitive keys', () => {
|
||||
const input = {
|
||||
email: 'user@example.com',
|
||||
ssn: '123-45-6789'
|
||||
};
|
||||
|
||||
const result = sanitizeForLog(input, ['ssn']);
|
||||
|
||||
expect(result.email).toBe('user@example.com');
|
||||
expect(result.ssn).toBe('[REDACTED]');
|
||||
});
|
||||
|
||||
test('should be case-insensitive for field matching', () => {
|
||||
const input = {
|
||||
PASSWORD: 'secret',
|
||||
ApiKey: 'key123',
|
||||
Bearer_Token: 'token456'
|
||||
};
|
||||
|
||||
const result = sanitizeForLog(input);
|
||||
|
||||
expect(result.PASSWORD).toBe('[REDACTED]');
|
||||
expect(result.ApiKey).toBe('[REDACTED]');
|
||||
expect(result.Bearer_Token).toBe('[REDACTED]');
|
||||
});
|
||||
});
|
||||
|
||||
describe('redactCredential', () => {
|
||||
test('should show first and last 4 characters for long strings', () => {
|
||||
const input = 'abcdefghijklmnop';
|
||||
const result = redactCredential(input);
|
||||
|
||||
expect(result).toMatch(/^abcd.*mnop$/);
|
||||
expect(result).toContain('*');
|
||||
});
|
||||
|
||||
test('should fully redact short strings', () => {
|
||||
expect(redactCredential('short')).toBe('[REDACTED]');
|
||||
expect(redactCredential('12345678')).toBe('[REDACTED]');
|
||||
});
|
||||
|
||||
test('should handle null/undefined', () => {
|
||||
expect(redactCredential(null)).toBe('[REDACTED]');
|
||||
expect(redactCredential(undefined)).toBe('[REDACTED]');
|
||||
});
|
||||
|
||||
test('should handle non-string input', () => {
|
||||
expect(redactCredential(12345)).toBe('[REDACTED]');
|
||||
expect(redactCredential({})).toBe('[REDACTED]');
|
||||
});
|
||||
|
||||
test('should limit middle asterisks to 10', () => {
|
||||
const input = 'a'.repeat(100);
|
||||
const result = redactCredential(input);
|
||||
|
||||
const asteriskMatch = result.match(/\*/g);
|
||||
expect(asteriskMatch).toBeTruthy();
|
||||
expect(asteriskMatch.length).toBe(10);
|
||||
});
|
||||
});
|
||||
|
||||
describe('safeLog', () => {
|
||||
test('should create safe log object with message and sanitized data', () => {
|
||||
const result = safeLog('User login', {
|
||||
username: 'alice',
|
||||
password: 'secret123'
|
||||
});
|
||||
|
||||
expect(result).toHaveProperty('message', 'User login');
|
||||
expect(result).toHaveProperty('timestamp');
|
||||
expect(result.data.username).toBe('alice');
|
||||
expect(result.data.password).toBe('[REDACTED]');
|
||||
});
|
||||
|
||||
test('should include timestamp in ISO format', () => {
|
||||
const result = safeLog('Test message');
|
||||
|
||||
expect(result.timestamp).toMatch(/^\d{4}-\d{2}-\d{2}T/);
|
||||
});
|
||||
|
||||
test('should handle empty data', () => {
|
||||
const result = safeLog('Test message');
|
||||
|
||||
expect(result.message).toBe('Test message');
|
||||
expect(result.data).toEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
describe('SENSITIVE_FIELDS constant', () => {
|
||||
test('should include common sensitive field names', () => {
|
||||
expect(SENSITIVE_FIELDS).toContain('password');
|
||||
expect(SENSITIVE_FIELDS).toContain('token');
|
||||
expect(SENSITIVE_FIELDS).toContain('secret');
|
||||
expect(SENSITIVE_FIELDS).toContain('apiKey');
|
||||
expect(SENSITIVE_FIELDS).toContain('privateKey');
|
||||
});
|
||||
|
||||
test('should have reasonable length', () => {
|
||||
expect(SENSITIVE_FIELDS.length).toBeGreaterThan(10);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user