Skip to content

Video polling sends the provider key to whatever host the job's polling_url names #1895

Closed

Description

@AbirAbbas

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

  • generateVideo in internal/provider/media.go: search resolve video polling URL. An absolute polling_url is kept as is by resolveEndpoint.
  • MediaClient.getJSON in the same file: it calls doEndpoint(ctx, http.MethodGet, endpoint, nil, true, model), which is always authenticated.
  • sameHostAsBase and downloadVideo in 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_video and a /videos answer whose polling_url points at a second stub on another port. The second stub records no Authorization header, and the run reports the video as failed, naming the polling host.
  • e2e: the control: the same run with polling_url on 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's invalidates says polling previously sent the key to any host.

Activity

  1. added
    bugSomething the code does that it should not
    sev:seriousWrong or missing behaviour a person meets in ordinary use
    area:providerRouting, lanes, refusals, hedging, what a call costs
    on Oct 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:providerRouting, lanes, refusals, hedging, what a call costsbugSomething the code does that it should notsev:seriousWrong or missing behaviour a person meets in ordinary use

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions