release: v2.0.6

Redesign the desktop workspace with task sidebars, live filters, clearer settings, and an accessible update dialog.

Harden encrypted backup imports, configuration persistence, history retention, queue snapshots, shutdown recovery, and update installation ordering.

Publish verified Windows artifacts and refreshed English documentation.
This commit is contained in:
Sucukdeluxe
2026-08-10 09:28:28 +02:00
commit 76a228fb7c
113 changed files with 36923 additions and 0 deletions
+52
View File
@@ -0,0 +1,52 @@
const { test } = require('node:test');
const assert = require('node:assert');
const { normalizeIp, isLoopbackIp, matchIpRule, evaluateClientAllowed } = require('../lib/ip-allowlist');
test('normalizeIp strips ::ffff: and lowercases', () => {
assert.equal(normalizeIp('::ffff:100.64.0.5'), '100.64.0.5');
assert.equal(normalizeIp('::FFFF:127.0.0.1'), '127.0.0.1');
assert.equal(normalizeIp(' 100.64.0.5 '), '100.64.0.5');
});
test('loopback is always allowed, even with a non-matching allowlist', () => {
for (const ip of ['127.0.0.1', '::1', '::ffff:127.0.0.1', '', 'localhost', '127.5.5.5']) {
assert.equal(evaluateClientAllowed(ip, ['203.0.113.5']), true, `${ip} loopback`);
}
});
test('fail-closed: empty allowlist rejects every non-loopback peer', () => {
for (const ip of ['100.64.0.5', '203.0.113.5', '10.0.0.2', '::ffff:192.168.1.9']) {
assert.equal(evaluateClientAllowed(ip, []), false, `${ip} must be rejected with empty allowlist`);
}
});
test('exact IP allow + reject', () => {
assert.equal(evaluateClientAllowed('203.0.113.5', ['203.0.113.5']), true);
assert.equal(evaluateClientAllowed('203.0.113.6', ['203.0.113.5']), false);
});
test('CIDR matching incl. the Tailscale CGNAT range 100.64.0.0/10', () => {
assert.equal(evaluateClientAllowed('100.64.0.5', ['100.64.0.0/10']), true);
assert.equal(evaluateClientAllowed('100.127.255.254', ['100.64.0.0/10']), true);
assert.equal(evaluateClientAllowed('100.128.0.1', ['100.64.0.0/10']), false, 'just outside the /10');
assert.equal(evaluateClientAllowed('::ffff:100.64.0.5', ['100.64.0.0/10']), true, 'mapped v4 in CIDR');
assert.equal(evaluateClientAllowed('10.0.0.5', ['10.0.0.0/24']), true);
assert.equal(evaluateClientAllowed('10.0.1.5', ['10.0.0.0/24']), false);
});
test('wildcard rules allow everything', () => {
assert.equal(evaluateClientAllowed('8.8.8.8', ['*']), true);
assert.equal(evaluateClientAllowed('8.8.8.8', ['0.0.0.0/0']), true);
});
test('matchIpRule rejects malformed rules and out-of-range octets', () => {
assert.equal(matchIpRule('1.2.3.4', 'not-an-ip'), false);
assert.equal(matchIpRule('1.2.3.4', '1.2.3.0/33'), false);
assert.equal(matchIpRule('1.2.3.999', '1.2.3.0/24'), false);
});
test('isLoopbackIp recognizes loopback forms', () => {
assert.equal(isLoopbackIp('127.0.0.1'), true);
assert.equal(isLoopbackIp('::1'), true);
assert.equal(isLoopbackIp('100.64.0.1'), false);
});