Skip to content

TypeScript: @dnsid-ai/transport

Node.js transport implementation for DNSid TypeScript packages.

Use this package when you want default Node DNS and HTTPS/TLS plumbing for @dnsid-ai/protocol or @dnsid-ai/sdk/node.

  • DNS TXT resolution through system DNS or a configured DNS server
  • DNS server parsing and lookup helpers
  • HTTPS JSON fetching
  • SSRF-safe fetch for profile packages that need connection-time DNS checks
  • TLS certificate capture
  • custom CA bundle support
  • custom DNS server support

This package is Node-only and intentionally separate from @dnsid-ai/protocol.

Terminal window
npm install @dnsid-ai/transport
import { createDefaultDnsResolver, createSsrfSafeFetch, fetchJson } from '@dnsid-ai/transport';
const dnsResolver = createDefaultDnsResolver({ dnsServer: process.env.DNSID_DNS_SERVER });
const result = await fetchJson('https://agent.example/.well-known/dnsid-status.json', {
allowedHost: 'agent.example',
dnsServer: process.env.DNSID_DNS_SERVER,
caBundlePath: process.env.DNSID_CA_BUNDLE,
});
const safeFetch = createSsrfSafeFetch({ dnsServer: process.env.DNSID_DNS_SERVER });

Most Node applications should use this through @dnsid-ai/sdk/node or profile defaults.

The default resolver queries the DNS servers returned by Node’s dns.getServers() in order, using UDP with TCP fallback to capture the recursive server’s remaining TXT TTL (capped at one day). dnsServer can override this list. On failures or empty answers it falls back to Node’s native TXT lookup with TTL 0, disabling identity caching for that result. Node’s server list may omit macOS scoped/split-DNS routing, so even positive wire answers are not guaranteed to match the platform resolver in those setups. Neither path validates DNSSEC (UNKNOWN); SERVFAIL is a resolution error. validated/required DNSSEC modes still need an injected validating resolver. Cached verification still re-fetches status unless verification.statusCheckInterval is set.

createSsrfSafeFetch() enforces unsafe-address rejection in the lookup used by the outgoing socket and returns redirects without following them automatically. Trusted test/private deployments may pass privateAddressHosts: exact hostnames or leading-dot suffixes such as .test (label-bounded, case-insensitive); only RFC 1918/ULA private and loopback results are then accepted for matching hosts, while link-local, mixed public/private, and IP-literal URLs remain blocked. Nothing is allowed by default, not even .test; a local dnsid stack needs privateAddressHosts: ['.test'] (or DNSID_PRIVATE_HOSTS=.test through loadEnvironment). Profile packages that accept a custom fetch cannot force equivalent behavior on arbitrary implementations, so custom fetch injection remains trusted infrastructure.

Node.js DNS and HTTPS transport for the DNSid protocol.

Provides the concrete network layer consumed by @dnsid-ai/core: DNS TXT resolvers for fetching identity records, SSRF-safe HTTPS JSON fetching that captures the peer TLS certificate, and fetch factories that route requests through a custom DNS server and/or CA bundle. Built on Node built-ins (node:dns, node:https, node:tls, node:net) plus undici; this package is Node-only and not usable in browsers.

IMPORTANT limitation: these resolvers cannot determine DNSSEC validation state. TXT lookups report DNSSECState.UNKNOWN; this package is not a production DNSSEC validator. DNS-over-HTTPS (DoH) is not supported.

Defined in: transport/src/index.ts:33

Result of fetchJson: the parsed body plus the TLS certificate presented by the peer.

data: unknown;

Defined in: transport/src/index.ts:35

JSON-decoded response body.

tlsCert: TLSCertificate;

Defined in: transport/src/index.ts:37

Peer certificate details (expiry and DNS SANs) captured from the TLS session.


Defined in: transport/src/index.ts:41

Options controlling fetchJson.

optional allowedHost?: string;

Defined in: transport/src/index.ts:45

If set, the URL host (and every redirect host) must match this host exactly.

optional caBundlePath?: string;

Defined in: transport/src/index.ts:51

