Skip to main content
Glama
badchars

osint-mcp-server

by badchars

dns_spf_chain

Trace SPF DNS records to map email authentication chains, identify included domains, IP ranges, and verify RFC 7208 compliance for domain security analysis.

Instructions

Recursively resolve SPF include chain. Shows all included domains, IP ranges, detected services (Google Workspace, Microsoft 365, SendGrid, etc.), and RFC 7208 lookup limit compliance.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
domainYesDomain to trace SPF chain for
max_depthNoMaximum recursion depth (default: 10)

Implementation Reference

  • The `dnsSpfChain` function resolves the SPF record chain for a given domain, tracking depth, includes, and IP ranges to identify services and detect SPF lookups exceeding the RFC limit.
    export async function dnsSpfChain(domain: string, maxDepth = 10): Promise<SpfChainResult> {
      const visited = new Set<string>();
      const chain: SpfChainNode[] = [];
      const allIpRanges: string[] = [];
      const services = new Set<string>();
      let totalLookups = 0;
    
      async function resolveSpf(d: string, depth: number): Promise<void> {
        if (depth > maxDepth || visited.has(d)) return;
        visited.add(d);
        totalLookups++;
    
        try {
          const txts = await dns.resolveTxt(d);
          const spfRecord = txts.map((t) => t.join("")).find((t) => t.startsWith("v=spf1"));
          if (!spfRecord) return;
    
          const mechanisms = spfRecord.split(/\s+/).filter((m) => m !== "v=spf1");
          const includes: string[] = [];
          const ipRanges: string[] = [];
    
          for (const mech of mechanisms) {
            if (mech.startsWith("include:")) {
              const target = mech.slice(8);
              includes.push(target);
              // Identify known services
              for (const [pattern, service] of Object.entries(SPF_SERVICE_PATTERNS)) {
                if (target.includes(pattern)) services.add(service);
              }
            } else if (mech.startsWith("ip4:") || mech.startsWith("ip6:")) {
              const range = mech.slice(4);
              ipRanges.push(range);
              allIpRanges.push(range);
            } else if (mech.startsWith("a:") || mech.startsWith("mx:")) {
              // Count as DNS lookup
              totalLookups++;
            }
          }
    
          chain.push({ domain: d, depth, mechanisms, includes, ipRanges });
    
          // Recurse into includes
          for (const inc of includes) {
            await resolveSpf(inc, depth + 1);
          }
        } catch {
          // Domain might not have TXT records
        }
      }
    
      await resolveSpf(domain, 0);
    
      const maxChainDepth = chain.reduce((max, n) => Math.max(max, n.depth), 0);
    
      return {
        domain,
        chainDepth: maxChainDepth,
        totalLookups,
        lookupLimit: 10, // RFC 7208
        chain,
        allIpRanges,
        services: [...services],
        exceedsLimit: totalLookups > 10,
      };
    }
  • The `dnsSpfChainTool` definition registers the `dns_spf_chain` tool and connects it to the `dnsSpfChain` handler function.
    const dnsSpfChainTool: ToolDef = {
      name: "dns_spf_chain",
      description: "Recursively resolve SPF include chain. Shows all included domains, IP ranges, detected services (Google Workspace, Microsoft 365, SendGrid, etc.), and RFC 7208 lookup limit compliance.",
      schema: {
        domain: z.string().describe("Domain to trace SPF chain for"),
        max_depth: z.number().optional().describe("Maximum recursion depth (default: 10)"),
      },
      execute: async (args) =>
        json(await dnsSpfChain(args.domain as string, args.max_depth as number | undefined)),
    };

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/badchars/osint-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server