Skip to content

Commit ccf927f

Browse files
committed
test(litellm): cover loopback endpoint policy
1 parent a72a90c commit ccf927f

2 files changed

Lines changed: 30 additions & 25 deletions

File tree

extensions/litellm/image-generation-provider.test.ts

Lines changed: 28 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,9 @@ describe("litellm image generation provider", () => {
254254
const cases = [
255255
"http://localhost:4000",
256256
"http://127.0.0.1:4000",
257+
"http://127.255.255.254:4000",
257258
"http://[::1]:4000",
259+
"http://[0:0:0:0:0:0:0:1]:4000",
258260
"http://host.docker.internal:4000",
259261
"https://localhost:4000",
260262
] as const;
@@ -283,6 +285,7 @@ describe("litellm image generation provider", () => {
283285
"https://192.168.5.10:4000",
284286
"http://printer.local:4000",
285287
"http://proxy.internal:4000",
288+
"http://127.evil.com:4000",
286289
"https://metadata.google.internal",
287290
] as const;
288291
for (const baseUrl of cases) {
@@ -303,33 +306,36 @@ describe("litellm image generation provider", () => {
303306
}
304307
});
305308

306-
it("honors explicit private-network opt-in for a LAN LiteLLM proxy", async () => {
307-
mockGeneratedPngResponse();
309+
it.each(["http://192.168.5.10:4000", "http://127.evil.com:4000"])(
310+
"honors explicit private-network opt-in for %s",
311+
async (baseUrl) => {
312+
mockGeneratedPngResponse();
308313

309-
const provider = buildLitellmImageGenerationProvider();
310-
await provider.generateImage({
311-
provider: "litellm",
312-
model: "gpt-image-2",
313-
prompt: "x",
314-
cfg: {
315-
models: {
316-
providers: {
317-
litellm: {
318-
baseUrl: "http://192.168.5.10:4000",
319-
request: { allowPrivateNetwork: true },
320-
models: [],
314+
const provider = buildLitellmImageGenerationProvider();
315+
await provider.generateImage({
316+
provider: "litellm",
317+
model: "gpt-image-2",
318+
prompt: "x",
319+
cfg: {
320+
models: {
321+
providers: {
322+
litellm: {
323+
baseUrl,
324+
request: { allowPrivateNetwork: true },
325+
models: [],
326+
},
321327
},
322328
},
323329
},
324-
},
325-
});
330+
});
326331

327-
expectFields(mockObjectArg(resolveProviderHttpRequestConfigMock), {
328-
allowPrivateNetwork: undefined,
329-
request: { allowPrivateNetwork: true },
330-
});
331-
expect(mockObjectArg(postJsonRequestMock).allowPrivateNetwork).toBe(true);
332-
});
332+
expectFields(mockObjectArg(resolveProviderHttpRequestConfigMock), {
333+
allowPrivateNetwork: undefined,
334+
request: { allowPrivateNetwork: true },
335+
});
336+
expect(mockObjectArg(postJsonRequestMock).allowPrivateNetwork).toBe(true);
337+
},
338+
);
333339

334340
it("does not allow private network for public hosts that embed private strings in the URL", async () => {
335341
// Must not be fooled by an attacker-controlled URL that mentions

extensions/litellm/image-generation-provider.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,8 @@ function isAutoAllowedLitellmHostname(hostname: string): boolean {
6363
) {
6464
return true;
6565
}
66-
// Only literal IPv4 loopback addresses qualify — DNS hostnames like
67-
// 127.evil.com must not auto-bypass the allowPrivateNetwork opt-in.
68-
if (lowered === "127.0.0.1" || (isIP(lowered) === 4 && lowered.startsWith("127."))) {
66+
// Only IPv4 literals may use the 127/8 loopback exemption.
67+
if (isIP(lowered) === 4 && lowered.startsWith("127.")) {
6968
return true;
7069
}
7170
if (lowered === "::1" || lowered === "0:0:0:0:0:0:0:1") {

0 commit comments

Comments
 (0)