Conversation
c2791d1 to
d5903b0
Compare
dc26160 to
597cbf5
Compare
| // In centralized mode, http.go strips Authorization and X-Api-Key | ||
| // (they carried the Coder token), so neither header is present | ||
| // here and cfg keeps the centralized key. |
There was a problem hiding this comment.
I'm confused by this comment, is this a reference to the coder repo? If yes, I would suggest not using http.go and making it clear that the upstream caller needs to strip Authorization and X-API-Key headers.
IIUC, at this point, the headers are as follows:
- Centralized: no Authorization header or X-API-Key
- BYOK (oauth): Authorization header includes user's oauth token, no X-API-key
- BYOK Api key: no Authorization header, X-API-Key is user's API key
| } else if apiKey := r.Header.Get("X-Api-Key"); apiKey != "" { | ||
| cfg.Key = apiKey | ||
| } |
There was a problem hiding this comment.
From this point on, we no longer know whether this interception is using a centralized (global) key or a BYOK (user's personal) API key, right? This could be useful to store and to show in the logs (the same for BYOK oauth token). For example, if Anthropic returns a 401, we wouldn't know if the failing key is the global key (affecting everyone) or a single user's personal key.
Additionally, this is probably out of scope for this PR, but it might make sense to store this information in the interception so we can later surface it in the UI, wdyt?
|
|
||
| // MaskSecret returns the first 4 and last 4 characters of s | ||
| // separated by "...", or the full string if 8 characters or fewer. | ||
| func MaskSecret(s string) string { |
There was a problem hiding this comment.
Is this a good idea? I think logging the auth mode ("centralized", "byok_bearer", "byok_apikey") rather than a hint of the secret might be cleaner 👀
Additionally, if we need to correlate a failure to a specific user, I believe we already log this in some cases.
Changes
aibridge:
config/config.go— AddedBYOKBearerTokenfield toAnthropicconfig for OAuth-based BYOK (Claude Max/Pro).provider/anthropic.go—CreateInterceptordetects user LLM credentials from surviving request headers and sets them on the config copy.InjectAuthHeaderskips centralized key injection when user credentials are already present (passthrough BYOK).intercept/messages/base.go—newMessagesServiceusesoption.WithAuthToken()(Authorization: Bearer) whenBYOKBearerTokenis set, otherwiseoption.WithAPIKey()(X-Api-Key).How the flow works end-to-end
Centralized: Client sends
Authorization: Bearer <coder-token>→ http.go extracts token, strips all auth headers → providers get no user credentials → inject centralized key.BYOK (Claude Max/Pro): Client sends
Authorization: Bearer <oauth-token>+X-Coder-AI-Governance-BYOK-Token: <coder-token>→ http.go extracts Coder token from BYOK header, strips only BYOK header →Authorization: Bearer <oauth-token>survives →CreateInterceptorpicks it up asBYOKBearerToken→ SDK sends it viaWithAuthToken(). Passthrough:InjectAuthHeadersees existingAuthorization, skips injection.BYOK (personal API key): Client sends
X-Api-Key: <api-key>+X-Coder-AI-Governance-BYOK-Token: <coder-token>→ same flow butCreateInterceptorpicks upX-Api-Keyascfg.Key→ SDK sends it viaWithAPIKey(). Passthrough:InjectAuthHeadersees existingX-Api-Key, skips injection.