mirror of
https://github.com/lukaszraczylo/traefik-llmgateway.git
synced 2026-10-19 05:47:48 +00:00
fix(security): harden retry, SSE, Redis and cost-budget paths
The gateway refuses a request for an unpriced model when the caller has a cost budget. An unpriced model cost 0, so the budget counter was never written and the budget could never fire. The new allowUnpricedWithCostBudget setting restores the old behaviour. The retry loop honours an upstream Retry-After of up to 30s and jitters its own computed backoff. SSE parsing bounds the total size of one event. The Redis client can use TLS. Provider base URLs and errors are scrubbed of credentials before they reach logs or the admin view. The gateway warns when failover and retry settings multiply into a large number of outbound calls. Claude-Session: https://claude.ai/code/session_01V5jYJNxpDFdJkyyExpADrv
This commit is contained in:
+20
-3
@@ -906,7 +906,14 @@ func (m *modelRegistry) warmFill(ctx context.Context) {
|
||||
cancel()
|
||||
m.finishRefresh(st, m.now(), ids, err)
|
||||
if err != nil {
|
||||
m.log("%s", fmt.Sprintf("model registry: initial discovery for provider %q failed: %v", name, err))
|
||||
// sanitizeProviderErr (admin.go): the error commonly embeds the
|
||||
// dialed URL verbatim, so a baseUrl an operator misconfigured
|
||||
// with embedded credentials would otherwise be written to
|
||||
// stderr in clear — which m.log (g.errorf, logger.go) does
|
||||
// unconditionally and unfiltered, against its own "Never log
|
||||
// key material" contract. Security audit run-1, finding F-2.
|
||||
m.log("%s", fmt.Sprintf("model registry: initial discovery for provider %q failed: %v",
|
||||
name, sanitizeProviderErr(err.Error(), m.adapters[name].base())))
|
||||
}
|
||||
// Own timeout budget, review fix (SHOULD-4): fctx above is
|
||||
// spent by listModels — reusing it here would hand the metadata
|
||||
@@ -982,7 +989,12 @@ func (m *modelRegistry) captureModelMetadata(ctx context.Context, name string, a
|
||||
}
|
||||
meta, err := mf.fetchModelMetadata(ctx)
|
||||
if err != nil {
|
||||
m.log("%s", fmt.Sprintf("model registry: metadata capture for provider %q failed: %v", name, err))
|
||||
// Scrubbed for the same reason as warmFill's own discovery-failure
|
||||
// line: fetchModelMetadata dials adapter.base()+metadataPath, so
|
||||
// this error carries the identical credential-bearing URL.
|
||||
// Security audit run-1, finding F-2.
|
||||
m.log("%s", fmt.Sprintf("model registry: metadata capture for provider %q failed: %v",
|
||||
name, sanitizeProviderErr(err.Error(), adapter.base())))
|
||||
return
|
||||
}
|
||||
if meta == nil {
|
||||
@@ -1045,7 +1057,12 @@ func (m *modelRegistry) refreshProvider(name string, st *providerState, adapter
|
||||
}
|
||||
m.finishRefresh(st, m.now(), ids, err)
|
||||
if err != nil {
|
||||
m.log("%s", fmt.Sprintf("model registry: discovery refresh for provider %q failed: %v", name, err))
|
||||
// Scrubbed (security audit run-1, finding F-2). This line
|
||||
// repeats on EVERY background refresh interval with no dedup,
|
||||
// so an unreachable provider whose baseUrl embeds a credential
|
||||
// would otherwise write it to stderr indefinitely.
|
||||
m.log("%s", fmt.Sprintf("model registry: discovery refresh for provider %q failed: %v",
|
||||
name, sanitizeProviderErr(err.Error(), adapter.base())))
|
||||
}
|
||||
}()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user