How to Build a Domain Intelligence Tool in JavaScript (DNS + Geolocation + Screenshots)

Collapse
X
 
  • Time
  • Show
Clear All
new posts
  • MyrinNew
    Senior Member
    • Feb 2024
    • 5168

    #1

    How to Build a Domain Intelligence Tool in JavaScript (DNS + Geolocation + Screenshots)

    Before you sign a contract with a vendor, onboard a new partner, or click a suspicious link, you probably want to know: who is behind this domain?


    Tools like DomainTools and SecurityTrails charge $99+/month for this. But you can build your own domain intelligence tool in under 120 lines of JavaScript using free APIs — and it runs from the command line in seconds.


    In this tutorial, we'll build a CLI that takes any domain and returns:
    • DNS records (A, MX, NS, TXT, CNAME)
    • WHOIS data (registrar, creation date, expiration)
    • Server geolocation (country, city, ISP, hosting provider)
    • A visual screenshot of the live site
    • A risk assessment based on domain age, hosting, and DNS configuration


    The result is a clean report you can use for vendor vetting, phishing analysis, or sales prospecting.


    What We're Building





    $ node domain-intel.js stripe.com

    ╔══════════════════════════════════════════════╗
    ║ DOMAIN INTELLIGENCE REPORT: stripe.com ║
    ╚══════════════════════════════════════════════╝

    DNS Records
    A: 185.166.143.0, 185.166.142.0
    MX: aspmx.l.google.com (pri 1)
    NS: ns-cloud-d1.googledomains.com
    TXT: v=spf1 include:_spf.google.com ...

    Server Location
    IP: 185.166.143.0
    Location: San Francisco, US
    ISP: Fastly, Inc.
    Hosting: Fastly CDN

    WHOIS
    Registrar: MarkMonitor Inc.
    Created: 1995-09-12
    Expires: 2031-09-11
    Age: 30.5 years

    Risk Score: 2/100 (Very Low)
    ✅ Domain age > 5 years
    ✅ Professional registrar
    ✅ SPF record configured
    ✅ MX records present
    ✅ CDN-hosted

    Screenshot saved: stripe.com-screenshot.png







    Prerequisites

    • Node.js 18+ (for native fetch)
    • A free API key from Frostbyte API Gateway — 200 free credits, no credit card


    Step 1: Get Your Free API Key





    curl -X POST https://api.frostbyte.systems/api/keys/create







    You'll get back something like:






    {
    "key": "gw_abc123...",
    "credits": 200,
    "rateLimit": "60/min"
    }







    Save that key. Each domain lookup uses 3 credits (one per API call), so you get ~66 full reports for free.


    Step 2: Build the Domain Intel Tool

    Create a file called domain-intel.js:






    import { writeFileSync } from 'fs';

    const API_KEY = process.env.FROSTBYTE_KEY || 'your-key-here';
    const BASE = 'https://api.frostbyte.systems/v1';

    async function api(path, options = {}) {
    const res = await fetch(`${BASE}${path}`, {
    ...options,
    headers: {
    'x-api-key': API_KEY,
    'Content-Type': 'application/json',
    ...options.headers,
    },
    });
    if (!res.ok) throw new Error(`API error: ${res.status}`);
    return res.json();
    }

    // ── Step A: DNS + WHOIS ──────────────────────────────
    async function getDomainInfo(domain) {
    const data = await api(`/agent-dns/resolve/${domain}`);
    return data;
    }

    async function getWhois(domain) {
    const data = await api(`/agent-dns/whois/${domain}`);
    return data;
    }

    // ── Step B: Geolocate the server IP ──────────────────
    async function geolocateIP(ip) {
    const data = await api(`/agent-geo/geo/${ip}`);
    return data;
    }

    // ── Step C: Screenshot the live site ─────────────────
    async function screenshotDomain(domain) {
    const data = await api('/agent-screenshot/screenshot', {
    method: 'POST',
    body: JSON.stringify({
    url: `https://${domain}`,
    viewport: 'desktop',
    fullPage: false,
    format: 'png',
    }),
    });
    return data;
    }

    // ── Risk scoring logic ───────────────────────────────
    function calculateRisk(dns, whois, geo) {
    let score = 50; // Start neutral
    const flags = [];

    // Domain age
    if (whois?.createdDate) {
    const ageYears =
    (Date.now() - new Date(whois.createdDate).getTime()) /
    (365.25 * 24 * 60 * 60 * 1000);
    if (ageYears > 5) {
    score -= 20;
    flags.push('✅ Domain age > 5 years');
    } else if (ageYears 0.5) {
    score += 25;
    flags.push('⚠️ Domain registered ');
    } else if (ageYears 1) {
    score += 10;
    flags.push('⚠️ Domain registered ');
    }
    } else {
    score += 10;
    flags.push('⚠️ WHOIS data unavailable');
    }

    // Registrar reputation
    const trustedRegistrars = [
    'markmonitor', 'cloudflare', 'godaddy', 'namecheap',
    'google', 'gandi', 'hover', 'name.com',
    ];
    if (whois?.registrar) {
    const reg = whois.registrar.toLowerCase();
    if (trustedRegistrars.some((t) => reg.includes(t))) {
    score -= 10;
    flags.push('✅ Professional registrar');
    }
    }

    // SPF record
    const hasSPF = dns?.TXT?.some((r) =>
    (r.value || r).toString().includes('v=spf1')
    );
    if (hasSPF) {
    score -= 5;
    flags.push('✅ SPF record configured');
    } else {
    score += 5;
    flags.push('⚠️ No SPF record');
    }

    // MX records
    if (dns?.MX?.length > 0) {
    score -= 5;
    flags.push('✅ MX records present');
    }

    // DMARC
    // (Would need _dmarc.domain TXT lookup — bonus feature)

    // Hosting
    if (geo?.isp) {
    const cdn = ['cloudflare', 'fastly', 'akamai', 'amazon', 'google'];
    if (cdn.some((c) => geo.isp.toLowerCase().includes(c))) {
    score -= 10;
    flags.push('✅ CDN-hosted');
    }
    }

    return {
    score: Math.max(0, Math.min(100, score)),
    flags,
    };
    }

    // ── Main: Assemble the report ────────────────────────
    async function investigate(domain) {
    console.log(`\n${'╔' + '═'.repeat(46) + '╗'}`);
    console.log(`║ DOMAIN INTELLIGENCE REPORT: ${domain.padEnd(16)} ║`);
    console.log(`${'╚' + '═'.repeat(46) + '╝'}\n`);

    // Run DNS and WHOIS in parallel
    console.log('Fetching DNS records...');
    const [dns, whois] = await Promise.all([
    getDomainInfo(domain).catch((e) => {
    console.error(' DNS lookup failed:', e.message);
    return null;
    }),
    getWhois(domain).catch((e) => {
    console.error(' WHOIS lookup failed:', e.message);
    return null;
    }),
    ]);

    // Print DNS
    if (dns) {
    console.log('\nDNS Records');
    if (dns.A) console.log(` A: ${dns.A.join(', ')}`);
    if (dns.AAAA) console.log(` AAAA: ${dns.AAAA.join(', ')}`);
    if (dns.MX)
    console.log(
    ` MX: ${dns.MX.map((r) => `${r.exchange || r} (pri ${r.priority || '?'})`).join(', ')}`
    );
    if (dns.NS) console.log(` NS: ${dns.NS.join(', ')}`);
    if (dns.CNAME) console.log(` CNAME: ${dns.CNAME.join(', ')}`);
    if (dns.TXT)
    console.log(
    ` TXT: ${dns.TXT.map((t) => (t.length > 60 ? t.slice(0, 60) + '...' : t)).join('\n ')}`
    );
    }

    // Print WHOIS
    if (whois) {
    console.log('\nWHOIS');
    if (whois.registrar) console.log(` Registrar: ${whois.registrar}`);
    if (whois.createdDate) console.log(` Created: ${whois.createdDate}`);
    if (whois.expiresDate) console.log(` Expires: ${whois.expiresDate}`);
    if (whois.createdDate) {
    const years = (
    (Date.now() - new Date(whois.createdDate).getTime()) /
    (365.25 * 24 * 60 * 60 * 1000)
    ).toFixed(1);
    console.log(` Age: ${years} years`);
    }
    }

    // Geolocate the primary A record
    const primaryIP = dns?.A?.[0];
    let geo = null;
    if (primaryIP) {
    console.log('\nGeolocating server...');
    geo = await geolocateIP(primaryIP).catch((e) => {
    console.error(' Geolocation failed:', e.message);
    return null;
    });
    if (geo) {
    console.log('\nServer Location');
    console.log(` IP: ${primaryIP}`);
    if (geo.city) console.log(` Location: ${geo.city}, ${geo.country}`);
    if (geo.isp) console.log(` ISP: ${geo.isp}`);
    if (geo.org) console.log(` Org: ${geo.org}`);
    }
    }

    // Risk score
    const { score, flags } = calculateRisk(dns, whois, geo);
    const level =
    score 20
    ? 'Very Low'
    : score 40
    ? 'Low'
    : score 60
    ? 'Medium'
    : score 80
    ? 'High'
    : 'Critical';
    console.log(`\nRisk Score: ${score}/100 (${level})`);
    flags.forEach((f) => console.log(` ${f}`));

    // Screenshot
    console.log('\nCapturing screenshot...');
    try {
    const shot = await screenshotDomain(domain);
    if (shot?.image) {
    // image is base64 — decode and save
    const buffer = Buffer.from(shot.image, 'base64');
    const filename = `${domain.replace(/\./g, '-')}-screenshot.png`;
    writeFileSync(filename, buffer);
    console.log(`Screenshot saved: ${filename}`);
    } else if (shot?.url) {
    console.log(`Screenshot URL: ${shot.url}`);
    }
    } catch (e) {
    console.log(` Screenshot failed: ${e.message}`);
    }

    console.log('\n' + '─'.repeat(48));
    console.log('Powered by Frostbyte API — https://api-catalog-three.vercel.app');

    return { dns, whois, geo, risk: { score, level, flags } };
    }

    // ── CLI entry ────────────────────────────────────────
    const domain = process.argv[2];
    if (!domain) {
    console.error('Usage: node domain-intel.js ');
    console.error('Example: node domain-intel.js stripe.com');
    process.exit(1);
    }

    investigate(domain.replace(/^https?:\/\//, '').replace(/\/.*$/, ''));







    Step 3: Run It





    export FROSTBYTE_KEY="gw_your_key_here"
    node domain-intel.js github.com







    How It Works

    The tool chains three API calls together:


    1. DNS + WHOIS (/v1/agent-dns)

    The DNS API returns all record types for a domain in a single call. We also pull WHOIS data separately to get registrar info, creation date, and expiration — critical signals for assessing domain legitimacy.






    // Get all DNS records
    const dns = await api(`/agent-dns/resolve/example.com`);
    // Returns: { A: [...], MX: [...], NS: [...], TXT: [...] }

    // Get WHOIS / RDAP data
    const whois = await api(`/agent-dns/whois/example.com`);
    // Returns: { registrar, createdDate, expiresDate, nameServers, ... }







    2. IP Geolocation (/v1/agent-geo)

    Once we have the A record IPs, we geolocate them to find out where the server is physically hosted. This reveals the hosting provider, country, and ISP — useful for detecting if a "US company" is actually hosted in an unexpected jurisdiction.






    const geo = await api(`/agent-geo/geo/185.166.143.0`);
    // Returns: { country: "US", city: "San Francisco", isp: "Fastly", ... }







    3. Visual Screenshot (/v1/agent-screenshot)

    A screenshot of the live site gives you instant visual confirmation. Does it look like a real business? Is it a parking page? A phishing clone? One glance tells you what DNS records can't.






    const shot = await api('/agent-screenshot/screenshot', {
    method: 'POST',
    body: JSON.stringify({ url: 'https://example.com', viewport: 'desktop' }),
    });
    // Returns: { image: "base64...", width: 1280, height: 800 }







    The Risk Scoring Algorithm

    The most useful part of this tool is the automated risk assessment. Here's how the scoring works:


    Domain age > 5 years -20 (safer)
    Domain age +25 (riskier)
    Trusted registrar (MarkMonitor, Cloudflare, etc.) -10
    SPF record present -5
    MX records configured -5
    Hosted on CDN (Cloudflare, Fastly, etc.) -10
    WHOIS data unavailable +10
    No SPF record +5


    The score starts at 50 and adjusts based on these signals. A domain like stripe.com (30+ years old, MarkMonitor, SPF, CDN) lands around 2/100. A freshly registered domain with no email records might score 80+.


    Real-World Use Cases

    Vendor Security Assessment

    Before connecting a third-party service to your infrastructure, run this tool to verify they're legitimate:






    node domain-intel.js new-saas-vendor.com







    Red flags to watch for:
    • Domain registered less than a year ago
    • No SPF/DMARC records (poor email security hygiene)
    • Hosted on a residential ISP instead of a cloud provider
    • WHOIS privacy on a supposedly enterprise company


    Phishing Detection

    Got a suspicious email with a link? Check the domain before clicking:






    node domain-intel.js amaz0n-secure-login.com







    Phishing domains almost always score high because they're newly registered, lack email infrastructure, and often use cheap registrars.


    Sales Prospecting

    Before reaching out to a company, understand their technical setup:






    node domain-intel.js target-company.io







    You can see what email provider they use (MX records), what CDN they're on, and how established they are. This is the same data sales intelligence platforms charge hundreds per month for.


    Extending the Tool

    Here are some ideas to make it more powerful:


    Add DNS Propagation Check

    The DNS API also supports propagation analysis across 8 resolvers:






    const prop = await api(`/agent-dns/propagation/${domain}`);
    // Shows if DNS records are consistent across Google, Cloudflare, Quad9, etc.







    Add Web Scraping for Page Content

    Use the scraper API to pull the actual page content and analyze it:






    const page = await api(
    `/agent-scraper/scrape?url=https://${domain}&format=markdown`
    );
    // Extract title, meta description, heading structure, outbound links







    Batch Processing

    Investigate multiple domains at once:






    const domains = ['example1.com', 'example2.com', 'example3.com'];
    const results = await Promise.all(domains.map(investigate));







    Export as JSON

    Add a --json flag for piping into other tools:






    if (process.argv.includes('--json')) {
    console.log(JSON.stringify(result, null, 2));
    process.exit(0);
    }







    API Costs

    Each full domain report uses:


    DNS Lookup 1
    WHOIS Lookup 1
    IP Geolocation 1
    Screenshot 1
    Total per domain 4


    With 200 free credits, you get 50 full domain reports without paying anything. If you need more, credits are $1 per 500 at api-catalog-three.vercel.app/topup.


    Wrapping Up

    In about 120 lines of JavaScript, you've built a domain intelligence tool that combines DNS analysis, server geolocation, visual screenshots, and automated risk scoring. It's the kind of tool that security teams, sales teams, and developers all find useful — and it runs locally with no dependencies beyond Node.js.


    The full source code uses three Frostbyte APIs through a single API key:
    • DNS Lookup — records, WHOIS, propagation
    • IP Geolocation — server location, ISP, hosting provider
    • Screenshots — visual confirmation of live sites


    Get your free API key at api-catalog-three.vercel.app and start investigating domains in seconds.





    Have questions or want to see more API tutorials? Drop a comment below or check out the full API docs.




    More...
Working...