diff --git a/tests/diagnostics-protocol.test.js b/tests/diagnostics-protocol.test.js index 6b69f6f..6dc7af6 100644 --- a/tests/diagnostics-protocol.test.js +++ b/tests/diagnostics-protocol.test.js @@ -1,10 +1,20 @@ const { test } = require('node:test'); const assert = require('node:assert'); +const os = require('os'); const WebSocket = require('ws'); const RemoteServer = require('../lib/remote-server'); const TOKEN = 'a'.repeat(64); +function firstLanIpv4() { + for (const entry of Object.values(os.networkInterfaces())) { + for (const net of (entry || [])) { + if (net && net.family === 'IPv4' && !net.internal && net.address) return net.address; + } + } + return null; +} + function startAgent(onDiagnosticRequest, extra) { const srv = new RemoteServer(); return srv.start({ port: 0, host: '127.0.0.1', token: TOKEN, diagnosticMode: true, onDiagnosticRequest, ...(extra || {}) }) @@ -78,6 +88,22 @@ test('a loopback diagnostic client connects even with a non-matching allowlist ( ws.close(); agent.stop(); }); +test('network bind (0.0.0.0): an allowlisted non-loopback peer connects over a real socket (the Tailscale path)', async (t) => { + const lan = firstLanIpv4(); + if (!lan) { t.skip('no non-internal IPv4 interface available'); return; } + const agent = await startAgent(() => {}, { host: '0.0.0.0', allowlist: [lan] }); + const port = agent.getPort(); + const ws = new WebSocket(`ws://${lan}:${port}`); + try { + await new Promise((resolve, reject) => { ws.on('open', resolve); ws.on('error', reject); }); + ws.send(JSON.stringify({ type: 'auth', token: TOKEN, role: 'diagnostic' })); + const ok = await once(ws, 'auth-ok'); + assert.ok(ok.clientId, 'allowlisted LAN peer authed over the 0.0.0.0 bind'); + } finally { + ws.close(); agent.stop(); + } +}); + test('wrong token is rejected and the ip is locked out after 5 attempts', async () => { const agent = await startAgent(() => {}); const port = agent.getPort();