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.