Adds missing server dir
This commit is contained in:
344
server/file-security.test.ts
Normal file
344
server/file-security.test.ts
Normal file
@@ -0,0 +1,344 @@
|
||||
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();
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user