diff --git a/backend/internal/service/grok_media.go b/backend/internal/service/grok_media.go index 8827f2d00..2ce2bc802 100644 --- a/backend/internal/service/grok_media.go +++ b/backend/internal/service/grok_media.go @@ -512,7 +512,7 @@ func (s *OpenAIGatewayService) forwardGrokMediaVideoContent( return nil, err } - contentURL, err := grokMediaSignedVideoContentURL(statusBody) + contentURL, err := grokMediaSignedVideoContentURL(statusBody, requestID) if err != nil { SetOpsLatencyMs(c, OpsUpstreamLatencyMsKey, time.Since(upstreamStart).Milliseconds()) return nil, err @@ -575,11 +575,18 @@ func (s *OpenAIGatewayService) forwardGrokMediaVideoContent( }, nil } -func grokMediaSignedVideoContentURL(body []byte) (string, error) { +func grokMediaSignedVideoContentURL(body []byte, requestID string) (string, error) { rawURL := strings.TrimSpace(gjson.GetBytes(body, "video.url").String()) if rawURL == "" { return "", nil } + // An upstream Sub2API rewrites protected content URLs to its own proxy + // endpoint. Treat that as an authenticated relay path, not as a signed URL; + // the caller will rebuild it against the configured account base URL and + // attach the upstream API key. + if isGrokMediaVideoContentURL(rawURL, requestID) { + return "", nil + } parsed, err := url.Parse(rawURL) if err != nil || !strings.EqualFold(parsed.Scheme, "https") || !strings.EqualFold(parsed.Hostname(), "vidgen.x.ai") || diff --git a/backend/internal/service/grok_media_content_test.go b/backend/internal/service/grok_media_content_test.go index ba6b1d0ff..62f058a00 100644 --- a/backend/internal/service/grok_media_content_test.go +++ b/backend/internal/service/grok_media_content_test.go @@ -220,6 +220,40 @@ func TestForwardGrokMediaContentFetchesValidatedSignedURLWithoutCredentials(t *t require.True(t, HTTPUpstreamRedirectsDisabled(upstream.requests[1].Context())) } +func TestForwardGrokMediaContentFollowsAuthenticatedSub2APIRelay(t *testing.T) { + for _, statusURL := range []string{ + `/v1/videos/task-1/content`, + `https://relay.example/v1/videos/task-1/content`, + } { + t.Run(statusURL, func(t *testing.T) { + upstream := &grokMediaContentUpstreamStub{ + responses: []*http.Response{ + grokMediaContentStatusResponse(`{"status":"completed","video":{"url":"` + statusURL + `"}}`), + { + StatusCode: http.StatusOK, + Header: http.Header{"Content-Type": []string{"video/mp4"}}, + Body: io.NopCloser(strings.NewReader("video-payload")), + }, + }, + } + svc := &OpenAIGatewayService{cfg: &config.Config{}, httpUpstream: upstream} + c, recorder := grokMediaContentTestContext(http.MethodGet, "https://api.example/v1/videos/task-1/content", nil) + + _, err := svc.ForwardGrokMedia( + context.Background(), c, grokMediaContentTestAccount(), + GrokMediaEndpointVideoContent, "task-1", nil, "", + ) + + require.NoError(t, err) + require.Equal(t, http.StatusOK, recorder.Code) + require.Equal(t, "video-payload", recorder.Body.String()) + require.Len(t, upstream.requests, 2) + require.Equal(t, "https://relay.example/v1/videos/task-1/content", upstream.requests[1].URL.String()) + require.Equal(t, "Bearer upstream-key", upstream.requests[1].Header.Get("Authorization")) + }) + } +} + func TestForwardGrokMediaContentRejectsUntrustedSignedURL(t *testing.T) { upstream := &grokMediaContentUpstreamStub{ responses: []*http.Response{ @@ -246,12 +280,21 @@ func TestGrokMediaSignedVideoContentURLRejectsDeceptiveOrigins(t *testing.T) { "http://vidgen.x.ai/video.mp4", } { t.Run(rawURL, func(t *testing.T) { - _, err := grokMediaSignedVideoContentURL([]byte(`{"video":{"url":"` + rawURL + `"}}`)) + _, err := grokMediaSignedVideoContentURL([]byte(`{"video":{"url":"`+rawURL+`"}}`), "task-1") require.ErrorContains(t, err, "unsupported video content URL") }) } } +func TestGrokMediaSignedVideoContentURLRejectsDifferentRelayTask(t *testing.T) { + _, err := grokMediaSignedVideoContentURL( + []byte(`{"video":{"url":"/v1/videos/task-2/content"}}`), + "task-1", + ) + + require.ErrorContains(t, err, "unsupported video content URL") +} + func TestForwardGrokVideoStatusRewritesOnlyProtectedContentURL(t *testing.T) { statusBody := `{"id":"task-1","status":"completed","url":"https://relay.example/v1/videos/task-1/content","download_url":"/v1/videos/task-1/content","video_url":"https://vidgen.x.ai/task-1.mp4","counter":9007199254740993}` upstream := &grokMediaContentUpstreamStub{