import { describe, it, expect } from 'vitest'; // Mock Express request object interface MockRequest { path: string; url: string; ip: string; headers: Record; } interface MockResponse { statusCode: number; headers: Record; 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(); }); }); });