#!/usr/bin/env node /** * The hook prober. * * Drop in a URL, learn where Big Tech has custody of it. * * Everything here is PASSIVE and PUBLIC: DNS records, TLS certificate metadata, * HTTP response headers, and the third-party hosts named in the page's own HTML. * Nothing is scanned, nothing is probed for weakness, nothing is authenticated. * If a site tells the world it sends email through Google, this reads it. * * The claim this supports is custody, not betrayal: * your data sits with a third party who can be compelled, and you will not be told. * That is provable from DNS. Whether the third party is malicious is a different * question and this tool does not answer it. * * Usage: * node probe.mjs https://example.com * node probe.mjs example.com --json */ import { execFile } from 'node:child_process'; import { promisify } from 'node:util'; const run = promisify(execFile); const UA = 'bigtech-hooks/0.1 (+https://bigtech.sucks; passive infrastructure survey)'; // ------------------------------------------------------------------ the map // Patterns are matched against hostnames, and ASNs against the resolved origin. const VENDORS = [ { vendor: 'Amazon', hook: 'hosting', asns: ['16509'], hosts: ['amazonaws.com', 'cloudfront.net', 'awsstatic.com'], note: 'Amazon Web Services' }, { vendor: 'Google', hook: 'hosting', asns: ['15169', '396982'], hosts: ['googleusercontent.com', '1e100.net', 'googleapis.com', 'gstatic.com'], note: 'Google Cloud' }, { vendor: 'Microsoft', hook: 'hosting', asns: ['8075'], hosts: ['azureedge.net', 'microsoftonline.com', 'windows.net', 'azure.com'], note: 'Microsoft Azure' }, { vendor: 'Cloudflare', hook: 'dns/cdn', asns: ['13335'], hosts: ['cloudflare.com', 'cloudflare.net', 'cdnjs.cloudflare.com'], note: 'Cloudflare' }, { vendor: 'Fastly', hook: 'cdn', asns: ['54113'], hosts: ['fastly.net', 'fastlylb.net'], note: 'Fastly' }, { vendor: 'Akamai', hook: 'cdn', asns: ['20940', '16625'], hosts: ['akamai.net', 'akamaized.net'], note: 'Akamai' }, { vendor: 'Vercel', hook: 'hosting', asns: ['13335'], hosts: ['vercel.app', 'vercel.com', 'now.sh'], note: 'Vercel' }, { vendor: 'Netlify', hook: 'hosting', asns: [], hosts: ['netlify.app', 'netlify.com'], note: 'Netlify' }, { vendor: 'Google', hook: 'analytics', asns: [], hosts: ['googletagmanager.com', 'google-analytics.com', 'doubleclick.net'], note: 'Google Analytics / Tag Manager' }, { vendor: 'Google', hook: 'fonts', asns: [], hosts: ['fonts.googleapis.com', 'fonts.gstatic.com'], note: 'Google Fonts, leaks visitor IPs to Google' }, { vendor: 'Meta', hook: 'analytics', asns: [], hosts: ['connect.facebook.net', 'facebook.com', 'fbcdn.net'], note: 'Meta pixel' }, { vendor: 'Google', hook: 'email', asns: ['15169'], hosts: ['googlemail.com', 'google.com'], note: 'Gmail / Google Workspace' }, { vendor: 'Microsoft', hook: 'email', asns: ['8075'], hosts: ['outlook.com', 'protection.outlook.com', 'microsoft.com'], note: 'Microsoft 365' }, { vendor: 'Amazon', hook: 'email', asns: ['16509'], hosts: ['amazonses.com', 'sendgrid.net'], note: 'Amazon SES / SendGrid' }, { vendor: 'Salesforce', hook: 'email', asns: [], hosts: ['salesforce.com', 'exacttarget.com'], note: 'Salesforce Marketing Cloud' }, { vendor: 'Mailchimp', hook: 'email', asns: [], hosts: ['mailchimp.com', 'list-manage.com'], note: 'Mailchimp' }, { vendor: 'Stripe', hook: 'payments', asns: [], hosts: ['stripe.com', 'stripe.network', 'js.stripe.com'], note: 'Stripe' }, { vendor: 'PayPal', hook: 'payments', asns: [], hosts: ['paypal.com', 'paypalobjects.com'], note: 'PayPal' }, { vendor: 'Auth0', hook: 'identity', asns: [], hosts: ['auth0.com'], note: 'Auth0' }, { vendor: 'Okta', hook: 'identity', asns: [], hosts: ['okta.com', 'oktapreview.com'], note: 'Okta' }, { vendor: 'Twilio', hook: 'messaging', asns: [], hosts: ['twilio.com', 'twiliocdn.com'], note: 'Twilio' }, { vendor: 'Intercom', hook: 'support', asns: [], hosts: ['intercom.io', 'intercomcdn.com'], note: 'Intercom' }, { vendor: 'HubSpot', hook: 'marketing', asns: [], hosts: ['hubspot.com', 'hubspot.net', 'hs-scripts.com'], note: 'HubSpot' }, { vendor: 'Slack', hook: 'chat', asns: [], hosts: ['slack.com', 'slack-edge.com'], note: 'Slack' }, { vendor: 'Discord', hook: 'chat', asns: [], hosts: ['discord.com', 'discordapp.com', 'discord.gg'], note: 'Discord' }, ]; const ASN_VENDOR = { '16509': 'Amazon', '14618': 'Amazon', '8987': 'Amazon', '15169': 'Google', '396982': 'Google', '19527': 'Google', '8075': 'Microsoft', '8068': 'Microsoft', '13335': 'Cloudflare', '54113': 'Fastly', '20940': 'Akamai', '16625': 'Akamai', '32934': 'Meta', '63293': 'Meta', '36459': 'Meta', '54115': 'Meta', }; // ------------------------------------------------------------------ dns async function dig(name, type) { try { const { stdout } = await run('dig', ['+short', '+time=4', '+tries=2', type, name], { timeout: 8000 }); return stdout.split('\n').map((s) => s.trim()).filter(Boolean); } catch { return []; } } async function asnFor(ip) { if (!/^\d+\.\d+\.\d+\.\d+$/.test(ip)) return null; const rev = ip.split('.').reverse().join('.'); const txt = await dig(`${rev}.origin.asn.cymru.com`, 'TXT'); if (!txt.length) return null; const asn = txt[0].replace(/"/g, '').split('|')[0].trim(); if (!asn) return null; const name = await dig(`AS${asn}.asn.cymru.com`, 'TXT'); const org = name.length ? name[0].replace(/"/g, '').split('|').slice(-1)[0].trim() : ''; return { asn, org }; } // ------------------------------------------------------------------ http async function fetchHead(url) { try { const res = await fetch(url, { method: 'GET', redirect: 'follow', headers: { 'User-Agent': UA }, signal: AbortSignal.timeout(15000), }); const text = await res.text(); return { status: res.status, finalUrl: res.url, headers: Object.fromEntries(res.headers.entries()), html: text.slice(0, 400000), }; } catch (err) { return { error: String(err.message || err) }; } } function hostsInHtml(html) { const hosts = new Set(); const re = /(?:https?:)?\/\/([a-z0-9.-]+\.[a-z]{2,})/gi; let m; while ((m = re.exec(html))) hosts.add(m[1].toLowerCase()); return [...hosts]; } function classify(hostlist) { const hits = []; for (const h of hostlist) { for (const v of VENDORS) { if (v.hosts.some((p) => h === p || h.endsWith('.' + p))) { hits.push({ vendor: v.vendor, hook: v.hook, evidence: h, note: v.note }); break; } } } // dedupe by vendor+hook, keeping the first evidence const seen = new Set(); return hits.filter((h) => { const k = h.vendor + '|' + h.hook; if (seen.has(k)) return false; seen.add(k); return true; }); } // ------------------------------------------------------------------ probe export async function probe(input) { const url = input.startsWith('http') ? input : `https://${input}`; const host = new URL(url).hostname.replace(/^www\./, ''); const apex = host.split('.').slice(-2).join('.'); const [a, aaaa, ns, mx, txt] = await Promise.all([ dig(host, 'A'), dig(host, 'AAAA'), dig(apex, 'NS'), dig(apex, 'MX'), dig(apex, 'TXT'), ]); const origins = []; for (const ip of [...a, ...aaaa].slice(0, 4)) { const asn = await asnFor(ip); if (asn) origins.push({ ip, ...asn }); } const page = await fetchHead(url); const pageHosts = page.html ? hostsInHtml(page.html) : []; const scriptHosts = page.html ? [...page.html.matchAll(/]+src=["']([^"']+)/gi)].map((m) => m[1]) : []; const hooks = []; const add = (vendor, hook, evidence, note) => { if (!vendor) return; if (hooks.some((h) => h.vendor === vendor && h.hook === hook)) return; hooks.push({ vendor, hook, evidence, note }); }; // hosting, from the ASN of the edge that answered for (const o of origins) { const v = ASN_VENDOR[o.asn]; if (v) add(v, 'hosting', `${o.ip} AS${o.asn} ${o.org}`.trim(), 'edge network'); } // dns for (const n of ns) { const c = classify([n])[0]; if (c) add(c.vendor, 'dns', n, c.note); } // email for (const m of mx) { const h = m.split(' ').pop(); const c = classify([h])[0]; if (c) add(c.vendor, 'email', h, c.note); } // what the SPF record says about who sends their mail for (const t of txt) { if (!/v=spf1/i.test(t)) continue; for (const c of VENDORS) { if (c.hook !== 'email') continue; if (c.hosts.some((p) => t.toLowerCase().includes(p.split('.')[0]))) { add(c.vendor, 'email-sender', c.hosts[0], c.note); } } } // headers const srv = (page.headers?.server || '').toLowerCase(); if (srv.includes('cloudflare')) add('Cloudflare', 'cdn', 'server: cloudflare', 'Cloudflare'); if (srv.includes('vercel')) add('Vercel', 'hosting', 'server: vercel', 'Vercel'); if (srv.includes('netlify')) add('Netlify', 'hosting', 'server: netlify', 'Netlify'); if (page.headers?.['x-powered-by']) add(null, null, null, null); // what the page itself loads for (const c of classify(pageHosts)) add(c.vendor, c.hook, c.evidence, c.note); return { input, host, apex, fetched: page.finalUrl || url, status: page.status ?? null, error: page.error ?? null, dns: { a, aaaa, ns, mx, txt: txt.filter((t) => /spf|verification|site|domain/i.test(t)) }, origins, thirdPartyHosts: pageHosts.slice(0, 60), scriptSources: scriptHosts.slice(0, 20), hooks: hooks.sort((x, y) => x.vendor.localeCompare(y.vendor) || x.hook.localeCompare(y.hook)), distinctVendors: [...new Set(hooks.map((h) => h.vendor))], }; } // ------------------------------------------------------------------ cli if (process.argv[1] && process.argv[1].endsWith('probe.mjs')) { const target = process.argv[2]; const asJson = process.argv.includes('--json'); if (!target) { console.error('usage: node probe.mjs [--json]'); process.exit(2); } const r = await probe(target); if (asJson) { console.log(JSON.stringify(r, null, 2)); process.exit(0); } console.log(`\n${r.host}${r.status ? ` (HTTP ${r.status})` : ''}`); console.log('='.repeat(60)); if (r.error) console.log(` note: ${r.error}`); console.log('\nEDGE'); for (const o of r.origins) console.log(` ${o.ip.padEnd(40)} AS${o.asn} ${o.org}`); if (r.hooks.length) { console.log('\nHOOKS'); for (const h of r.hooks) { console.log(` ${h.vendor.padEnd(14)} ${h.hook.padEnd(14)} ${h.evidence}`); } console.log(`\n distinct vendors with a hook: ${r.distinctVendors.length} -> ${r.distinctVendors.join(', ')}`); } else { console.log('\nHOOKS\n none detected from public records'); } console.log(''); }