The direct answer

A hostname check is incomplete if the fetcher later resolves or connects through a different path. For a deliberately narrow transport, carry one authorized address into connection establishment and compare the resulting peer with that decision before sending the HTTP request. A post-connection mismatch check is a backstop, not proof that no forbidden network contact occurred.

Close the gap left by a URL admission check

The document ingestion guide requires a separate remote fetcher and actual-destination validation. Here the question is narrower: how does an approved resolver result reach the socket rather than becoming a disposable preflight observation?

This proposed design selects the documented interfaces of Node.js v24.16.0, using built-in https.request for a direct HTTPS GET to one approved DNS hostname on port 443. It supports IPv4 only, one fresh connection, and no proxy, redirect, custom connector, or connection pool. This is a reviewed documentation target, not a recommendation to install a particular release or a tested implementation.

Bind a resolver decision to the dialing lookup

Start with an application-owned source record containing the permitted hostname, port, resource path, and destination policy version. No caller-supplied request options may override the transport plan. This exercise assumes an explicit address allowlist maintained for that source, not a complete public-address classifier.

Propose resolving that hostname through the approved resolver, checking the candidate against the source policy, and selecting one authorized IPv4 address. Reject an unsupported family, ambiguous result, or unavailable policy. Freeze the hostname, selected address, family, port, and attempt identity as one decision record.

The versioned net documentation describes custom lookup, family, and family autoselection. The proposed request uses family: 4 and autoSelectFamily: false. Its lookup closure checks the requested hostname and returns only the already approved address and family; it does not perform a second DNS query. Reject unexpected lookup arguments instead of falling back to the default resolver.

Make proxy and socket reuse assumptions explicit

Use a dedicated https.Agent created for this attempt alone, with keepAlive: false and proxyEnv: {}; never share it with another request. Destroy it when the attempt finishes. The versioned HTTP documentation describes agent pooling, custom lookup, and environment-driven proxy configuration. An empty explicit proxy configuration keeps this plan separate from inherited global-agent settings.

The HTTPS documentation describes the corresponding agent and accepted request options. Keep the logical hostname for the HTTP Host value and TLS server name rather than replacing identity with the selected IP. Preserve normal certificate verification. A custom dialer, proxy agent, wrapper, or reused socket is outside this plan and must trigger review, not quietly bypass its lookup gate.

Observe the connection before releasing HTTP bytes

Attach the request’s socket handler and wait for the new TLS connection’s secureConnect event. Require a present peer address equal to the selected address, remoteFamily equal to IPv4, and remotePort equal to 443. Treat missing observations, reuse, errors, or unexpected transport state as failure.

The TLS documentation explains that this event alone does not establish certificate authorization. Require the authorized TLS state and retain the built-in hostname verification, with certificate rejection enabled; do not replace it with an unconditional success callback.

Only after those gates may the consumer call request.end() for this bodyless request. Before that, prohibit write, flushHeaders, and the Expect header: HTTP documentation describes buffering and the ways headers can be released early. This ordering is a proposed implementation obligation, not verified behavior of code supplied here.

By the observation stage, TCP and TLS traffic has already happened. Destroying a mismatched connection can stop the HTTP operation but cannot undo that contact. Address pinning must constrain the dial beforehand; network policy remains a separate layer.

Review a harmless resolver-to-connection recorder

Invent source S-1, allowed address label PEER-A, and unapproved label PEER-B. These are symbolic records, not IP addresses or reachable URLs. A proposed offline recorder captures the selected address, lookup return, planned dial address, observed peer, TLS decision, and whether an HTTP-release operation was requested.

Original proposed recorder cases; UNRUN and no network activity
Invented conditionExpected recordDecision
Selected, lookup, dial, and peer all A; TLS validOne HTTP release after observationsAccept narrow positive fixture
Resolver candidate B is unauthorizedNo dial and no HTTP releaseReject before connection
Lookup tries a second resolver result BPlan violation; no releaseReject fallback
Planned dial A, observed peer BMismatch; destroy requested; no HTTP releaseContact already possible; investigate
Peer missing or TLS authorization falseNo HTTP releaseFail closed
Proxy or reused socket suppliedUnsupported transport recordedReject this plan
Response requests a redirectNo second attempt authorizedHold instead of following

The recorder would check only its supplied records and release ordering. It cannot prove that Node, the operating system, DNS, a proxy, or the network obeyed the plan. Even the positive row is an expected result, not an executed observation.

Keep unsupported routes and evidence limits visible

This design does not cover IPv6 or mapped-address normalization, multi-address racing, HTTP/2 reuse, tunnels, transparent intermediaries, or service-mesh routing. A process-visible peer does not reveal every downstream hop. Supporting those paths requires a new transport-specific design and authorized evidence; an IPv4 registry or a mock cannot fill that gap.

Record minimized source and attempt references, policy version, selected and observed destination, and failure category in protected operational evidence. Avoid full URLs containing credentials or sensitive query strings. Bound connection time, response bytes, and attempt count separately.

Everything here is UNRUN. No DNS lookup, socket, model request, SSRF probe, scan, or fetch of a fixture URL occurred. Boundary is a prelaunch commercial model router with planned request-path policies; this is not a shipped network-enforcement engine or proof of real egress prevention.

Primary sources

Official references supporting the guidance. Examples in this guide are synthetic and are not reports of a production incident.

  1. Node.js v24.16.0: Net
  2. Node.js v24.16.0: HTTP
  3. Node.js v24.16.0: HTTPS
  4. Node.js v24.16.0: TLS