x402-domain-check / tree / src/check.js ยท raw (this view has old GitHub links rewritten for the mirror; raw is byte-exact)
// DomainCheck engine: domain and email trust reports from public DNS (DoH) and RDAP. No API keys, no paid upstreams.
//   checkDomain('example.com', { tier: 'full' | 'preview' })
//   checkEmail('jane@example.com', { tier: 'full' | 'preview' })
// Mailbox existence is NOT probed (no SMTP RCPT checks: they are unreliable, often blocked, and look like abuse).
import { getDomain, getPublicSuffix } from 'tldts';
import { DohResolver } from './dns.js';
import { Rdap } from './rdap.js';
import { parseEmail, normalizeDomain, isDisposableDomain, FREE_PROVIDERS } from './email.js';
import { parseSpf, parseDmarc, parseMtaSts, parseTlsRpt, parseBimi, parseDkim, DKIM_SELECTORS, mxProvider, isParkingNs } from './mailauth.js';
import { unquoteTxt } from './dns.js';

export const ENGINE_VERSION = 'domaincheck-2026.10.21';

// Each flag: [id, severity, score penalty, description]. Public at /v1/flags.
export const FLAGS = {
  nonexistent_domain: ['critical', 100, 'The domain does not exist in DNS (NXDOMAIN).'],
  registry_hold: ['critical', 45, 'Registry/registrar status is hold, pending delete or redemption: the domain is suspended or expiring.'],
  newly_registered_7d: ['high', 45, 'Registered in the last 7 days. Most phishing and throwaway-signup domains are this young.'],
  newly_registered_30d: ['high', 35, 'Registered in the last 30 days.'],
  young_domain_90d: ['medium', 20, 'Registered in the last 90 days.'],
  young_domain_1y: ['low', 5, 'Registered in the last year.'],
  expiring_15d: ['medium', 15, 'Registration expires within 15 days.'],
  disposable_email_domain: ['high', 45, 'Known disposable / throwaway email provider.'],
  parked_domain: ['medium', 30, 'Nameservers belong to a domain-parking or for-sale service.'],
  no_mail_server: ['medium', 15, 'No MX and no A/AAAA record: the domain cannot receive email.'],
  null_mx: ['medium', 10, 'Null MX (RFC 7505): the domain explicitly accepts no email.'],
  no_spf: ['low', 10, 'No SPF record: anyone can spoof mail from this domain.'],
  spf_pass_all: ['medium', 15, 'SPF ends in +all: every server on the internet is authorized to send as this domain.'],
  spf_multiple: ['low', 10, 'More than one SPF record (permerror for receivers).'],
  no_dmarc: ['low', 10, 'No DMARC record (checked the host and its registrable domain).'],
  dmarc_none: ['low', 5, 'DMARC policy is p=none (monitoring only, no enforcement).'],
  no_web_address: ['low', 10, 'No A/AAAA record: nothing is hosted at this name.'],
  dns_lookup_failed: ['info', 0, 'Some DNS lookups failed or timed out; affected sections are marked.'],
  rdap_unavailable: ['info', 0, 'Registration data (RDAP) was unavailable for this TLD or timed out; age-based checks skipped.'],
  role_address: ['low', 10, 'Role account (info@, support@, noreply@ ...), usually not a single person.'],
  free_email_provider: ['info', 0, 'Free webmail provider (gmail.com, outlook.com ...).'],
  invalid_syntax: ['critical', 100, 'Not a syntactically valid email address.'],
};
const flag = (id, extra) => ({ id, severity: FLAGS[id][0], penalty: FLAGS[id][1], description: FLAGS[id][2], ...(extra ? { detail: extra } : {}) });

const rdapCache = new Map(); // registrable domain -> { at, value }, per isolate, 6 h; keeps registry load polite
const RDAP_TTL = 6 * 3600000;

function scoreOf(flags) {
  if (flags.some((f) => f.id === 'nonexistent_domain' || f.id === 'invalid_syntax')) return 0;
  return Math.max(0, 100 - flags.reduce((s, f) => s + f.penalty, 0));
}
const verdictOf = (score) => (score >= 70 ? 'low_risk' : score >= 40 ? 'medium_risk' : 'high_risk');

