54 lines
3 KiB
TypeScript
54 lines
3 KiB
TypeScript
import { createRequire } from 'node:module';
|
|
import { createServer, request } from 'node:http';
|
|
import { describe, expect, it } from 'vitest';
|
|
const require = createRequire(import.meta.url);
|
|
const { sanitizeProxyHeaders } = require('../tools/production-transport.cjs');
|
|
const origin = new URL('https://random.ochenstarik.ru');
|
|
function fake(peer: string) {
|
|
return { socket: { remoteAddress: peer }, headers: {
|
|
host: 'evil.test', 'x-forwarded-for': '203.0.113.8', 'x-forwarded-proto': 'https',
|
|
'x-forwarded-host': 'evil.test', 'x-real-ip': '203.0.113.9', forwarded: 'for=evil',
|
|
'cf-connecting-ip': '203.0.113.10', 'x-forwarded-unknown': 'evil',
|
|
} as Record<string, string> };
|
|
}
|
|
describe('production socket trust boundary', () => {
|
|
it.each(['192.168.1.2', '10.77.0.2', '127.0.0.1', '::ffff:192.168.1.2'])('ignores all forwarded headers from foreign peer %s', (peer) => {
|
|
const req = fake(peer);
|
|
sanitizeProxyHeaders(req, { trustedProxyIp: '10.77.0.1', origin });
|
|
expect(req.headers['x-forwarded-for']).toBe(peer.replace('::ffff:', ''));
|
|
expect(req.headers['x-forwarded-proto']).toBe('http');
|
|
expect(req.headers.host).toBe(origin.host);
|
|
expect(req.headers.forwarded).toBeUndefined();
|
|
expect(req.headers['x-real-ip']).toBeUndefined();
|
|
expect(req.headers['cf-connecting-ip']).toBeUndefined();
|
|
expect(req.headers['x-forwarded-unknown']).toBeUndefined();
|
|
});
|
|
it('accepts one overwritten client IP only from the VPS socket peer', () => {
|
|
const req = fake('::ffff:10.77.0.1');
|
|
sanitizeProxyHeaders(req, { trustedProxyIp: '10.77.0.1', origin });
|
|
expect(req.headers['x-forwarded-for']).toBe('203.0.113.8');
|
|
expect(req.headers['x-forwarded-proto']).toBe('https');
|
|
req.headers['x-forwarded-for'] = '203.0.113.8, 192.0.2.3';
|
|
sanitizeProxyHeaders(req, { trustedProxyIp: '10.77.0.1', origin });
|
|
expect(req.headers['x-forwarded-for']).toBe('10.77.0.1');
|
|
});
|
|
it('sanitizes a real loopback HTTP connection before the downstream handler', async () => {
|
|
const server = createServer((req, res) => {
|
|
sanitizeProxyHeaders(req, { trustedProxyIp: '10.77.0.1', origin });
|
|
res.end(JSON.stringify(req.headers));
|
|
});
|
|
await new Promise<void>(resolve => server.listen(0, '127.0.0.1', resolve));
|
|
try {
|
|
const address = server.address() as { port: number };
|
|
const body = await new Promise<string>((resolve, reject) => {
|
|
const req = request({ host: '127.0.0.1', port: address.port, headers: fake('ignored').headers }, res => {
|
|
let data = ''; res.on('data', chunk => data += chunk); res.on('end', () => resolve(data));
|
|
}); req.on('error', reject); req.end();
|
|
});
|
|
const headers = JSON.parse(body);
|
|
expect(headers['x-forwarded-for']).toBe('127.0.0.1');
|
|
expect(headers['x-forwarded-proto']).toBe('http');
|
|
expect(headers['x-forwarded-host']).toBe(origin.host);
|
|
} finally { await new Promise<void>((resolve, reject) => server.close(err => err ? reject(err) : resolve())); }
|
|
});
|
|
});
|