Skip to content

Commit 51f9546

Browse files
committed
fix: address issue lidge-jun#758
Fixes lidge-jun#758
1 parent be177ea commit 51f9546

3 files changed

Lines changed: 45 additions & 5 deletions

File tree

src/lib/destination-policy.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -130,13 +130,20 @@ function registryAllowsPrivateNetwork(name: string): boolean {
130130
return getProviderRegistryEntry(name)?.allowPrivateNetworkByDefault === true;
131131
}
132132

133+
/** True when the provider config or registry default admits private/loopback destinations. */
134+
export function providerAllowsPrivateNetwork(
135+
name: string,
136+
provider: Pick<OcxProviderConfig, "allowPrivateNetwork">,
137+
): boolean {
138+
return provider.allowPrivateNetwork === true || registryAllowsPrivateNetwork(name);
139+
}
140+
133141
export function providerDestinationConfigError(name: string, provider: Pick<OcxProviderConfig, "baseUrl" | "allowPrivateNetwork">): string | null {
134142
const assessment = assessDestination(provider.baseUrl);
135143
if (!assessment) return null;
136144
if (assessment.kind === "public" || assessment.kind === "hostname") return null;
137145
if (assessment.kind === "metadata") return "baseUrl targets a blocked metadata endpoint";
138-
if (registryAllowsPrivateNetwork(name)) return null;
139-
if (provider.allowPrivateNetwork === true) return null;
146+
if (providerAllowsPrivateNetwork(name, provider)) return null;
140147
return `baseUrl points to a ${assessment.detail}; set allowPrivateNetwork:true only for intentionally local/self-hosted providers`;
141148
}
142149

@@ -174,7 +181,7 @@ export async function providerDestinationResolvedError(
174181
if (!hostname || isIP(hostname) !== 0 || hostname === "localhost" || hostname.endsWith(".localhost")) {
175182
return null; // literals and localhost are fully handled by the sync path
176183
}
177-
if (registryAllowsPrivateNetwork(name) || provider.allowPrivateNetwork === true) return null;
184+
if (providerAllowsPrivateNetwork(name, provider)) return null;
178185
let addresses: { address: string }[];
179186
try {
180187
addresses = await lookup(hostname, { all: true, verbatim: true });

src/lib/provider-outbound.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { OcxProviderConfig } from "../types";
22
import {
33
assessUrlDestination,
44
DestinationDnsResolutionError,
5+
providerAllowsPrivateNetwork,
56
providerDestinationConfigError,
67
resolvePublicAddresses,
78
} from "./destination-policy";
@@ -116,7 +117,8 @@ export async function providerOutboundGet(
116117
if (assessment?.kind === "metadata" || assessment?.kind === "link-local" || assessment?.kind === "unspecified") {
117118
throw new ProviderOutboundPolicyError(`provider URL targets ${assessment.detail}`);
118119
}
119-
if (!provider.allowPrivateNetwork) {
120+
const allowPrivate = providerAllowsPrivateNetwork(name, provider);
121+
if (!allowPrivate) {
120122
const destinationError = providerDestinationConfigError(name, {
121123
baseUrl: url,
122124
allowPrivateNetwork: false,
@@ -129,11 +131,12 @@ export async function providerOutboundGet(
129131
const proxyConfigured = configuredProxyFor();
130132
const resolveAddresses = dependencies.resolveAddresses ?? resolvePublicAddresses;
131133
const pinnedGet = dependencies.pinnedGet ?? pinnedHttpGet;
134+
const allowPrivate = providerAllowsPrivateNetwork(name, provider);
132135
let resolved: Awaited<ReturnType<typeof resolvePublicAddresses>>;
133136
try {
134137
resolved = await resolveAddresses(url, {
135138
context: "provider URL",
136-
allowPrivateNetwork: provider.allowPrivateNetwork,
139+
allowPrivateNetwork: allowPrivate,
137140
});
138141
} catch (error) {
139142
const dnsResolutionFailed = error instanceof DestinationDnsResolutionError

tests/provider-outbound.test.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,36 @@ describe("provider outbound GET transport", () => {
9090
expect(captured.address).toBeUndefined();
9191
});
9292

93+
test("built-in ollama admits loopback discovery without an explicit allowPrivateNetwork flag (#758)", async () => {
94+
for (const key of proxyKeys) delete process.env[key];
95+
const { providerOutboundGet } = await import("../src/lib/provider-outbound");
96+
let sawAllowPrivate: boolean | undefined;
97+
const dependencies: ProviderOutboundDependencies = {
98+
resolveAddresses: mock(async (_url, options) => {
99+
sawAllowPrivate = typeof options === "object" && options?.allowPrivateNetwork === true;
100+
return {
101+
hostname: "127.0.0.1",
102+
addresses: [{ address: "127.0.0.1", family: 4 }],
103+
privateNetwork: true,
104+
};
105+
}),
106+
pinnedGet: mock(async () => new Response('{"data":[{"id":"llama"}]}', {
107+
status: 200,
108+
headers: { "content-type": "application/json" },
109+
})),
110+
};
111+
112+
const response = await providerOutboundGet(
113+
"ollama",
114+
{ baseUrl: "http://127.0.0.1:11434/v1" },
115+
"http://127.0.0.1:11434/v1/models",
116+
{},
117+
dependencies,
118+
);
119+
expect(sawAllowPrivate).toBe(true);
120+
expect(await response.json()).toEqual({ data: [{ id: "llama" }] });
121+
});
122+
93123
test("direct redirects return the same credential-safe final-URL guidance", async () => {
94124
for (const key of proxyKeys) delete process.env[key];
95125
const redirectTarget = new URL("https://final.example/v1/models?token=secret#fragment");

0 commit comments

Comments
 (0)