Files
flipstone-radar/server/file-security.test.ts

344 lines
8.6 KiB
TypeScript

import { describe, it, expect } from 'vitest';
// Mock Express request object
interface MockRequest {
path: string;
url: string;
ip: string;
headers: Record<string, string>;
}
interface MockResponse {
statusCode: number;
headers: Record<string, string>;
body: string;
status: (code: number) => MockResponse;
json: (data: any) => MockResponse;
setHeader: (name: string, value: string) => void;
end: (data: string) => void;
}
// Simulate the security middleware logic
function createSecurityMiddleware() {
const sensitiveFiles = [
'/config.json',
'/config.example.json',
'/.env',
'/package.json',
'/package-lock.json',
'/tsconfig.json',
'/server/',
'/.git/',
'/node_modules/',
'/dist/',
'/build/',
'/.vscode/',
'/.idea/',
'/README.md',
'/CLAUDE.md'
];
return (req: MockRequest, res: MockResponse, next: () => void) => {
const normalizedPath = req.path.toLowerCase();
// Check if the request is for a sensitive file or directory
const isSensitiveFile = sensitiveFiles.some(sensitiveFile =>
normalizedPath === sensitiveFile.toLowerCase() ||
normalizedPath.startsWith(sensitiveFile.toLowerCase())
);
if (isSensitiveFile) {
console.warn(`🚫 Blocked access to sensitive file: ${req.path} from ${req.ip}`);
res.statusCode = 403;
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify({
error: 'Access denied',
message: 'This resource is not available'
}));
return;
}
next();
};
}
// Create mock response object
function createMockResponse(): MockResponse {
const response: MockResponse = {
statusCode: 200,
headers: {},
body: '',
status: (code: number) => {
response.statusCode = code;
return response;
},
json: (data: any) => {
response.body = JSON.stringify(data);
return response;
},
setHeader: (name: string, value: string) => {
response.headers[name] = value;
},
end: (data: string) => {
response.body = data;
}
};
return response;
}
describe('File Security Middleware', () => {
const securityMiddleware = createSecurityMiddleware();
describe('Sensitive File Protection', () => {
it('should block access to config.json', () => {
const req: MockRequest = {
path: '/config.json',
url: '/config.json',
ip: '127.0.0.1',
headers: {}
};
const res = createMockResponse();
const next = vi.fn();
securityMiddleware(req, res, next);
expect(res.statusCode).toBe(403);
expect(res.headers['Content-Type']).toBe('application/json');
expect(res.body).toContain('Access denied');
expect(next).not.toHaveBeenCalled();
});
it('should block access to config.example.json', () => {
const req: MockRequest = {
path: '/config.example.json',
url: '/config.example.json',
ip: '127.0.0.1',
headers: {}
};
const res = createMockResponse();
const next = vi.fn();
securityMiddleware(req, res, next);
expect(res.statusCode).toBe(403);
expect(next).not.toHaveBeenCalled();
});
it('should block access to .env files', () => {
const req: MockRequest = {
path: '/.env',
url: '/.env',
ip: '127.0.0.1',
headers: {}
};
const res = createMockResponse();
const next = vi.fn();
securityMiddleware(req, res, next);
expect(res.statusCode).toBe(403);
expect(next).not.toHaveBeenCalled();
});
it('should block access to package.json', () => {
const req: MockRequest = {
path: '/package.json',
url: '/package.json',
ip: '127.0.0.1',
headers: {}
};
const res = createMockResponse();
const next = vi.fn();
securityMiddleware(req, res, next);
expect(res.statusCode).toBe(403);
expect(next).not.toHaveBeenCalled();
});
it('should block access to server directory', () => {
const req: MockRequest = {
path: '/server/index.ts',
url: '/server/index.ts',
ip: '127.0.0.1',
headers: {}
};
const res = createMockResponse();
const next = vi.fn();
securityMiddleware(req, res, next);
expect(res.statusCode).toBe(403);
expect(next).not.toHaveBeenCalled();
});
it('should block access to git directory', () => {
const req: MockRequest = {
path: '/.git/config',
url: '/.git/config',
ip: '127.0.0.1',
headers: {}
};
const res = createMockResponse();
const next = vi.fn();
securityMiddleware(req, res, next);
expect(res.statusCode).toBe(403);
expect(next).not.toHaveBeenCalled();
});
it('should block access to node_modules', () => {
const req: MockRequest = {
path: '/node_modules/package/index.js',
url: '/node_modules/package/index.js',
ip: '127.0.0.1',
headers: {}
};
const res = createMockResponse();
const next = vi.fn();
securityMiddleware(req, res, next);
expect(res.statusCode).toBe(403);
expect(next).not.toHaveBeenCalled();
});
it('should block access to README.md', () => {
const req: MockRequest = {
path: '/README.md',
url: '/README.md',
ip: '127.0.0.1',
headers: {}
};
const res = createMockResponse();
const next = vi.fn();
securityMiddleware(req, res, next);
expect(res.statusCode).toBe(403);
expect(next).not.toHaveBeenCalled();
});
it('should block access to CLAUDE.md', () => {
const req: MockRequest = {
path: '/CLAUDE.md',
url: '/CLAUDE.md',
ip: '127.0.0.1',
headers: {}
};
const res = createMockResponse();
const next = vi.fn();
securityMiddleware(req, res, next);
expect(res.statusCode).toBe(403);
expect(next).not.toHaveBeenCalled();
});
it('should be case insensitive', () => {
const req: MockRequest = {
path: '/CONFIG.JSON',
url: '/CONFIG.JSON',
ip: '127.0.0.1',
headers: {}
};
const res = createMockResponse();
const next = vi.fn();
securityMiddleware(req, res, next);
expect(res.statusCode).toBe(403);
expect(next).not.toHaveBeenCalled();
});
});
describe('Allowed File Access', () => {
it('should allow access to API endpoints', () => {
const req: MockRequest = {
path: '/api/workflow-runs',
url: '/api/workflow-runs',
ip: '127.0.0.1',
headers: {}
};
const res = createMockResponse();
const next = vi.fn();
securityMiddleware(req, res, next);
expect(res.statusCode).toBe(200);
expect(next).toHaveBeenCalled();
});
it('should allow access to static assets', () => {
const req: MockRequest = {
path: '/assets/main.js',
url: '/assets/main.js',
ip: '127.0.0.1',
headers: {}
};
const res = createMockResponse();
const next = vi.fn();
securityMiddleware(req, res, next);
expect(res.statusCode).toBe(200);
expect(next).toHaveBeenCalled();
});
it('should allow access to root path', () => {
const req: MockRequest = {
path: '/',
url: '/',
ip: '127.0.0.1',
headers: {}
};
const res = createMockResponse();
const next = vi.fn();
securityMiddleware(req, res, next);
expect(res.statusCode).toBe(200);
expect(next).toHaveBeenCalled();
});
it('should allow access to legitimate files', () => {
const req: MockRequest = {
path: '/favicon.ico',
url: '/favicon.ico',
ip: '127.0.0.1',
headers: {}
};
const res = createMockResponse();
const next = vi.fn();
securityMiddleware(req, res, next);
expect(res.statusCode).toBe(200);
expect(next).toHaveBeenCalled();
});
});
describe('Security Logging', () => {
it('should log blocked access attempts', () => {
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
const req: MockRequest = {
path: '/config.json',
url: '/config.json',
ip: '192.168.1.100',
headers: {}
};
const res = createMockResponse();
const next = vi.fn();
securityMiddleware(req, res, next);
expect(consoleSpy).toHaveBeenCalledWith(
expect.stringContaining('🚫 Blocked access to sensitive file: /config.json from 192.168.1.100')
);
consoleSpy.mockRestore();
});
});
});