/** Mail-authentication grade A-F from SPF, DMARC, MTA-STS. */
export function mailGrade({ spf, dmarc, mta_sts }) {
  let pts = 0;
  if (spf?.present && !spf.multiple) pts += spf.policy === 'fail' ? 3 : spf.policy === 'softfail' ? 2 : spf.policy === 'redirect' ? 2 : spf.policy === 'pass' ? 0 : 1;
  if (dmarc?.present) pts += dmarc.policy === 'reject' ? 4 : dmarc.policy === 'quarantine' ? 3 : 1;
  if (mta_sts?.present) pts += 1;
  return pts >= 7 ? 'A' : pts >= 5 ? 'B' : pts >= 4 ? 'C' : pts >= 2 ? 'D' : 'F';
}

function deps(o) {
  return { resolver: o.resolver || new DohResolver({ fetchImpl: o.fetchImpl, timeoutMs: o.dnsTimeoutMs }), rdap: o.rdap || new Rdap({ fetchImpl: o.fetchImpl, timeoutMs: o.rdapTimeoutMs, now: o.now, userAgent: o.userAgent }), now: o.now || (() => Date.now()) };
}

async function rdapCached(rdap, domain, now) {
  const hit = rdapCache.get(domain);
  if (hit && now() - hit.at < RDAP_TTL && hit.value.found) return { ...hit.value, cached: true };
  const value = await rdap.lookup(domain);
  if (value.found) { rdapCache.set(domain, { at: now(), value }); if (rdapCache.size > 2000) rdapCache.delete(rdapCache.keys().next().value); }
  return value;
}
export const _clearRdapCache = () => rdapCache.clear();

/**
 * Domain trust + email-authentication report.
 * @param {string} input domain, URL or email address
 * @param {{tier?:'full'|'preview', resolver?:any, rdap?:any, fetchImpl?:Function, now?:()=>number}} [o]
 */
