fix(coreapi): assert token_exchange_url path in validateExchangeURL · Entire

fix(coreapi): assert token_exchange_url path in validateExchangeURL

4a937e7→main·

toothbrush·4w ago·2 files·+10 added/-0 removed

exchangeSubjectToken strips oauthTokenPath and PostOAuthToken re-appends it, but validateExchangeURL only gated scheme and host. A hint whose path wasn't /oauth/token would make the strip a no-op and POST to a server-chosen path. Validate the path so the round-trip is honest.

Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com

Sessions

ea9e8dae6384View transcript

[?
Review PR Comments on Cross-Jurisdiction RoutingClaude Code·Opus 4.8[1m]·2 steps](/content/gh/entireio/cli/session/2d6d6a51-951b-44ac-be1b-df81b4957e30#timeline-ea9e8dae6384/index.html)

Changes

2

338 unmodified lines

339
340
341
342
343
344
345
346
347
348
11 unmodified lines

360
361
362
363
364
365
366
367
368

338 unmodified lines

//     core's /oauth/token always lives on the same origin as its
//     middleware; off-origin hints have no legitimate use and would
//     let a misconfigured / hostile core exfiltrate the user's JWT.
//   - Path must be exactly oauthTokenPath. exchangeSubjectToken strips
//     that suffix and PostOAuthToken re-appends it; validating it here
//     keeps that round-trip honest instead of silently POSTing to a
//     server-chosen path.
func validateExchangeURL(raw string, requestURL *url.URL) error {
    if raw == "" {
        return errors.New("token_exchange_url missing")
11 unmodified lines

if exchange.Host != requestURL.Host {
        return fmt.Errorf("token_exchange_url host %q must match response host %q", exchange.Host, requestURL.Host)
    }
    if exchange.Path != oauthTokenPath {
        return fmt.Errorf("token_exchange_url path %q must be %q", exchange.Path, oauthTokenPath)
    }
    return nil
}

Minternal/coreapi/cross_juris_transport.go+7

415 unmodified lines

416
417
418
419
420
421
422
423
424

415 unmodified lines

if err := validateExchangeURL("http://localhost:1234/oauth/token", mustParse("http://localhost:1234/api")); err != nil {
        t.Errorf("loopback http must pass: %v", err)
    }
    if err := validateExchangeURL("https://example.test/auth/x", mustParse("https://example.test/api")); err == nil {
         t.Error("non-/oauth/token path must be refused")
    }
}// TestRoundTripper_TokenCacheReusesExchanged confirms the per-origin