feat: byok-observability for aibridge#239
Conversation
9bf93dd to
c5dd566
Compare
71c69d3 to
30d306a
Compare
c5dd566 to
d6e95ea
Compare
7d0c198 to
b1c6f3f
Compare
d6e95ea to
b6a01e9
Compare
e1e15df to
b6a01e9
Compare
a610090 to
d3e5994
Compare
d3e5994 to
ec6ae45
Compare
| // | ||
| // In BYOK mode the user's credential is in Authorization. Replace | ||
| // the centralized key with it so it is forwarded upstream. | ||
| credKind := intercept.CredentialKindCentralized |
There was a problem hiding this comment.
Isn't setting credHint missing here? Also, we should probably check if cfg.key is empty (chatgpt case), and error if the credential key is empty.
There was a problem hiding this comment.
Isn't setting credHint missing here?
Actually - no, it's later used like this:
interceptor.SetCredential(credKind, utils.MaskSecret(cfg.Key))It's easier for openai case, because authz header is always used.
if cfg.key is empty (chatgpt case), and error if the credential key is empty
Yeah, I remember about this. I can add in this PR.
There was a problem hiding this comment.
if cfg.key is empty (chatgpt case), and error if the credential key is empty
We already know that claude (maybe other clients) send HEAD requests without authentication, maybe we don't need it?
Also I think it's not specific to chatgpt case.
There was a problem hiding this comment.
My suggestion was to use a credSecret := cfg.Key like we do in Anthropic, instead of setting cfg.Key directly.
Regarding the empty key, I think this case can still happen, where we proceed to the interceptors with an empty key. This would likely be a misconfiguration, e.g., someone using the ChatGPT provider (no key configured) without an actual subscription. I assume in this case, the upstream provider would return a 401, but I think aibridge should catch this early and fail fast rather than sending a request it knows will fail. Claude Code already does this: it errors when no key is configured.
This applies to all providers, not just OpenAI, so I'm fine with addressing it separately 🙂
There was a problem hiding this comment.
My suggestion was to use a credSecret := cfg.Key like we do in Anthropic, instead of setting cfg.Key directly.
In Anthropic it's a bit different, we have 2 fields in config:
- Key
- BYOKBearerToken
So I had to usecredSecret := cfg.Key. In OpenAI implementation is easier.
Instead of setting cfg.Key
We set cfg.Key in Anthropic as well.
Regarding the empty key, I think this case can still happen, where we proceed to the interceptors with an empty key.
Theoretically it may be possible. My main concern that as we already know, request without authenticate maybe a valid request (e.g. ClaudeCode sending HEAD healthcheck request).
So I wasn't sure if we want to intervene and fail every request without authentication?
There was a problem hiding this comment.
I think for now we need to assume that all requests that reach this point have been authenticated by coder (HEAD requests without auth do not get this far).
The case I was mentioning is the case where the user is correctly authenticated via coder, but not sending any authentication to the upstream provider. This is an edge case, and as we talked yesterday, we can address this after 🙂
There was a problem hiding this comment.
@ssncferreira yeah, let's address later
The case I was mentioning is the case where the user is correctly authenticated via coder, but not sending any authentication to the upstream provider.
I meant the same. e.g. agent sent Coder-Token, but didn't send LLM token. I didn't want to fail such request, because it maybe valid request (e.g. healthcheck)?
|
I think we are missing adding the credential info to the logs, especially for error cases like 401/403/429 |
Co-authored-by: Susana Ferreira <susana@coder.com>
I extended logger with credential info in |
| // | ||
| // In BYOK mode the user's credential is in Authorization. Replace | ||
| // the centralized key with it so it is forwarded upstream. | ||
| credKind := intercept.CredentialKindCentralized |
There was a problem hiding this comment.
My suggestion was to use a credSecret := cfg.Key like we do in Anthropic, instead of setting cfg.Key directly.
Regarding the empty key, I think this case can still happen, where we proceed to the interceptors with an empty key. This would likely be a misconfiguration, e.g., someone using the ChatGPT provider (no key configured) without an actual subscription. I assume in this case, the upstream provider would return a 401, but I think aibridge should catch this early and fail fast rather than sending a request it knows will fail. Claude Code already does this: it errors when no key is configured.
This applies to all providers, not just OpenAI, so I'm fine with addressing it separately 🙂
| {"long_api_key", "sk-ant-api03-abcdefgh", "sk-a*************efgh"}, | ||
| {"unicode", "hélloworld🌍!", "hé********🌍!"}, | ||
| {"github_token", "ghp_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefgh", "ghp_******************************efgh"}, | ||
| {"short", "short", "***"}, |
There was a problem hiding this comment.
Can you please add a follow-up to make these more useful as previously discussed? Not sure what happened to my comment.
There was a problem hiding this comment.
I added logging of secret_length as we discussed:
Lines 249 to 250 in fefb191
On the call consensus was to use short format to avoid wasting DB space.
If you refer to the fact that whole secret is masked, it's by design? Because secret is too short to reveal smth. (shouldn't happen with real providers).
I think this was your original comment: #239 (comment)
But okay, I'll create follow-up PR on that.
| // | ||
| // In BYOK mode the user's credential is in Authorization. Replace | ||
| // the centralized key with it so it is forwarded upstream. | ||
| credKind := intercept.CredentialKindCentralized |
There was a problem hiding this comment.
I think for now we need to assume that all requests that reach this point have been authenticated by coder (HEAD requests without auth do not get this far).
The case I was mentioning is the case where the user is correctly authenticated via coder, but not sending any authentication to the upstream provider. This is an edge case, and as we talked yesterday, we can address this after 🙂
utils/mask_test.go
Outdated
| {"medium_15_chars", "thisisquitelong", "th***ng"}, | ||
| {"long_api_key", "sk-ant-api03-abcdefgh", "sk-a***efgh"}, | ||
| {"unicode", "hélloworld🌍!", "hé***🌍!"}, | ||
| {"github_token", "ghp_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefgh", "ghp_***efgh"}, |
There was a problem hiding this comment.
small nit: I prefer ... instead of ***. The latter is generally associated with censored words, which is not the case.
There was a problem hiding this comment.
okay, I'll replace considering Anthropic and OpenAI use ..., and we use it here: https://github.com/coder/aibridge/blob/main/intercept/apidump/headers.go#L25
I remember @dannykopping proposed ***, so I kept ***.
| ExtraHeaders: extractCopilotHeaders(r), | ||
| } | ||
|
|
||
| cred := intercept.NewCredentialInfo(intercept.CredentialKindBYOK, key) |
There was a problem hiding this comment.
I'm wondering if it makes sense to add the github token here 🤔 I don't think the copilot case fits the reasoning for logging and debugging on the UI.
There was a problem hiding this comment.
@ssncferreira
I had very similar proposal, see: #216 (comment)
Co-authored-by: Susana Ferreira <susana@coder.com>
Summary
Records credential metadata on each AI Bridge interception, enabling admins to see how requests were authenticated and identify which credential was used.
Each provider's
CreateInterceptornow captures two new fields on the interceptor:credential_kind:centralized,personal_api_key, orsubscriptioncredential_hint: masked credential for audit (e.g.sk-a...(13)...efgh)Changes
Interceptorinterface withSetCredential,CredentialKind, andCredentialHint, backed by an embeddableCredentialFieldshelperCreateInterceptor:centralized(admin key) orpersonal(Bearer token)centralized,personal_api_key(X-Api-Key), orsubscription(Bearer token)subscriptionMaskSecretto embed hidden character count (e.g.sk-a...(13)...efghinstead ofsk-a*************efgh)Relates to coder/coder#23808