Skip to content

feat: byok-observability for aibridge#239

Open
evgeniy-scherbina wants to merge 1 commit intomainfrom
yevhenii/byok-observability
Open

feat: byok-observability for aibridge#239
evgeniy-scherbina wants to merge 1 commit intomainfrom
yevhenii/byok-observability

Conversation

@evgeniy-scherbina
Copy link
Copy Markdown
Contributor

@evgeniy-scherbina evgeniy-scherbina commented Mar 30, 2026

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 CreateInterceptor now captures two new fields on the interceptor:

  • credential_kind: centralized, personal_api_key, or subscription
  • credential_hint: masked credential for audit (e.g. sk-a...(13)...efgh)

Changes

  • Extended Interceptor interface with SetCredential, CredentialKind, and CredentialHint, backed by an embeddable CredentialFields helper
  • Each provider sets credential metadata in CreateInterceptor:
    • OpenAI: centralized (admin key) or personal (Bearer token)
    • Anthropic: centralized, personal_api_key (X-Api-Key), or subscription (Bearer token)
    • Copilot: always subscription
  • Improved MaskSecret to embed hidden character count (e.g. sk-a...(13)...efgh instead of sk-a*************efgh)

Relates to coder/coder#23808

@evgeniy-scherbina evgeniy-scherbina force-pushed the yevhenii/byok-observability branch from 9bf93dd to c5dd566 Compare March 30, 2026 22:37
@evgeniy-scherbina evgeniy-scherbina changed the base branch from yevhenii/aibridge-chatgpt-provider to ssncf/feat-validate-provider-names March 30, 2026 22:38
@ssncferreira ssncferreira force-pushed the ssncf/feat-validate-provider-names branch 2 times, most recently from 71c69d3 to 30d306a Compare March 31, 2026 10:48
@evgeniy-scherbina evgeniy-scherbina force-pushed the yevhenii/byok-observability branch from c5dd566 to d6e95ea Compare March 31, 2026 14:00
@ssncferreira ssncferreira force-pushed the ssncf/feat-validate-provider-names branch from 7d0c198 to b1c6f3f Compare March 31, 2026 14:36
@evgeniy-scherbina evgeniy-scherbina force-pushed the yevhenii/byok-observability branch from d6e95ea to b6a01e9 Compare March 31, 2026 16:47
@evgeniy-scherbina evgeniy-scherbina changed the base branch from ssncf/feat-validate-provider-names to main March 31, 2026 16:47
@evgeniy-scherbina evgeniy-scherbina force-pushed the yevhenii/byok-observability branch from e1e15df to b6a01e9 Compare March 31, 2026 17:38
if cfg.BYOKBearerToken != "" {
credHint = intercept.MaskCredential(cfg.BYOKBearerToken)
}
interceptor.SetCredential(credKind, credHint)
Copy link
Copy Markdown
Contributor Author

@evgeniy-scherbina evgeniy-scherbina Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider moving it to constructors instead of SetCredential?

  • messages.NewStreamingInterceptor(NewCredentialFields(credKind, credHint), ...)
  • messages.NewBlockingInterceptor(NewCredentialFields(credKind, credHint), ...)
  • ...
    ?

@evgeniy-scherbina evgeniy-scherbina force-pushed the yevhenii/byok-observability branch from a610090 to d3e5994 Compare March 31, 2026 19:20
return nil, UnknownRoute
}

interceptor.SetCredential(intercept.CredentialKindSubscription, utils.MaskSecret(key))
Copy link
Copy Markdown
Contributor Author

@evgeniy-scherbina evgeniy-scherbina Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CredentialKindSubscription makes sense for Copilot?

credKind := intercept.CredentialKindCentralized
if token := utils.ExtractBearerToken(r.Header.Get("Authorization")); token != "" {
cfg.Key = token
credKind = intercept.CredentialKindPersonalAPIKey
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit tricky, it can be either CredentialKindPersonalAPIKey or CredentialKindSubscription.
We can rely on some hacks to determine it:

  • Presence of Chatgpt-Account-Id header
  • Check is it JWT Token or API KEY
  • Rely on URL (openai vs chatgpt)

See more details here: #227 (comment)

Alternatively we can introduce new enum value, smth like: CredentialKindPersonal

suffix := string(runes[len(runes)-reveal:])
masked := len(runes) - reveal*2
return prefix + strings.Repeat("*", masked) + suffix
return prefix + fmt.Sprintf("...(%d)...", masked) + suffix
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This approach preserves the length of the secret, but doesn't generate huge strings which we need to log and store in DB.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant