From 014d1704de0c35a383c914ac618576110faa141c Mon Sep 17 00:00:00 2001 From: MrUnknownDE Date: Sun, 17 May 2026 21:24:10 +0200 Subject: [PATCH] add dual-stack check --- backend/routes/privacy.js | 12 ++++--- frontend/app/pages/home.js | 66 ++++++++++++++++++++++++++------------ 2 files changed, 54 insertions(+), 24 deletions(-) diff --git a/backend/routes/privacy.js b/backend/routes/privacy.js index ce983af..16c3b1e 100644 --- a/backend/routes/privacy.js +++ b/backend/routes/privacy.js @@ -8,9 +8,10 @@ const logger = pino({ level: process.env.LOG_LEVEL || 'info' }); // ASN org-name patterns that strongly suggest a VPN service const VPN_PATTERNS = [ - /\bvpn\b/i, /nordvpn/i, /expressvpn/i, /mullvad/i, /surfshark/i, - /protonvpn/i, /cyberghost/i, /ipvanish/i, /purevpn/i, /tunnelbear/i, - /private.?internet.?access/i, /\bpia\b/i, /hide\.?my\.?ip/i, /hidemyass/i, + /\bvpn\b/i, /nordvpn/i, /expressvpn/i, /mullvad/i, /31173\s+services/i, + /owl\s+limited/i, /surfshark/i, /protonvpn/i, /cyberghost/i, /ipvanish/i, + /purevpn/i, /tunnelbear/i, /private.?internet.?access/i, /\bpia\b/i, + /hide\.?my\.?ip/i, /hidemyass/i, /windscribe/i, /perfect.?privacy/i, ]; // ASN org-name patterns that suggest cloud/datacenter/hosting (but not necessarily VPN) @@ -58,7 +59,10 @@ router.get('/:ip', async (req, res, next) => { // Tor exit-node check (async DNS, ~100–300 ms) const tor = await isTorExit(ip); - if (tor) flags.push({ id: 'tor', label: 'Tor Exit Node', color: 'red' }); + if (tor) { + flags.length = 0; // Tor supersedes all network-type flags — showing "Residential" alongside Tor is misleading + flags.push({ id: 'tor', label: 'Tor Exit Node', color: 'red' }); + } logger.info({ ip, flags: flags.map(f => f.id) }, 'Privacy check complete'); res.json({ success: true, ip, flags }); diff --git a/frontend/app/pages/home.js b/frontend/app/pages/home.js index fb199ce..8453cea 100644 --- a/frontend/app/pages/home.js +++ b/frontend/app/pages/home.js @@ -31,9 +31,21 @@ export const page = { - - -
+ +
+
+ IPv4 +
+
+
+
+
+ IPv6 +
+ detecting… +
+
+
@@ -474,19 +486,28 @@ export const page = { // ── IPv6 detection ─────────────────────────────────────────── async function checkIPv6() { - const ctrl = new AbortController(); - const timer = setTimeout(() => ctrl.abort(), 4000); + const ctrl = new AbortController(); + const timer = setTimeout(() => ctrl.abort(), 4000); + const v6Flags = document.getElementById('privacy-flags-v6'); try { const r = await fetch('https://ipv6.icanhazip.com', { signal: ctrl.signal }); const ip6 = r.ok ? (await r.text()).trim() : null; - if (ip6 && ip6.includes(':')) { + if (ip6 && ip6.includes(':') && ip6 !== currentIp) { + // Separate IPv6 address — show it and run its own privacy check document.getElementById('ipv6-address').textContent = ip6; - const row = document.getElementById('ipv6-row'); - row.classList.remove('hidden'); + document.getElementById('ipv6-row').classList.remove('hidden'); copyIpv6Btn.classList.remove('hidden'); + if (v6Flags) v6Flags.innerHTML = '
'; + fetchPrivacyFlags(ip6, 6); + } else { + // No IPv6, timed out, or same exit as IPv4 (VPN tunnels both) + if (v6Flags) v6Flags.innerHTML = 'N/A'; } - } catch { /* no IPv6 or timeout — show nothing */ } - finally { clearTimeout(timer); } + } catch { + if (v6Flags) v6Flags.innerHTML = 'N/A'; + } finally { + clearTimeout(timer); + } } // ── Privacy / risk flags ────────────────────────────────────── @@ -497,19 +518,25 @@ export const page = { red: 'bg-red-900/40 text-red-300 border border-red-700/50', }; - async function fetchPrivacyFlags(ip) { - const loader = document.getElementById('privacy-loader'); - const container = document.getElementById('privacy-flags'); + async function fetchPrivacyFlags(ip, version = 4) { + const container = document.getElementById(`privacy-flags-v${version}`); + const loader = document.getElementById(`privacy-loader-v${version}`); + if (!container) return; try { const r = await fetch(`${API}/privacy/${encodeURIComponent(ip)}`); const data = await r.json(); - if (!data.flags?.length) return; + loader?.remove(); + if (!data.flags?.length) { + container.innerHTML = ''; + return; + } container.innerHTML = data.flags.map(f => `${f.label}` ).join(''); - container.classList.remove('hidden'); - } catch { /* silent fail */ } - finally { loader?.classList.add('hidden'); } + } catch { + loader?.remove(); + container.innerHTML = 'N/A'; + } } // ── Browser fingerprint ─────────────────────────────────────── @@ -572,7 +599,7 @@ export const page = { if (!r.ok) throw new Error(`${r.statusText} (${r.status})`); const data = await r.json(); currentIp = data.ip; - fetchPrivacyFlags(data.ip); + fetchPrivacyFlags(data.ip, 4); ipAddressSpan.textContent = data.ip; ipAddressLink.classList.remove('hidden'); @@ -941,8 +968,7 @@ export const page = { // ── Bootstrap ──────────────────────────────────────────────── populateBrowserFingerprint(); - checkIPv6(); - fetchIpInfo(); + fetchIpInfo().then(() => checkIPv6()); const params = new URLSearchParams(search); const ipParam = params.get('ip');