Repository navigation
Video polling sends the provider key to whatever host the job's polling_url names #1895
Description
What happened
MediaClient.generateVideo polls whatever polling_url the provider's job answer names, and the poll always carries the configured provider key. A polling URL on a different host gets the bearer. Downloads already guard against this (sameHostAsBase); polling does not.
Observed on 2026-10-22 against dev@d4cef55fa with the stub test below:
=== RUN TestIssueReproVideoPollingSendsKeyToAnyHost
zz_issue_repro_test.go:40: base host 127.0.0.1:40578, polling host 127.0.0.1:41182, polling request Authorization="Bearer sk-or-REPRO-KEY", GenerateVideo err=video job failed: repro stops here
zz_issue_repro_test.go:42: the polling host 127.0.0.1:41182 is not the configured host 127.0.0.1:40578 but received the bearer
--- FAIL: TestIssueReproVideoPollingSendsKeyToAnyHost (0.05s)
Found during the #1894 credential audit and listed there as a follow-up. It has not been seen against a real provider: it fires only if a provider's job answer names a polling host other than the configured one.
Replication
Deterministic (no model). Save the test below as internal/provider/zz_issue_repro_test.go and run:
go test -count=1 -run TestIssueReproVideoPollingSendsKeyToAnyHost -v ./internal/provider/Today it fails with the output above: the second host (a different host:port from the configured base) receives Authorization: Bearer sk-or-REPRO-KEY.
Test (stub servers only, no network)
package provider
import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"net/url"
"testing"
"time"
)
func TestIssueReproVideoPollingSendsKeyToAnyHost(t *testing.T) {
seen := make(chan string, 4)
other := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
seen <- r.Header.Get("Authorization")
w.Header().Set("Content-Type", "application/json")
fmt.Fprint(w, `{"id":"job-1","status":"failed","error":"repro stops here"}`)
}))
defer other.Close()
base := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
fmt.Fprintf(w, `{"id":"job-1","status":"pending","polling_url":%q}`, other.URL+"/poll/job-1")
}))
defer base.Close()
client, err := NewMediaClient(Config{APIKey: "sk-or-REPRO-KEY", BaseURL: base.URL + "/api/v1"})
if err != nil {
t.Fatal(err)
}
client.videoWait = func(context.Context, time.Duration) error { return nil }
_, err = client.GenerateVideo(context.Background(), VideoRequest{Model: "repro/video", Prompt: "x"})
b, _ := url.Parse(base.URL)
o, _ := url.Parse(other.URL)
select {
case got := <-seen:
t.Logf("base host %s, polling host %s, polling request Authorization=%q, GenerateVideo err=%v", b.Host, o.Host, got, err)
if got != "" {
t.Errorf("the polling host %s is not the configured host %s but received the bearer", o.Host, b.Host)
}
default:
t.Fatalf("polling host never asked; err=%v", err)
}
}Field (real models). Not observed. It needs a video job answer whose polling_url names a host other than the configured base.
Where
generateVideoininternal/provider/media.go: searchresolve video polling URL. An absolutepolling_urlis kept as is byresolveEndpoint.MediaClient.getJSONin the same file: it callsdoEndpoint(ctx, http.MethodGet, endpoint, nil, true, model), which is always authenticated.sameHostAsBaseanddownloadVideoin the same file are the existing guard that downloads already use.
The fix
Poll with the bearer only when sameHostAsBase(pollingURL) holds, the same decision downloadVideo already makes. A polling URL on another host is fetched without the key. If that host then refuses, the video ends with an error that names the polling host rather than retrying with the key. Polling on the configured host is unchanged.
Acceptance
- e2e: through the real binary, with a scripted stub serving the chat completion that calls
generate_videoand a/videosanswer whosepolling_urlpoints at a second stub on another port. The second stub records noAuthorizationheader, and the run reports the video as failed, naming the polling host. - e2e: the control: the same run with
polling_urlon the configured host still polls with the bearer and finishes the video. - Unit: the test above passes inverted (the foreign polling host receives no
Authorization), plus a same-host case that still receives it. - If the failure wording is new, the manual page that covers video generation (
internal/manual/chat/) quotes it, and the change entry'sinvalidatessays polling previously sent the key to any host.
Activity