SPEC §16 requirements 5 and 6 — judging the address an advertised issuer, token_endpoint, or device_authorization_endpoint resolves to, at the moment of connection — are [Go-first]: implemented only in Go, via surfguard's dial-time enforcement. Appendix F records the per-SDK state and the seam each SDK would need. This issue tracks extending enforcement to the four remaining SDKs with an OAuth surface (Swift ships no OAuth discovery/device/exchange path and is out of scope).
Why this matters: the attack the requirements close is credential-bearing SSRF — a public issuer whose metadata names a private token_endpoint receives the client_id, device_code, authorization code, client_secret, or refresh token on the SDK's own POST. Requirement 5 (metadata GET, #804) and requirement 6 (credential POSTs, #810) are closed in Go only.
Per-SDK issues (each carries its implementation spec):
Upstream prerequisites (surfguard repo):
Invariants that bind every per-SDK implementation (restated in each issue; normative text lives in SPEC §16 and Appendix F):
- Connect-time judgement, not preflight. The address judged MUST be the address connected — a dial hook, a resolved-address pin held through the socket, or equivalent. A classification pass followed by a second, independent resolution is a DNS-rebinding window, not an implementation.
- Loopback is trusted operator configuration. Loopback/private admission MUST be an explicit policy option on the SDK's constructors (mirroring Go's
AllowLoopback / policy overrides) — never inferred from the metadata-provided endpoint, the request URL, or any other request-derived value, which would recreate the SSRF hole the policy closes.
- The three overrides. Each SDK MUST expose a replacement policy, a replacement client/transport for the requests, and a way to disable enforcement (a caller-supplied client counts) — SPEC §16's override contract.
- Verdict taxonomy. Refusals are the SDK's typed
api_error (or invalid_issuer_origin on the discovery hop), non-retryable, terminating the device poll loop on first refusal; unresolvable and blocked stay distinct verdicts.
- Injected clients are the caller's, enforcement included — the policy applies to default transports; redirect suppression (already uniform) applies everywhere.
Definition of done for this umbrella: Appendix F's requirement-5/6 tables show enforcement in all five SDKs, and §16 drops [Go-first].
SPEC §16 requirements 5 and 6 — judging the address an advertised issuer,
token_endpoint, ordevice_authorization_endpointresolves to, at the moment of connection — are[Go-first]: implemented only in Go, via surfguard's dial-time enforcement. Appendix F records the per-SDK state and the seam each SDK would need. This issue tracks extending enforcement to the four remaining SDKs with an OAuth surface (Swift ships no OAuth discovery/device/exchange path and is out of scope).Why this matters: the attack the requirements close is credential-bearing SSRF — a public issuer whose metadata names a private
token_endpointreceives theclient_id,device_code, authorization code,client_secret, or refresh token on the SDK's own POST. Requirement 5 (metadata GET, #804) and requirement 6 (credential POSTs, #810) are closed in Go only.Per-SDK issues (each carries its implementation spec):
Upstream prerequisites (surfguard repo):
Invariants that bind every per-SDK implementation (restated in each issue; normative text lives in SPEC §16 and Appendix F):
AllowLoopback/ policy overrides) — never inferred from the metadata-provided endpoint, the request URL, or any other request-derived value, which would recreate the SSRF hole the policy closes.api_error(orinvalid_issuer_originon the discovery hop), non-retryable, terminating the device poll loop on first refusal; unresolvable and blocked stay distinct verdicts.Definition of done for this umbrella: Appendix F's requirement-5/6 tables show enforcement in all five SDKs, and §16 drops
[Go-first].