DEV Community

Edison Flores
Edison Flores

Posted on

I built a free scam checker that runs entirely in your browser (no API, no server)

What I built

A domain reputation checker that runs 100% client-side. No API key. No server. No backend. Just JavaScript.

Try it: The code is open-source at https://github.com/alicelabs-llc/universal-trust-adapter

What it checks (8 heuristics)

1. URL Shorteners

Detects bit.ly, tinyurl, t.co and 20+ others. URL shorteners hide the destination — you can't inspect where you'll end up.

2. Suspicious TLDs

.zip, .xyz, .top, .click — these TLDs are commonly abused for spam and scams.

3. Punycode / IDN

Internationalized domain names that use Unicode characters to imitate legitimate brands (homograph attacks).

4. Typosquatting

amaz0n.com, paypa1.com, app1e.com — detects 1-character typos of popular brands using Levenshtein distance.

5. Subdomain abuse

Deep subdomain chains and brand names in subdomains of unrelated root domains.

6. HTTP tokens

@, //, long numeric sequences in the domain.

The code (simplified)

function checkDomain(domain) {
    let risk = 0;
    const reasons = [];

    // URL shortener check
    const shorteners = ['bit.ly', 'tinyurl.com', 't.co', 'goo.gl'];
    if (shorteners.some(s => domain.includes(s))) {
        risk += 30;
        reasons.push('URL shortener: destination hidden');
    }

    // Suspicious TLD
    const badTlds = ['.zip', '.xyz', '.top', '.click'];
    if (badTlds.some(tld => domain.endsWith(tld))) {
        risk += 25;
        reasons.push('Suspicious TLD');
    }

    // Typosquatting
    const brands = ['google', 'amazon', 'paypal', 'apple'];
    const bare = domain.split('.')[0];
    for (const brand of brands) {
        if (levenshtein(bare, brand) === 1) {
            risk += 40;
            reasons.push(`Typosquatting: "${bare}" vs "${brand}"`);
        }
    }

    return {
        decision: risk >= 40 ? 'DANGEROUS' : risk >= 20 ? 'CAUTION' : 'UNKNOWN',
        risk_score: risk,
        reasons: reasons
    };
}
Enter fullscreen mode Exit fullscreen mode

Why client-side?

  • No API key — anyone can use it
  • No server costs — runs in the browser
  • No latency — instant results
  • No privacy concerns — domains don't leave the browser
  • CORS not needed — it's all local

Honest limitations

This is heuristic v1. No threat feeds. A brand-new scam site with a normal TLD and no typosquatting returns UNKNOWN, not SAFE. This is not a substitute for commercial threat intelligence — it's a free, transparent first check.

Use case: AI agents that recommend products

If you're building an agent that recommends products or stores, the agent should check the domain before recommending:

# Before the agent recommends a store
result = check_domain("suspicious-store.xyz")
if result["decision"] == "DANGEROUS":
    agent_response = f"I cannot recommend this store. Reasons: {result['reasons']}"
Enter fullscreen mode Exit fullscreen mode

Links


Free. Transparent. Client-side. No API key.

Top comments (0)