cloudwego / cloudwego/eino

components/model/ollama: requests to ollama.com fail on hosts without ~/.ollama/id_ed25519 — forced request signing has no opt-out

Open
#1,277 1 comment 0 reactions 0 assignees View on GitHub
Dominant language
Go
Stars
13k
Forks
1.1k
Avg merge
4h 6m
Merged PRs (30d)
41

Description

## 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/ollama` v0.1.9
- `github.com/eino-contrib/ollama` v0.1.0 (only published version)
- Go 1.25, linux/amd64
- No `~/.ollama/id_ed25519` on the host (headless server)

## Repro

```go
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 directory
```

The 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):

```go
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_ed25519` and returns an error
when missing, aborting the request. It also emits `slog.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 `Authorization` at `client.go:143` **before**
`c.http.Do()`, so a caller-supplied `http.RoundTripper` that sets its own
`Authorization: Bearer ` 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)

1. Skip signing when the caller's `HTTPClient`/transport will set its own
`Authorization` (or expose an explicit `SkipAuth`/`NoSign` option on `Client`).
2. Honor `OLLAMA_AUTH=false` as an opt-out by changing `||` to a plain check of
`UseAuth()` and treating the `ollama.com` hostname as the *default* (on) rather
than *forced*.
3. 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.)

Contributor guide

Open the contributing guide

Research direction

Start in api/client.go at do() and stream(), then read envconfig/config.go and auth/auth.go to trace the forced signing and missing-key error. Reproduce the keyless https://ollama.com request with a caller-supplied Authorization header. Done means the documented API-key use works without ~/.ollama/id_ed25519 while existing Ollama authentication behavior remains covered.

Written by the indexing model from the issue text.

Assessment

Tech stack
go, ollama
Domain
api, authentication, backend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
50/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.