chatopenai.New bypasses base-URL validation, so an http:// endpoint sends the key in cleartext
What happens
chatopenai.New skips the construction-time validation that chat.New performs,
including the base-URL check. A client configured with a cleartext http://
endpoint and no AllowInsecureBaseURL is returned with a nil error, and the API
key then goes over the wire in cleartext.
Why
New calls build directly:
func New(ctx context.Context, settings chat.Settings, opts ...Option) (chat.ChatClient, error) {
return build(ctx, settings, opts...)
}which is structurally identical to the registry factory newOpenAI. Whatever
chat.New runs around the factory (validateProviderConfig,
validateGenerationConfig, preValidateModelSupport, and assertCapabilities
afterwards) is not reached. chat.ValidateBaseURL(baseURL, allowInsecure) lives
in the core, so it is on the side that gets skipped.
Measured difference
| Config | chat.New |
chatopenai.New |
|---|---|---|
ProviderOpenAICompatible, Model set, no BaseURL |
fatal, invalid chat provider base URL |
client returned, nil error, talks to api.openai.com |
BaseURL: "http://..." without AllowInsecureBaseURL |
fatal | client returned, nil error, key sent over cleartext |
Effort: "bananas" |
reported and dropped | passed through unreported |
Produced by executing both constructors against this commit rather than by reading.
Why it matters
Validating the endpoint before credentials reach the wire is finding M-3 of the core's own security audit. This constructor walks around it.
Why a careful reader concludes it is safe
Two things corroborate the wrong answer. New's doc comment says "otherwise
chat.New with the provider name does the same job through the registry", which
states the equivalence directly. And build does perform one check of its own,
rejecting ProviderOpenAICompatible with an empty Model, so validation appears
to be happening.
The only reason to prefer chatopenai.New over chat.New is WithSeed.
Expected
Either New performs the same validation as chat.New before delegating to
build, or its doc comment stops claiming equivalence and states plainly that the
caller is responsible for validating the config. Which is right is a judgement for
whoever owns this adapter.
Found while populating this repository's AGENTS.md for #1 (closed), and documented there as a trap. Raised separately because it is a defect rather than a documentation hazard.