Skip to content

openApiTools: optional pinned-DNS private-address check (#291) - #384

Merged
LinuxDevil merged 1 commit into
mainfrom
fix/lou-s6-openapi
Oct 4, 2026
Merged

LinuxDevil merged 1 commit into
mainfrom
fix/lou-s6-openapi

Conversation

@LinuxDevil

Copy link
Copy Markdown
Owner

Summary

Closes #291 — follow-up to N13a's web-fetch / http_request hardening (#255, #277).

Adds openApiTools({ privateAddresses: 'refuse' }) (default 'allow', unchanged behavior) with an allowPrivate host-pattern list, using the same pinned DNS lookup as http_request and web_fetch (src/security/privateAddress.ts):

  • Pinned lookup on every connection. Operation requests and the document fetch go through an undici Agent whose connect.lookup resolves each host once, checks every returned address, and connects the socket to the checked address — each redirect hop included. A DNS-rebinding name (public to the check, 127.0.0.1 to the connection) never reaches a private address.
  • Literals and localhost names refused up front. IP literals in every form URL normalizes (decimal, octal, hex, IPv4-mapped IPv6) and localhost / *.localhost are refused when the tools are made and on every hop, as a ConfigurationError.
  • allowPrivate lists hosts (intranet.example, *.corp.example, an IP address) that may be or resolve to private addresses.
  • 'refuse' cannot be combined with a custom fetch — a custom fetch resolves names itself, so the check could not pin the address it connects to; making the tools throws instead of checking only half.
  • Node only (undici, node:dns): undici loads lazily when tools with 'refuse' are made (LOU-D19); where it cannot load, making the tools throws a ConfigurationError rather than sending unchecked requests. The Worker bundle is unaffected (the unused export is tree-shaken — the cloudflare adapter's zero-node:-reference tests pass).

Default stays 'allow': base URLs are developer-configured and often internal, so the check is opt-in per the issue's API decision.

Tests

New src/tools/openapi/openApiTools.privateAddress.test.ts (33 tests, fully offline — stubbed resolver + a local node:http server that counts TCP connections):

  • a base URL whose name resolves to a private address is refused and nothing connects;
  • a rebinding name (public first, 127.0.0.1 second) is resolved exactly once and never reaches the local server;
  • 18 private-literal / localhost base-URL forms refused at creation; allowPrivate exemptions;
  • the document fetch checked the same way, every redirect hop;
  • config validation (custom fetch, allowPrivate without 'refuse', invalid patterns, unknown mode) and the no-undici runtime path.

Docs

  • docs/openapi-tools.md: the "No private-address check" bullet replaced with the new 'refuse' semantics; options table gains privateAddresses / allowPrivate.
  • CHANGELOG.md entry under Unreleased → Security.
  • llms-full.txt regenerated (npm run docs:llms; docs:llms:check passes).

Verification

  • npx vitest run src/tools/openapi/ — 63/63 pass
  • npx vitest run src/security/privateAddress.test.ts src/deploy/adapters/cloudflare.test.ts src/providers/importGraph.test.ts — 101/101 pass (incl. Worker bundle node-builtin leak check)
  • npm run build, npm run typecheck, eslint src/tools/openapi/ — clean
  • typecheck:tests has 133 pre-existing errors on base (unchanged; none in the new file)

New option privateAddresses: 'refuse' (default 'allow') with allowPrivate
host patterns. Operation requests and the document fetch go through an
undici Agent whose connect.lookup is the shared pinned lookup, so every
connection and redirect hop resolves once, checks every address and
connects to the checked one. Private IP literals (every form URL
normalizes, IPv4-mapped IPv6 included) and localhost names are refused
when the tools are made and on every hop. 'refuse' cannot be combined
with a custom fetch and throws a ConfigurationError where undici cannot
load.
@LinuxDevil
LinuxDevil merged commit fa85aa9 into main Oct 4, 2026
6 of 7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

openApiTools: optional pinned-DNS private-address check (follow-up to N13a)

1 participant