























@@ -1,22 +1,29 @@
11import type { OpenClawConfig } from "openclaw/plugin-sdk/provider-auth";
22import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
334-const { fetchWithSsrFGuardMock } = vi.hoisted(() => ({
5-fetchWithSsrFGuardMock: vi.fn(async ({ init, url }: { init?: RequestInit; url: string }) => ({
6-response: await fetch(url, init),
7-release: async () => {},
8-})),
4+const { fetchConfiguredLocalOriginWithSsrFGuardMock } = vi.hoisted(() => ({
5+fetchConfiguredLocalOriginWithSsrFGuardMock: vi.fn(
6+async ({ init, url }: { init?: RequestInit; url: string }) => ({
7+response: await fetch(url, init),
8+release: async () => {},
9+}),
10+),
911}));
10121113vi.mock("openclaw/plugin-sdk/ssrf-runtime", () => ({
12-fetchWithSsrFGuard: fetchWithSsrFGuardMock,
14+fetchWithSsrFGuard: vi.fn(),
1315formatErrorMessage: (error: unknown) => (error instanceof Error ? error.message : String(error)),
14-ssrfPolicyFromHttpBaseUrlAllowedHostname: (baseUrl: string) => {
16+ssrfPolicyFromHttpBaseUrlAllowedOrigin: (baseUrl: string) => {
1517const parsed = new URL(baseUrl);
16-return { allowedHostnames: [parsed.hostname] };
18+return { allowedOrigins: [parsed.origin] };
1719},
1820}));
192122+// Import-resolution gating for this private helper is covered in sdk-alias.test.ts.
23+vi.mock("openclaw/plugin-sdk/ssrf-runtime-internal", () => ({
24+fetchConfiguredLocalOriginWithSsrFGuard: fetchConfiguredLocalOriginWithSsrFGuardMock,
25+}));
26+2027let createOllamaEmbeddingProvider: typeof import("./embedding-provider.js").createOllamaEmbeddingProvider;
2128let ollamaMemoryEmbeddingProviderAdapter: typeof import("./memory-embedding-adapter.js").ollamaMemoryEmbeddingProviderAdapter;
2229@@ -26,7 +33,7 @@ beforeAll(async () => {
2633});
27342835beforeEach(() => {
29-fetchWithSsrFGuardMock.mockClear();
36+fetchConfiguredLocalOriginWithSsrFGuardMock.mockClear();
3037});
31383239afterEach(() => {
@@ -67,6 +74,14 @@ function readFirstEmbeddingInput(fetchMock: ReturnType<typeof mockEmbeddingFetch
6774return body.input;
6875}
697677+function firstGuardedFetchCall(): Record<string, unknown> {
78+const call = fetchConfiguredLocalOriginWithSsrFGuardMock.mock.calls[0]?.[0];
79+if (!call || typeof call !== "object") {
80+throw new Error("expected guarded fetch call");
81+}
82+return call as Record<string, unknown>;
83+}
84+7085function expectEmbeddingFetch(
7186fetchMock: ReturnType<typeof mockEmbeddingFetch>,
7287url: string,
@@ -109,6 +124,50 @@ describe("ollama embedding provider", () => {
109124expect(vector[1]).toBeCloseTo(0.8, 5);
110125});
111126127+it("marks the configured Ollama origin for managed-proxy direct routing", async () => {
128+const fetchMock = mockEmbeddingFetch([1, 0]);
129+130+const { provider } = await createOllamaEmbeddingProvider({
131+config: {} as OpenClawConfig,
132+provider: "ollama",
133+model: "nomic-embed-text",
134+fallback: "none",
135+remote: { baseUrl: "http://127.0.0.1:11434/v1" },
136+});
137+138+await provider.embedQuery("hello");
139+140+expect(fetchMock).toHaveBeenCalledTimes(1);
141+expect(firstGuardedFetchCall()).toMatchObject({
142+url: "http://127.0.0.1:11434/api/embed",
143+policy: { allowedOrigins: ["http://127.0.0.1:11434"] },
144+configuredLocalOriginBaseUrl: "http://127.0.0.1:11434",
145+auditContext: "ollama-memory-embedding",
146+});
147+});
148+149+it("passes cloud Ollama origins through the guarded fetch contract", async () => {
150+const fetchMock = mockEmbeddingFetch([1, 0]);
151+152+const { provider } = await createOllamaEmbeddingProvider({
153+config: {} as OpenClawConfig,
154+provider: "ollama",
155+model: "nomic-embed-text",
156+fallback: "none",
157+remote: { baseUrl: "https://ollama.com" },
158+});
159+160+await provider.embedQuery("hello");
161+162+expect(fetchMock).toHaveBeenCalledTimes(1);
163+expect(firstGuardedFetchCall()).toMatchObject({
164+url: "https://ollama.com/api/embed",
165+policy: { allowedOrigins: ["https://ollama.com"] },
166+configuredLocalOriginBaseUrl: "https://ollama.com",
167+auditContext: "ollama-memory-embedding",
168+});
169+});
170+112171it("resolves configured base URL and headers without sending local marker auth", async () => {
113172const fetchMock = mockEmbeddingFetch([1, 0]);
114173@@ -249,6 +308,12 @@ describe("ollama embedding provider", () => {
249308await expect(provider.embedBatch(["a", "bb", "ccc"])).resolves.toHaveLength(3);
250309expect(fetchMock).toHaveBeenCalledTimes(1);
251310expect(inputs).toEqual([["a", "bb", "ccc"]]);
311+expect(firstGuardedFetchCall()).toMatchObject({
312+url: "http://127.0.0.1:11434/api/embed",
313+policy: { allowedOrigins: ["http://127.0.0.1:11434"] },
314+configuredLocalOriginBaseUrl: "http://127.0.0.1:11434",
315+auditContext: "ollama-memory-embedding",
316+});
252317});
253318254319it("reports malformed embed JSON with a provider-owned error", async () => {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。