basecamp / basecamp/basecamp-sdk
Extend OAuth endpoint address enforcement beyond Go (SPEC §16 req 5–6): umbrella
- Dominant language
- Go
- Stars
- 49
- Forks
- 12
- Avg merge
- 20h 47m
- Merged PRs (30d)
- 89
Description
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):
- [ ] Ruby — #814
- [ ] TypeScript — #815
- [ ] Python — #816
- [ ] Kotlin — #817
Upstream prerequisites (surfguard repo):
- [ ] Release the staged 0.2.0 gem — basecamp/surfguard#24
- [ ] Accept TS/Python/Kotlin classification-core ports with mirrored corpus + drift tests — basecamp/surfguard#25
Invariants that bind every per-SDK implementation (restated in each issue; normative text lives in SPEC §16 and Appendix F):
1. **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.
2. **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.
3. **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.
4. **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.
5. **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]`.
Contributor guide
Research direction
Start with SPEC §16 and Appendix F, then read the implementation specifications in issues #814–#817 for the four remaining SDKs. Check the surfguard prerequisite issues and their mirrored corpus and drift tests. Done means Appendix F shows requirements 5 and 6 enforced in all five SDKs and §16 no longer says [Go-first].
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go, kotlin, python, ruby, typescript
- Domain
- api, authentication, security
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100