Summary
When a connector authenticates with OAuth M2M (WithClientCredentials, or client credentials via DSN), the driver's token requests are issued with Go's default HTTP client, so they reach the workspace as Go-http-client/2.0 instead of the User-Agent configured through WithUserAgentEntry. Query traffic is attributed correctly — only the token exchange is anonymous.
This matters for ISV partner attribution: the Databricks partner program asks integrations to identify themselves via a programmatic User-Agent, and these token grants show up in system.access.audit as an unidentified generic Go client.
Current behavior
Connector configured as:
connector, err := dbsql.NewConnector(
dbsql.WithServerHostname(host),
dbsql.WithPort(443),
dbsql.WithHTTPPath(httpPath),
dbsql.WithClientCredentials(clientID, clientSecret),
dbsql.WithUserAgentEntry("MyISV_MyProduct/1.0"),
)
system.access.audit rows for the resulting activity (identifiers redacted):
| service_name |
action_name |
user_agent |
| workspace |
workspaceInHouseOAuthClientAuthentication |
Go-http-client/2.0 |
| workspace |
mintOAuthToken |
Go-http-client/2.0 |
| accounts |
oidcTokenAuthorization |
godatabrickssqlconnector/1.13.0 (MyISV_MyProduct/1.0) |
The Thrift/query path carries the configured entry as expected; the token-mint requests do not.
Root cause
BuildUserAgent(cfg) is applied only to the Thrift HTTP client:
internal/client/client.go — thriftHttpClient.SetHeader("User-Agent", BuildUserAgent(cfg))
The M2M authenticator builds its token source with a hardcoded background context, so a caller has no way to supply an *http.Client — oauth2 takes its HTTP client from the context (oauth2.HTTPClient), and that context never leaves the package:
auth/oauth/m2m/m2m.go (on main) — GetConfig(context.Background(), ...) and config.TokenSource(context.Background())
Because of that, neither WithUserAgentEntry nor WithTransport can influence token requests.
This is adjacent to a limitation already acknowledged in-tree for the endpoint-resolution path (auth/oauth/oauth.go):
NOTE: this client uses the default transport, matching the existing oidc.NewProvider discovery below. A connector-supplied transport / TLS config (WithTransport, WithSkipTLSHostVerify) is not yet threaded into the OAuth endpoint-resolution path; that is a pre-existing limitation, tracked separately.
This report is that same limitation one step further along: the token exchange itself.
Expected behavior
Token requests (and ideally OIDC discovery) carry the same User-Agent as the rest of the connector's traffic — which is what BuildUserAgent's own doc comment states as the intent:
…used by the driver for Thrift, telemetry, and feature-flag requests so all traffic from a single connection is attributable to the same identifier in access logs.
Possible directions
- Thread the connector's HTTP client/context into the authenticator — e.g. have the connector pass an
*http.Client built with the driver's transport + User-Agent, and create the token source with context.WithValue(ctx, oauth2.HTTPClient, client).
- Or expose an option to supply the token-exchange HTTP client, so callers can set headers without re-implementing the M2M flow.
We verified direction 1 works: injecting an *http.Client whose RoundTripper sets the User-Agent into the token source makes every mintOAuthToken audit row correctly attributed. We did not keep that as a workaround — replacing the built-in authenticator with an application-side copy of the credential flow would risk drifting from upstream on every release, which is exactly why we'd prefer this handled in the driver.
Environment
databricks-sql-go v1.13.0 — code path verified unchanged in v1.14.0 and on main
- Auth: OAuth M2M (client credentials, Databricks-managed service principal)
- Go 1.25, Linux
Happy to open a PR for direction 1 if you'd like it contributed.
Summary
When a connector authenticates with OAuth M2M (
WithClientCredentials, or client credentials via DSN), the driver's token requests are issued with Go's default HTTP client, so they reach the workspace asGo-http-client/2.0instead of the User-Agent configured throughWithUserAgentEntry. Query traffic is attributed correctly — only the token exchange is anonymous.This matters for ISV partner attribution: the Databricks partner program asks integrations to identify themselves via a programmatic User-Agent, and these token grants show up in
system.access.auditas an unidentified generic Go client.Current behavior
Connector configured as:
system.access.auditrows for the resulting activity (identifiers redacted):workspaceInHouseOAuthClientAuthenticationGo-http-client/2.0mintOAuthTokenGo-http-client/2.0oidcTokenAuthorizationgodatabrickssqlconnector/1.13.0 (MyISV_MyProduct/1.0)The Thrift/query path carries the configured entry as expected; the token-mint requests do not.
Root cause
BuildUserAgent(cfg)is applied only to the Thrift HTTP client:internal/client/client.go—thriftHttpClient.SetHeader("User-Agent", BuildUserAgent(cfg))The M2M authenticator builds its token source with a hardcoded background context, so a caller has no way to supply an
*http.Client—oauth2takes its HTTP client from the context (oauth2.HTTPClient), and that context never leaves the package:auth/oauth/m2m/m2m.go(onmain) —GetConfig(context.Background(), ...)andconfig.TokenSource(context.Background())Because of that, neither
WithUserAgentEntrynorWithTransportcan influence token requests.This is adjacent to a limitation already acknowledged in-tree for the endpoint-resolution path (
auth/oauth/oauth.go):This report is that same limitation one step further along: the token exchange itself.
Expected behavior
Token requests (and ideally OIDC discovery) carry the same User-Agent as the rest of the connector's traffic — which is what
BuildUserAgent's own doc comment states as the intent:Possible directions
*http.Clientbuilt with the driver's transport + User-Agent, and create the token source withcontext.WithValue(ctx, oauth2.HTTPClient, client).We verified direction 1 works: injecting an
*http.ClientwhoseRoundTrippersets the User-Agent into the token source makes everymintOAuthTokenaudit row correctly attributed. We did not keep that as a workaround — replacing the built-in authenticator with an application-side copy of the credential flow would risk drifting from upstream on every release, which is exactly why we'd prefer this handled in the driver.Environment
databricks-sql-gov1.13.0 — code path verified unchanged in v1.14.0 and onmainHappy to open a PR for direction 1 if you'd like it contributed.