basecamp / basecamp/basecamp-sdk

Extend OAuth endpoint address enforcement beyond Go (SPEC §16 req 5–6): umbrella

Open
#818 0 comments 0 reactions 0 assignees View on GitHub
security spec
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.