components/model/ollama: requests to ollama.com fail on hosts without ~/.ollama/id_ed25519 — forced request signing has no opt-out
Summary
components/model/ollama (v0.1.9, dep eino-contrib/ollama v0.1.0) unconditionally
signs every request with the local Ollama CLI's private key (~/.ollama/id_ed25519)
when the base URL hostname is ollama.com. On hosts where the Ollama app was never
installed (servers, CI, containers), the key is absent, so every API call fails
before the request is sent — even when the app supplies its own Authorization
header via the injected HTTPClient (the documented way to use an API key).
Environment
github.com/cloudwego/eino-ext/components/model/ollamav0.1.9github.com/eino-contrib/ollamav0.1.0 (only published version)- Go 1.25, linux/amd64
- No
~/.ollama/id_ed25519on the host (headless server)
Repro
package main
import (
"context"
"fmt"
"net/http"
"net/url"
"github.com/eino-contrib/ollama/api"
)
func main() {
base, _ := url.Parse("https://ollama.com")
client := api.NewClient(base, &http.Client{})
_, err := client.List(context.Background())
fmt.Println("err:", err)
// err: open /home/user/.ollama/id_ed25519: no such file or directory
}Output on a keyless host:
{"level":"INFO","msg":"Failed to load private key: open /home/user/.ollama/id_ed25519: no such file or directory"}
err: open /home/user/.ollama/id_ed25519: no such file or directoryThe same snippet against https://ollama.com. (trailing dot) succeeds — confirming
the hardcoded hostname comparison is the trigger.
Root cause
api/client.go, in both do() (line 119) and stream() (line 184):
if envconfig.UseAuth() || c.base.Hostname() == "ollama.com" {
token, err = getAuthorizationToken(ctx, chal) // → auth.Sign()
if err != nil {
return err // request aborted before it is sent
}
...
}- The hostname branch is unconditional.
OLLAMA_AUTH(envconfig/config.go:198) participates via||, so it can only add the signing requirement, never remove it. auth.Sign(auth/auth.go:59) reads~/.ollama/id_ed25519and returns an error when missing, aborting the request. It also emitsslog.Info("Failed to load private key: ...")to the host application's default logger — INFO-level noise for a hard failure.- The signed token is set as
Authorizationatclient.go:143beforec.http.Do(), so a caller-suppliedhttp.RoundTripperthat sets its ownAuthorization: Bearer <api-key>would overwrite it anyway — the signing is redundant in that setup, yet still mandatory.
This makes ChatModel with BaseURL: "https://ollama.com" + API-key auth via
HTTPClient unusable on any host without the Ollama CLI installed — the primary
use case for a model-component library running on servers.
Suggested fixes (any one)
- Skip signing when the caller's
HTTPClient/transport will set its ownAuthorization(or expose an explicitSkipAuth/NoSignoption onClient). - Honor
OLLAMA_AUTH=falseas an opt-out by changing||to a plain check ofUseAuth()and treating theollama.comhostname as the default (on) rather than forced. - Fall back to an unsigned request when the key is missing instead of aborting.
Workaround
Use the FQDN form with a trailing dot: https://ollama.com. — valid DNS,
url.Parse does not normalize the hostname, so the comparison misses while
DNS/TLS treat it identically. (Also https://OLLAMA.COM works, but a future
case-insensitive comparison would silently break it.)
Source: cloudwego/eino