03

Discover Authorization Without Creating an SSRF Proxy

Follow standards-defined metadata while validating every network destination before use. • Lab status: conceptual reconstruction. Receipts are expected simulated outputs, not deployed-system measurements.

System map · Day 03

Whole-system design

Five stable layers. Today's work is expanded and linked; the rest stays in context.

Entry and policy

Covered — Client entryAhead — Browser and callback

Onboarding and control

Connector control plane

Design target · not proved

Treats every discovered URL and capability as untrusted input before applying it.

Authorization services

Authorization server

Design target · not proved

Publishes endpoint and capability metadata without becoming trusted merely by discovery.

Compute and execution

Covered — Connector runtime computeAhead — Refresh coordinator compute

External resource server

Design target · not proved

Identifies its authorization server through protected-resource metadata or a challenge.

Storage and evidence

Covered — Specification draft store · Connector configuration storeAhead — Secret and token vault · Ordered migration log

Sanitized discovery cache

Design target · not proved

Caches only sanitized metadata and preserves the caller-supplied configuration unchanged.

Evidence plane

Source-backed today

Pairs the accepted metadata receipt with one server-side request-forgery denial.

Traversed today

discover · External resource serverAuthorization serverobserve · Connector runtime computeEvidence plane

Overview

Why discovery expands the trust boundary

Day 02 produced a connector draft with a protected resource URL. OAuth protected-resource metadata can point the gateway to an authorization server, whose metadata then supplies authorization and token endpoints. Each hop is attacker-influenced network input, so blindly following it can cause server-side request forgery (SSRF): making the gateway fetch an unintended internal service.

Today you will resolve one protected-resource chain, sanitize every URL before connection and after redirect, cache only the validated result, and produce SCG-R03. The discovery sequence follows RFC 9728, RFC 8414, OpenID Connect Discovery, and the MCP 2025-11-25 authorization profile.

Resolve metadata through a guarded fetcher

Connector control first uses a WWW-Authenticate resource metadata hint when present, then bounded well-known fallbacks. A guarded fetcher requires HTTPS outside explicit loopback development, resolves DNS, rejects private, loopback, link-local, multicast, and reserved ranges, binds the connection to a vetted address, and verifies the connected peer. That binding closes the DNS-rebinding gap between validation and fetch. It also limits bytes and redirects, and repeats resolution, binding, and peer verification for every redirect. Production code should use vetted URL, IP, and transport libraries rather than handwritten string tests.

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://calendar.example/.well-known/oauth-protected-resource"

{
  "issuer": "https://auth.example",
  "authorization_endpoint": "https://auth.example/authorize",
  "token_endpoint": "https://auth.example/token",
  "registration_endpoint": "https://auth.example/register"
}
const policy = {
  protocols: ["https:"],
  maxRedirects: 2,
  maxBytes: 65536,
  rejectAddressClasses: ["private", "loopback", "link-local", "reserved"],
  connectToResolvedAddress: true,
  verifyConnectedPeerAddress: true,
  revalidateRedirectTarget: true,
} as const;

Cache only a sanitized discovery receipt

The discovery cache stores normalized endpoints, issuer, source URL, validation timestamp, expiry, and policy version. It does not cache raw response headers, cookies, or arbitrary metadata fields. Cache lookup must still confirm that the requested resource and policy version match.

{
  "cacheKey": "calendar-lab|https://calendar.example/mcp|policy-v1",
  "issuer": "https://auth.example",
  "tokenEndpoint": "https://auth.example/token",
  "expiresAt": "2026-08-14T12:10:00Z",
  "sanitized": true
}

{
  "receipt": "SCG-R03",
  "normal": "metadata_chain_accepted",
  "denied": "private_destination",
  "redirectsChecked": 1,
  "networkResponseBodyStored": false
}

The denied path changes the token endpoint to a link-local address; no connection may be attempted. Recovery corrects the fixture metadata and starts a fresh bounded resolution rather than trusting the failed cache entry. A public-status request remains the unaffected control. Cleanup evicts only the exercise cache key.

Score one destination decision

Given a normalized URL, resolved addresses, redirect count, and environment policy, decide only allow or SSRF-deny. A public HTTPS endpoint with all public resolved addresses is allowed; any private or link-local result is denied. The expected evidence is the decision plus one policy reason.

The misconception is “HTTPS means public.” Replay an HTTPS hostname whose resolved address is private to show why scheme validation alone is insufficient. Decline tasks asking for DNS-client implementation, arbitrary proxy configuration, or complete authorization because they add multiple unknowns.

Carry sanitized metadata forward

SCG-R03 contains the canonical resource, selected issuer, sanitized endpoints, cache policy version, normal resolution, SSRF denial, and cleanup result. Day 04 uses those exact endpoints to bind an authorization request to a browser callback without turning discovery into authority.