Path to a PEM CA bundle appended to the system root certificates.

optional dnsServer?: string;

Defined in: transport/src/index.ts:53

Custom DNS server (host, host:port, or [ipv6]:port) used to resolve the target.

optional domainBoundary?: boolean;

Defined in: transport/src/index.ts:47

With allowedHost, also accept subdomains of the allowed host (*.allowedHost).

optional maxResponseBytes?: number;

Defined in: transport/src/index.ts:55

Maximum accepted response body size in bytes. Defaults to 1 MiB.

optional privateAddressHosts?: readonly string[];

Defined in: transport/src/index.ts:57

Same as TransportConfig.privateAddressHosts: hostnames or .suffix entries that may resolve to loopback/private addresses.

optional signal?: AbortSignal;

Defined in: transport/src/index.ts:43

Shared verification deadline/cancellation, including redirects.

optional timeoutMs?: number;

Defined in: transport/src/index.ts:49

Request timeout in milliseconds. Defaults to 10 000.


Defined in: transport/src/index.ts:71

Explicit exceptions for trusted test/private deployments using createSsrfSafeFetch.

optional privateAddressHosts?: readonly string[];

Defined in: transport/src/index.ts:78

Hostnames, or leading-dot suffixes such as .test, whose resolved RFC 1918/ULA private or loopback addresses may be contacted. Link-local and other unsafe ranges remain blocked, IP-literal URLs are never exempted, and nothing is allowed by default. Same semantics as TransportConfig.privateAddressHosts.


Defined in: protocol/src/types.ts:105

SDK-managed DNS and HTTPS deployment settings (DnsidConfig.transport). Never alters protocol semantics.

optional caBundlePath?: string;

Defined in: protocol/src/types.ts:109

Path to a PEM CA bundle appended to the system root certificates for TLS verification.

optional dnsServer?: string;

Defined in: protocol/src/types.ts:107

Custom DNS server (host, host:port, or [ipv6]:port). Omit to use the system resolver.

optional privateAddressHosts?: readonly string[];

Defined in: protocol/src/types.ts:121

Hostnames, or leading-dot suffixes such as .test, whose SDK-managed HTTPS destinations may resolve to loopback or private-use addresses. An exact entry matches only that name; .test matches test and every name beneath it on a DNS-label boundary, case-insensitively and ignoring a trailing dot. Link-local, multicast, reserved, and mixed public/private resolutions stay rejected, IP-literal URLs are never exempted, and every redirect hop is matched independently. Nothing is allowed by default; there is no built-in .test exemption. Entries are validated at construction (IP literals, ports, schemes, paths, credentials → ArgumentError). Applies only to the default fetcher; rejected when fetchJson is injected. Not a DNSid protocol field.

type FetchLike = (input, init?) => Promise<Response>;

Defined in: transport/src/index.ts:30

Minimal WHATWG-fetch-compatible function signature returned by the fetch factories.

RequestInfo | URL

RequestInit

Promise<Response>

function createDefaultDnsResolver(config): DNSResolver;

Defined in: transport/src/index.ts:165

Creates the DNS resolver used to fetch DNSid identity records (TXT).

Uses the configured server, or queries Node’s system DNS server list in order. Wire replies supply remaining TXT TTLs; if those servers cannot answer, the system TXT API is the fallback (TTL 0). DNSSEC state is always UNKNOWN.

Pick<TransportConfig, "dnsServer">

DNSResolver


function createDnsidFetch(config): FetchLike;

Defined in: transport/src/index.ts:105

Creates a fetch function honoring the transport configuration.

With no custom DNS server or CA bundle this returns the global fetch unchanged. Otherwise it returns an undici-backed fetch whose connections resolve hostnames via the configured DNS server and/or trust the extra CA bundle in addition to the system roots. Unlike createSsrfSafeFetch, the returned fetch applies no private-address filtering.

TransportConfig

DNS server and/or CA bundle overrides.

FetchLike

A FetchLike suitable for passing to core verification APIs.

