import { test } from 'node:test'; import assert from 'node:assert/strict'; import { analyzeLink, hostFromText, registrableDomain } from './linkSafety'; test('visible text that names another site is a mismatch', () => { const r = analyzeLink('https://matrix.lotusguild.org/login', 'https://evil.example/login'); assert.equal(r?.mismatch, true); assert.equal(r?.shownHost, 'matrix.lotusguild.org'); assert.equal(r?.realHost, 'evil.example'); }); test('honest links are not flagged: same registrable domain, plain words, non-http', () => { assert.equal( analyzeLink('youtube.com/watch?v=1', 'https://www.youtube.com/watch?v=1')?.mismatch, false, ); assert.equal(analyzeLink('bbc.co.uk', 'https://news.bbc.co.uk/x')?.mismatch, false); assert.equal(analyzeLink('click here', 'https://evil.example')?.mismatch, false); assert.equal(analyzeLink('evil.example', 'mailto:someone@evil.example'), null); assert.equal(analyzeLink('elsewhere.org', 'https://matrix.to/#/#room:x'), null); assert.equal( analyzeLink('lotusguild.org', 'https://chat.lotusguild.org/home/!r:x')?.mismatch, false, ); }); test('registrable domain handles two-label suffixes', () => { assert.equal(registrableDomain('news.bbc.co.uk'), 'bbc.co.uk'); assert.equal(registrableDomain('www.example.com'), 'example.com'); assert.equal(registrableDomain('example.com'), 'example.com'); }); test('hostFromText only accepts URL/host-shaped text; IDN becomes punycode', () => { assert.equal(hostFromText('Visit https://a.example/path'), null); assert.equal(hostFromText('a.example/path'), 'a.example'); assert.equal(hostFromText('user@a.example:8448/x'), 'a.example'); assert.equal(hostFromText('pаypal.com'), 'xn--pypal-4ve.com'); // Cyrillic а assert.equal(analyzeLink('paypal.com', 'https://xn--pypal-4ve.com/')?.punycode, true); assert.equal(analyzeLink('paypal.com', 'https://xn--pypal-4ve.com/')?.mismatch, true); }); test('a trusted name inside a sentence still exposes a different destination (#335)', () => { const a = analyzeLink('Log in at chat.lotusguild.org', 'https://evil.example/'); assert.equal(a?.mismatch, true); assert.equal(a?.shownHost, 'chat.lotusguild.org'); assert.equal( analyzeLink('chat.lotusguild.org (official)', 'https://evil.example/')?.mismatch, true, ); assert.equal( analyzeLink('Open https://paypal.com/login now', 'https://evil.example/')?.mismatch, true, ); // honest sentences are not flagged assert.equal( analyzeLink('Log in at chat.lotusguild.org', 'https://chat.lotusguild.org/login')?.mismatch, false, ); assert.equal(analyzeLink('read the docs', 'https://evil.example/')?.mismatch, false); assert.equal(analyzeLink('see main.py and report.pdf', 'https://github.com/x')?.mismatch, false); assert.equal(analyzeLink('release v1.2.3 notes', 'https://github.com/x')?.mismatch, false); }); test('shared hosting suffixes are separate sites (#335)', () => { assert.equal(registrableDomain('alice.github.io'), 'alice.github.io'); assert.equal(registrableDomain('www.alice.github.io'), 'alice.github.io'); assert.equal(registrableDomain('bucket.s3.amazonaws.com'), 'bucket.s3.amazonaws.com'); assert.equal(analyzeLink('alice.github.io', 'https://evil.github.io/')?.mismatch, true); assert.equal(analyzeLink('lotusguild.pages.dev', 'https://evil.pages.dev/')?.mismatch, true); assert.equal( analyzeLink('alice.github.io/blog', 'https://alice.github.io/blog/1')?.mismatch, false, ); assert.equal(registrableDomain('news.bbc.co.uk'), 'bbc.co.uk'); });