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 = {
-
-
-
+
+
+
+
+
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');