import { createDnsidFetch, createDefaultDnsResolver } from '@dnsid-ai/transport';
const config = { dnsServer: '1.1.1.1' };
const fetchImpl = createDnsidFetch(config);
const dnsResolver = createDefaultDnsResolver(config);
const res = await fetchImpl('https://agent.example/.well-known/jwks.json');
const [records, dnssec] = await dnsResolver.fetchTXT('_dnsid.agent.example');

function createDnsResolverFromServer(server): DNSResolver;

Defined in: transport/src/index.ts:198

Creates a DNSResolver that queries a specific DNS server.

Accepts host, host:port, or [ipv6]:port. A hostname (rather than an IP literal) is resolved once via the system resolver on first use, then cached for the resolver’s lifetime. TXT answers carry the remaining TTL (capped at one day) and DNSSECState.UNKNOWN; NXDOMAIN/NODATA yield an empty record set, and other errors (including SERVFAIL) reject.

string

DNS server address, optionally with port.

DNSResolver


function createLookup(dnsServer): LookupFunction;

Defined in: transport/src/index.ts:267

Creates a Node lookup function that resolves A/AAAA records via the given DNS server instead of the system resolver.

A hostname-form server is itself resolved once via the system resolver and cached. Suitable for https.RequestOptions.lookup or an undici connect option. The callback receives an Error when no A/AAAA records exist.

string

DNS server address (host, host:port, or [ipv6]:port).

LookupFunction


function createSsrfSafeFetch(config?, options?): FetchLike;

Defined in: transport/src/index.ts:132

Creates a fetch function that rejects connections to private, loopback, link-local, and other non-routable addresses (SSRF protection).

Every hostname is resolved (via the configured DNS server, or the system resolver otherwise) and each resulting address is checked with isUnsafeIp before connecting; requests resolving to an unsafe address fail with a VerificationError (TLSError, surfaced through the fetch rejection). Redirects are returned to the caller instead of followed automatically so each destination can be explicitly policy-checked.

TransportConfig = {}

Optional DNS server and/or CA bundle overrides.

SsrfSafeFetchOptions = {}

Explicit hostname-scoped private-address exceptions for trusted test/private deployments.

FetchLike

A FetchLike with address filtering applied on every lookup.


function fetchJson(url, opts?): Promise<FetchResult>;

Defined in: transport/src/index.ts:230

Fetches a JSON document over HTTPS with strict transport checks, returning the parsed body together with the peer TLS certificate.

Enforces: HTTPS-only URLs, optional host pinning via allowedHost (with optional subdomain boundary), SSRF-safe address resolution, at most 5 same-policy HTTPS redirects, an expected 200 status, and a bounded response body size. All lookups route through opts.dnsServer when provided.

string

Absolute HTTPS URL to fetch.

HTTPSFetchOptions

Transport constraints; see HTTPSFetchOptions.

Promise<FetchResult>

The parsed JSON body and captured TLS certificate.

VerificationError with VerificationCode.TLSError for policy or transport failures (non-HTTPS URL, disallowed or unsafe host, redirect violations, non-200 status, oversized body, timeout, TLS/connection errors); with VerificationCode.RecordInvalid when the body is not valid JSON. Network-shaped failures are marked transient.


function formatDnsServer(address, port?): string;

Defined in: transport/src/index.ts:252

Formats a resolved address (bracketing IPv6) with an optional port for Resolver.setServers.

string

string

string


function isUnsafeIp(address): boolean;

Defined in: transport/src/index.ts:396

True if an IP address must not be contacted by SSRF-safe transports: private, loopback, link-local, CGN, documentation, multicast, reserved, 6to4/Teredo, and IPv4-mapped/compatible IPv6 forms of any of these. Malformed IP-shaped input is treated as unsafe; non-IP strings return false.

string

boolean


function parseDnsServer(server):
| {
host: string;
port?: string;
}
| null;

Defined in: transport/src/index.ts:241

Splits a DNS server string into host and optional port.

Supports host, host:port, and [ipv6]:port. Unbracketed strings containing multiple colons (bare IPv6 literals) are returned whole as host, which callers pass through to Node unchanged.

string

| { host: string; port?: string; } | null