export async function checkDomain(input, o = {}) {
  const t0 = Date.now();
  const tier = o.tier || 'full';
  const fullTier = tier !== 'preview';
  const extras = fullTier && !o.mailOnly; // DKIM / MTA-STS / TLS-RPT / BIMI / CAA
  const { resolver, rdap, now } = deps(o);
  const host = normalizeDomain(input);
  if (!host) return { error: 'invalid_domain', message: 'not a valid domain name' };
  const registrable = getDomain(host, { allowPrivateDomains: false });
  const suffix = getPublicSuffix(host, { allowPrivateDomains: false });
  if (!registrable) return { error: 'invalid_domain', message: `"${host}" is a public suffix or has no registrable domain` };

  const q = (name, type) => resolver.query(name, type);
  const base = {
    a: q(host, 'A'), aaaa: q(host, 'AAAA'), mx: q(host, 'MX'), txt: q(host, 'TXT'),
    ns: q(registrable, 'NS'), dmarc: q(`_dmarc.${host}`, 'TXT'),
  };
  const full = extras ? {
    caa: q(host, 'CAA'), mtasts: q(`_mta-sts.${host}`, 'TXT'), tlsrpt: q(`_smtp._tls.${host}`, 'TXT'), bimi: q(`default._bimi.${host}`, 'TXT'),
    ...Object.fromEntries(DKIM_SELECTORS.map((s) => [`dkim:${s}`, q(`${s}._domainkey.${host}`, 'TXT')])),
  } : {};
  const rdapP = fullTier && !o.skipRdap ? rdapCached(rdap, registrable, now) : null;
  const keys = [...Object.keys(base), ...Object.keys(full)];
  const vals = await Promise.all([...Object.values(base), ...Object.values(full)]);
  const R = Object.fromEntries(keys.map((k, i) => [k, vals[i]]));
  let dmarcRes = R.dmarc; let dmarcFrom = null;
  if (!DohResolver.of(dmarcRes, 'TXT').some((a) => /v\s*=\s*DMARC1/i.test(unquoteTxt(a.data))) && host !== registrable) {
    dmarcRes = await q(`_dmarc.${registrable}`, 'TXT'); dmarcFrom = registrable;
  }
  // Core lookups failed: no trustworthy report is possible. Return an error (the API answers 503 and never charges).
  const core = ['a', 'mx', 'ns', 'txt'].filter((k) => R[k].status === 'ERROR');
  if (core.length >= 2) return { error: 'dns_unavailable', message: `DNS lookups failed (${core.map((k) => `${k}: ${R[k].error}`).join('; ')}); not charged, please retry`, status: 503 };
  const failed = Object.entries(R).filter(([, r]) => r.status === 'ERROR' || r.status === 'SERVFAIL').map(([k]) => k);
  const exists = !(R.a.status === 'NXDOMAIN' && R.mx.status === 'NXDOMAIN' && R.ns.status === 'NXDOMAIN') && !(R.a.status === 'NXDOMAIN' && R.txt.status === 'NXDOMAIN' && R.mx.status === 'NXDOMAIN');
  const flags = [];
  const txts = DohResolver.of(R.txt, 'TXT').map((a) => unquoteTxt(a.data));
  const mxRecs = DohResolver.of(R.mx, 'MX').map((a) => { const [p, h] = String(a.data).split(/\s+/); return { priority: Number(p), host: String(h || '').replace(/\.$/, '').toLowerCase() }; }).sort((x, y) => x.priority - y.priority);
  const nullMx = mxRecs.length === 1 && (mxRecs[0].host === '' || mxRecs[0].host === '.');
  const a = DohResolver.of(R.a, 'A').map((x) => x.data); const aaaa = DohResolver.of(R.aaaa, 'AAAA').map((x) => x.data);
  const ns = DohResolver.of(R.ns, 'NS').map((x) => String(x.data).replace(/\.$/, '').toLowerCase());
  const spf = parseSpf(txts);
  const dmarc = parseDmarc(DohResolver.of(dmarcRes, 'TXT').map((x) => unquoteTxt(x.data)), { inherited: !!dmarcFrom, from: dmarcFrom });

  const report = {
    input: String(input), domain: host, registrable_domain: registrable, public_suffix: suffix, tier, exists,
    dns: {
      a, aaaa, ns,
      mx: nullMx ? [] : mxRecs, null_mx: nullMx,
      cname: [...DohResolver.of(R.a, 'CNAME')].map((x) => String(x.data).replace(/\.$/, '')),
      dnssec_validated: !!(R.a.ad || R.mx.ad || R.ns.ad),
      ...(extras ? { caa: DohResolver.of(R.caa, 'CAA').map((x) => x.data) } : {}),
      txt_count: txts.length,
    },
    email: { accepts_mail: !nullMx && (mxRecs.length > 0 || a.length > 0 || aaaa.length > 0), mx_provider: nullMx ? null : mxProvider(mxRecs.map((m) => m.host)), spf, dmarc },
  };
  if (extras) {
    const dkim = DKIM_SELECTORS.map((s) => ({ selector: s, rec: parseDkim(DohResolver.of(R[`dkim:${s}`], 'TXT').map((x) => unquoteTxt(x.data))) })).filter((d) => d.rec).map((d) => ({ selector: d.selector, ...d.rec }));
    const live = dkim.filter((k) => !k.revoked);
    report.email.dkim = { selectors_found: live, revoked_selectors: dkim.length - live.length, selectors_checked: DKIM_SELECTORS.length, note: 'DKIM selectors cannot be enumerated; only common selectors are checked, so none found does not mean DKIM is absent.' };
    report.email.mta_sts = parseMtaSts(DohResolver.of(R.mtasts, 'TXT').map((x) => unquoteTxt(x.data)));
    report.email.tls_rpt = parseTlsRpt(DohResolver.of(R.tlsrpt, 'TXT').map((x) => unquoteTxt(x.data)));
    report.email.bimi = parseBimi(DohResolver.of(R.bimi, 'TXT').map((x) => unquoteTxt(x.data)));
  }
  if (fullTier) report.email.grade = mailGrade(report.email);

  if (!exists) flags.push(flag('nonexistent_domain'));
  else {
    if (isDisposableDomain(host)) flags.push(flag('disposable_email_domain'));
    if (ns.some(isParkingNs)) flags.push(flag('parked_domain', ns.filter(isParkingNs).join(', ')));
    if (nullMx) flags.push(flag('null_mx'));
    else if (!report.email.accepts_mail) flags.push(flag('no_mail_server'));
    if (!a.length && !aaaa.length) flags.push(flag('no_web_address'));
    if (!spf.present) flags.push(flag('no_spf'));
    else { if (spf.policy === 'pass') flags.push(flag('spf_pass_all')); if (spf.multiple) flags.push(flag('spf_multiple')); }
    if (!dmarc.present) flags.push(flag('no_dmarc'));
    else if (dmarc.policy === 'none') flags.push(flag('dmarc_none'));
  }
  if (rdapP) {
    const reg = await rdapP;
    report.registration = reg;
    if (exists) {
      if (!reg.found || reg.age_days == null) flags.push(flag('rdap_unavailable', reg.error || reg.note || 'no registration date published'));
      else {
        const age = reg.age_days;
        if (age < 7) flags.push(flag('newly_registered_7d', `${age} days`));
        else if (age < 30) flags.push(flag('newly_registered_30d', `${age} days`));
        else if (age < 90) flags.push(flag('young_domain_90d', `${age} days`));
        else if (age < 365) flags.push(flag('young_domain_1y', `${age} days`));
      }
      if (reg.found && reg.expires_in_days != null && reg.expires_in_days < 15) flags.push(flag('expiring_15d', `${reg.expires_in_days} days`));
      if (reg.found && (reg.status || []).some((s) => /hold|pending delete|pendingdelete|redemption/.test(s))) flags.push(flag('registry_hold', reg.status.filter((s) => /hold|delete|redemption/.test(s)).join(', ')));
    }
  }
  if (failed.length) flags.push(flag('dns_lookup_failed', failed.join(', ')));
  if (fullTier) {
    report.score = scoreOf(flags);
    report.verdict = exists ? verdictOf(report.score) : 'nonexistent';
  }
  const { input: _i, domain, registrable_domain, public_suffix, tier: _t, exists: _e, score, verdict, ...rest } = report;
  return {
    input: String(input), domain, registrable_domain, public_suffix, tier, exists,
    ...(fullTier ? { verdict, score } : {}), flags, ...rest,
    engine: ENGINE_VERSION, dns_queries: Object.keys(R).length + (dmarcFrom ? 1 : 0), ms: Date.now() - t0,
  };
}

