diff --git a/src/lib/onboard/setup-nim-vllm.test.ts b/src/lib/onboard/setup-nim-vllm.test.ts index 5041cfe56b8..a83fe7a746e 100644 --- a/src/lib/onboard/setup-nim-vllm.test.ts +++ b/src/lib/onboard/setup-nim-vllm.test.ts @@ -134,6 +134,62 @@ describe("setupNim vLLM route containment", () => { expect(renderedOutput).not.toContain("localhost:8000"); }); + it("authorizes a managed loopback endpoint without a trusted-private capability (#8539)", async () => { + const apiKey = "a".repeat(64); + const queryVllmModels = vi.fn(() => JSON.stringify({ data: [{ id: "served/model" }] })); + const validateOpenAiLikeSelection = vi.fn(async () => ({ + ok: true, + api: "openai-completions", + })); + const handler = createSetupNimVllmHandler( + deps({ + getLocalProviderBaseUrl: () => "http://127.0.0.1:8000/v1", + getLocalProviderValidationBaseUrl: () => "http://127.0.0.1:8000/v1", + getManagedVllmProviderBinding: () => ({ + baseUrl: "http://127.0.0.1:8000/v1", + apiKey, + }), + queryVllmModels, + validateOpenAiLikeSelection, + }), + ); + + await expect(handler(state(null))).resolves.toBe("selected"); + expect(validateOpenAiLikeSelection).toHaveBeenCalledWith( + "Local vLLM", + "http://127.0.0.1:8000/v1", + "served/model", + null, + undefined, + undefined, + { apiKey, pinnedAddresses: [], trustedPrivateCapability: undefined }, + ); + }); + + it("fails closed for a managed endpoint that is neither loopback nor operator-trusted private", async () => { + const queryVllmModels = vi.fn(() => JSON.stringify({ data: [{ id: "served/model" }] })); + const validateOpenAiLikeSelection = vi.fn(async () => ({ ok: true })); + const handler = createSetupNimVllmHandler( + deps({ + getLocalProviderBaseUrl: () => "http://93.184.216.34:8000/v1", + getLocalProviderValidationBaseUrl: () => "http://93.184.216.34:8000/v1", + getManagedVllmProviderBinding: () => ({ + baseUrl: "http://93.184.216.34:8000/v1", + apiKey: "a".repeat(64), + }), + queryVllmModels, + validateOpenAiLikeSelection, + }), + ); + + await expect(handler(state(null))).rejects.toThrow("exit 1"); + expect(queryVllmModels).not.toHaveBeenCalled(); + expect(validateOpenAiLikeSelection).not.toHaveBeenCalled(); + expect(console.error).toHaveBeenCalledWith( + " Managed vLLM endpoint authorization could not be verified.", + ); + }); + it("rejects a root-matched alias with topology-neutral recovery for a managed dual endpoint", async () => { const selection = state("required/model"); const validateOpenAiLikeSelection = vi.fn(async () => ({ ok: true })); diff --git a/src/lib/onboard/setup-nim-vllm.ts b/src/lib/onboard/setup-nim-vllm.ts index 4459c252146..5d276679342 100644 --- a/src/lib/onboard/setup-nim-vllm.ts +++ b/src/lib/onboard/setup-nim-vllm.ts @@ -3,9 +3,11 @@ import { assertEndpointResolvesPublic, + isTrustedPrivateEndpointCapability, type TrustedPrivateEndpointCapability, } from "../inference/endpoint-ssrf-preflight"; import { VLLM_MODELS } from "../inference/vllm-models"; +import { isLoopbackHostname } from "../private-networks"; import { cliName } from "./branding"; import type { SetupNimSelectionResult, SetupNimSelectionState } from "./setup-nim-flow"; @@ -77,7 +79,13 @@ async function managedVllmValidationOptions(baseUrl: string, apiKey: string) { const preflight = await assertEndpointResolvesPublic(baseUrl, undefined, { trustedPrivateHosts: [hostname], }); - if (!preflight.ok || !preflight.trustedPrivateCapability) { + if (!preflight.ok) { + throw new Error("Managed vLLM endpoint authorization failed."); + } + if ( + !isLoopbackHostname(hostname) && + !isTrustedPrivateEndpointCapability(preflight.trustedPrivateCapability) + ) { throw new Error("Managed vLLM endpoint authorization failed."); } return { @@ -263,6 +271,16 @@ export function createSetupNimVllmHandler( ? " ✓ Using managed vLLM endpoint" : ` ✓ Using existing vLLM on localhost:${deps.VLLM_PORT}`, ); + let managedValidationOptions: Awaited> | null = + null; + if (apiKey) { + try { + managedValidationOptions = await managedVllmValidationOptions(validationBaseUrl, apiKey); + } catch { + console.error(" Managed vLLM endpoint authorization could not be verified."); + deps.exitProcess(1); + } + } const raw = apiKey ? deps.queryVllmModels(validationBaseUrl, apiKey) : deps.runCapture(["curl", "-sf", `${validationBaseUrl}/models`], { @@ -330,16 +348,6 @@ export function createSetupNimVllmHandler( } const validationModel = deps.requireValue(state.model, "Expected a detected vLLM model"); - let managedValidationOptions: Awaited> | null = - null; - if (apiKey) { - try { - managedValidationOptions = await managedVllmValidationOptions(validationBaseUrl, apiKey); - } catch { - console.error(" Managed vLLM endpoint authorization could not be verified."); - deps.exitProcess(1); - } - } const validation = apiKey ? await deps.validateOpenAiLikeSelection( "Local vLLM",