import { tap, expect } from '@git.zone/tstest/tapbundle'; import * as smartproxy from '../ts/index.js'; import type { IRouteConfig } from '../ts/proxies/smart-proxy/models/route-types.js'; import * as net from 'net'; tap.test('route security should block connections from unauthorized IPs', async () => { // Create a target server that should never receive connections let targetServerConnections = 0; const targetServer = net.createServer((socket) => { targetServerConnections++; console.log('Target server received connection - this should not happen!'); socket.write('ERROR: This connection should have been blocked'); socket.end(); }); await new Promise((resolve) => { targetServer.listen(9990, '127.0.0.1', () => { console.log('Target server listening on port 9990'); resolve(); }); }); // Create proxy with restrictive security at route level const routes: IRouteConfig[] = [{ name: 'secure-route', match: { ports: 9991 }, action: { type: 'forward', target: { host: '127.0.0.1', port: 9990 } }, security: { // Only allow a non-existent IP ipAllowList: ['192.168.99.99'] } }]; const proxy = new smartproxy.SmartProxy({ enableDetailedLogging: true, routes: routes }); await proxy.start(); console.log('Proxy started on port 9991'); // Wait a moment to ensure server is fully ready await new Promise(resolve => setTimeout(resolve, 100)); // Try to connect from localhost (should be blocked) const client = new net.Socket(); const events: string[] = []; const result = await new Promise((resolve) => { let resolved = false; client.on('connect', () => { console.log('Client connected (TCP handshake succeeded)'); events.push('connected'); // Send initial data to trigger routing client.write('test'); }); client.on('data', (data) => { console.log('Client received data:', data.toString()); events.push('data'); if (!resolved) { resolved = true; resolve('data'); } }); client.on('error', (err: any) => { console.log('Client error:', err.code); events.push('error'); if (!resolved) { resolved = true; resolve('error'); } }); client.on('close', () => { console.log('Client connection closed by server'); events.push('closed'); if (!resolved) { resolved = true; resolve('closed'); } }); setTimeout(() => { if (!resolved) { resolved = true; resolve('timeout'); } }, 2000); console.log('Attempting connection from 127.0.0.1...'); client.connect(9991, '127.0.0.1'); }); console.log('Connection result:', result); console.log('Events:', events); // The connection might be closed before or after TCP handshake // What matters is that the target server never receives a connection console.log('Test passed: Connection was properly blocked by security'); // Target server should not have received any connections expect(targetServerConnections).toEqual(0); // Clean up client.destroy(); await proxy.stop(); await new Promise((resolve) => { targetServer.close(() => resolve()); }); }); tap.test('route security with block list should work', async () => { // Create a target server let targetServerConnections = 0; const targetServer = net.createServer((socket) => { targetServerConnections++; socket.write('Hello from target'); socket.end(); }); await new Promise((resolve) => { targetServer.listen(9992, '127.0.0.1', () => resolve()); }); // Create proxy with security at route level (not action level) const routes: IRouteConfig[] = [{ name: 'secure-route-level', match: { ports: 9993 }, action: { type: 'forward', target: { host: '127.0.0.1', port: 9992 } }, security: { // Security at route level, not action level ipBlockList: ['127.0.0.1', '::1', '::ffff:127.0.0.1'] } }]; const proxy = new smartproxy.SmartProxy({ enableDetailedLogging: true, routes: routes }); await proxy.start(); // Try to connect (should be blocked) const client = new net.Socket(); const events: string[] = []; const result = await new Promise((resolve) => { let resolved = false; const timeout = setTimeout(() => { if (!resolved) { resolved = true; resolve('timeout'); } }, 2000); client.on('connect', () => { console.log('Client connected to block list test'); events.push('connected'); // Send initial data to trigger routing client.write('test'); }); client.on('error', () => { events.push('error'); if (!resolved) { resolved = true; clearTimeout(timeout); resolve('error'); } }); client.on('close', () => { events.push('closed'); if (!resolved) { resolved = true; clearTimeout(timeout); resolve('closed'); } }); client.connect(9993, '127.0.0.1'); }); // Should connect then be immediately closed by security expect(events).toContain('connected'); expect(events).toContain('closed'); expect(result).toEqual('closed'); expect(targetServerConnections).toEqual(0); // Clean up client.destroy(); await proxy.stop(); await new Promise((resolve) => { targetServer.close(() => resolve()); }); }); tap.test('route without security should allow all connections', async () => { // Create echo server const echoServer = net.createServer((socket) => { socket.on('data', (data) => { socket.write(data); }); }); await new Promise((resolve) => { echoServer.listen(9994, '127.0.0.1', () => resolve()); }); // Create proxy without security const routes: IRouteConfig[] = [{ name: 'open-route', match: { ports: 9995 }, action: { type: 'forward', target: { host: '127.0.0.1', port: 9994 } } // No security defined }]; const proxy = new smartproxy.SmartProxy({ enableDetailedLogging: false, routes: routes }); await proxy.start(); // Connect and test echo const client = new net.Socket(); await new Promise((resolve) => { client.connect(9995, '127.0.0.1', () => resolve()); }); // Send data and verify echo const testData = 'Hello World'; client.write(testData); const response = await new Promise((resolve) => { client.once('data', (data) => { resolve(data.toString()); }); setTimeout(() => resolve(''), 2000); }); expect(response).toEqual(testData); // Clean up client.destroy(); await proxy.stop(); await new Promise((resolve) => { echoServer.close(() => resolve()); }); }); export default tap.start();