/**
 * Email address check: syntax, disposable / role / free provider, domain mail setup (MX, SPF, DMARC) and, on the full
 * tier, domain registration age and parking. Mailbox existence is not probed.
 */
export async function checkEmail(input, o = {}) {
  const t0 = Date.now();
  const tier = o.tier || 'full';
  const p = parseEmail(input);
  const out = { input: p.input, tier, syntax: { valid: p.valid_syntax, reason: p.reason } };
  if (!p.valid_syntax) {
    return { ...out, verdict: 'invalid', score: 0, flags: [flag('invalid_syntax', p.reason)], mailbox_checked: false, engine: ENGINE_VERSION, ms: Date.now() - t0 };
  }
  Object.assign(out, { normalized: p.normalized, canonical: p.canonical, local: p.local, domain: p.domain, plus_tag: p.plus_tag, role: p.role, free_provider: p.free_provider, disposable: p.disposable, smtputf8: p.smtputf8 });
  const flags = [];
  if (p.disposable) flags.push(flag('disposable_email_domain'));
  if (p.role) flags.push(flag('role_address'));
  if (p.free_provider) flags.push(flag('free_email_provider'));
  if (tier === 'preview') {
    return { ...out, flags, mailbox_checked: false, note: 'Free preview: syntax and provider lists only. The paid check adds MX / SPF / DMARC, domain age (RDAP), parking and a score + verdict.', engine: ENGINE_VERSION, ms: Date.now() - t0 };
  }
  // Big free providers are long-lived and well configured; skip RDAP for them to keep the call fast.
  const d = await checkDomain(p.domain, { ...o, tier: 'full', mailOnly: true, skipRdap: FREE_PROVIDERS.has(p.domain) });
  if (d.error) return d;
  const domFlags = (d.flags || []).filter((f) => !['no_web_address', 'disposable_email_domain'].includes(f.id));
  flags.push(...domFlags);
  const undeliverable = !d.exists || d.dns?.null_mx || !d.email?.accepts_mail;
  const score = undeliverable ? 0 : scoreOf(flags);
  const verdict = undeliverable ? 'undeliverable' : (p.disposable || score < 70) ? 'risky' : 'valid';
  return {
    ...out,
    verdict, score,
    deliverability: { domain_exists: d.exists, accepts_mail: !!d.email?.accepts_mail, mx: d.dns?.mx || [], null_mx: !!d.dns?.null_mx, mx_provider: d.email?.mx_provider || null },
    domain_auth: { spf: d.email?.spf?.present ? d.email.spf.policy : 'missing', dmarc: d.email?.dmarc?.present ? d.email.dmarc.policy : 'missing', grade: d.email?.grade || null },
    registration: d.registration ? { registrar: d.registration.registrar || null, created: d.registration.created || null, age_days: d.registration.age_days ?? null, source: d.registration.source } : null,
    flags,
    mailbox_checked: false,
    note: 'Mailbox existence is not probed (no SMTP RCPT check). "valid" means the address is well-formed, the domain accepts mail and nothing looks risky.',
    engine: ENGINE_VERSION, dns_queries: d.dns_queries, ms: Date.now() - t0,
  };
}

agentgate-source: code mirror on Cloudflare Pages. All repos