mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:10:44 +00:00
fix(extensions): guard model and Twilio fetches
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { discoverKilocodeModels, KILOCODE_MODELS_URL } from "./provider-models.js";
|
||||
|
||||
const { fetchWithSsrFGuardMock } = vi.hoisted(() => ({
|
||||
fetchWithSsrFGuardMock: vi.fn(),
|
||||
@@ -7,8 +6,13 @@ const { fetchWithSsrFGuardMock } = vi.hoisted(() => ({
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/ssrf-runtime", () => ({
|
||||
fetchWithSsrFGuard: fetchWithSsrFGuardMock,
|
||||
ssrfPolicyFromHttpBaseUrlAllowedHostname: (baseUrl: string) => ({
|
||||
allowedHostnames: [new URL(baseUrl).hostname],
|
||||
}),
|
||||
}));
|
||||
|
||||
import { discoverKilocodeModels, KILOCODE_MODELS_URL } from "./provider-models.js";
|
||||
|
||||
type MockKilocodeFetchResponse = {
|
||||
ok: boolean;
|
||||
status?: number;
|
||||
@@ -79,9 +83,14 @@ async function withFetchPathTest(mockFetch: MockKilocodeFetch, runAssertions: ()
|
||||
delete process.env.NODE_ENV;
|
||||
delete process.env.VITEST;
|
||||
|
||||
fetchWithSsrFGuardMock.mockReset();
|
||||
const callMockFetch = mockFetch as unknown as (
|
||||
url: string,
|
||||
init?: RequestInit,
|
||||
) => Promise<unknown>;
|
||||
fetchWithSsrFGuardMock.mockImplementation(
|
||||
async (params: { url: string; init?: RequestInit }) => ({
|
||||
response: await mockFetch(params.url, params.init),
|
||||
response: await callMockFetch(params.url, params.init),
|
||||
release,
|
||||
}),
|
||||
);
|
||||
@@ -89,7 +98,6 @@ async function withFetchPathTest(mockFetch: MockKilocodeFetch, runAssertions: ()
|
||||
try {
|
||||
await runAssertions();
|
||||
} finally {
|
||||
fetchWithSsrFGuardMock.mockReset();
|
||||
if (origNodeEnv === undefined) {
|
||||
delete process.env.NODE_ENV;
|
||||
} else {
|
||||
@@ -100,6 +108,7 @@ async function withFetchPathTest(mockFetch: MockKilocodeFetch, runAssertions: ()
|
||||
} else {
|
||||
process.env.VITEST = origVitest;
|
||||
}
|
||||
fetchWithSsrFGuardMock.mockReset();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,6 +150,9 @@ describe("discoverKilocodeModels (fetch path)", () => {
|
||||
init: expect.objectContaining({
|
||||
headers: { Accept: "application/json" },
|
||||
}),
|
||||
policy: { allowedHostnames: ["api.kilo.ai"] },
|
||||
timeoutMs: 5000,
|
||||
auditContext: "kilocode.model_discovery",
|
||||
}),
|
||||
);
|
||||
expect(mockFetch).toHaveBeenCalledWith(
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import type { ModelDefinitionConfig } from "openclaw/plugin-sdk/provider-model-shared";
|
||||
import { createSubsystemLogger } from "openclaw/plugin-sdk/runtime-env";
|
||||
import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
|
||||
import {
|
||||
fetchWithSsrFGuard,
|
||||
ssrfPolicyFromHttpBaseUrlAllowedHostname,
|
||||
} from "openclaw/plugin-sdk/ssrf-runtime";
|
||||
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
|
||||
|
||||
const log = createSubsystemLogger("kilocode-models");
|
||||
@@ -142,6 +145,7 @@ export async function discoverKilocodeModels(): Promise<ModelDefinitionConfig[]>
|
||||
headers: { Accept: "application/json" },
|
||||
},
|
||||
timeoutMs: DISCOVERY_TIMEOUT_MS,
|
||||
policy: ssrfPolicyFromHttpBaseUrlAllowedHostname(KILOCODE_BASE_URL),
|
||||
auditContext: "kilocode.model_discovery",
|
||||
});
|
||||
try {
|
||||
|
||||
@@ -1,22 +1,23 @@
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const { fetchWithSsrFGuardMock } = vi.hoisted(() => ({
|
||||
fetchWithSsrFGuardMock: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../../../api.js", () => ({
|
||||
fetchWithSsrFGuard: fetchWithSsrFGuardMock,
|
||||
}));
|
||||
|
||||
import { TwilioApiError, twilioApiRequest } from "./api.js";
|
||||
|
||||
const apiMocks = vi.hoisted(() => ({
|
||||
fetchWithSsrFGuard: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/ssrf-runtime", () => ({
|
||||
fetchWithSsrFGuard: apiMocks.fetchWithSsrFGuard,
|
||||
}));
|
||||
|
||||
describe("twilioApiRequest", () => {
|
||||
afterEach(() => {
|
||||
apiMocks.fetchWithSsrFGuard.mockReset();
|
||||
fetchWithSsrFGuardMock.mockReset();
|
||||
});
|
||||
|
||||
it("posts form bodies with basic auth and parses json", async () => {
|
||||
const release = vi.fn(async () => {});
|
||||
apiMocks.fetchWithSsrFGuard.mockResolvedValue({
|
||||
fetchWithSsrFGuardMock.mockResolvedValue({
|
||||
response: new Response(JSON.stringify({ sid: "CA123" }), { status: 200 }),
|
||||
release,
|
||||
});
|
||||
@@ -34,10 +35,12 @@ describe("twilioApiRequest", () => {
|
||||
}),
|
||||
).resolves.toEqual({ sid: "CA123" });
|
||||
|
||||
const [{ url, init, auditContext, policy }] = apiMocks.fetchWithSsrFGuard.mock.calls[0] ?? [];
|
||||
const [{ url, init, auditContext, policy, timeoutMs }] =
|
||||
fetchWithSsrFGuardMock.mock.calls[0] ?? [];
|
||||
expect(url).toBe("https://api.twilio.com/Calls.json");
|
||||
expect(auditContext).toBe("voice-call.twilio.api");
|
||||
expect(policy).toEqual({ allowedHostnames: ["api.twilio.com"] });
|
||||
expect(timeoutMs).toBe(30_000);
|
||||
expect(init).toEqual(
|
||||
expect.objectContaining({
|
||||
method: "POST",
|
||||
@@ -63,7 +66,7 @@ describe("twilioApiRequest", () => {
|
||||
new Response("missing", { status: 404 }),
|
||||
];
|
||||
const release = vi.fn(async () => {});
|
||||
apiMocks.fetchWithSsrFGuard.mockImplementation(async () => ({
|
||||
fetchWithSsrFGuardMock.mockImplementation(async () => ({
|
||||
response: responses.shift()!,
|
||||
release,
|
||||
}));
|
||||
@@ -93,7 +96,7 @@ describe("twilioApiRequest", () => {
|
||||
|
||||
it("throws twilio api errors for non-ok responses", async () => {
|
||||
const release = vi.fn(async () => {});
|
||||
apiMocks.fetchWithSsrFGuard.mockResolvedValue({
|
||||
fetchWithSsrFGuardMock.mockResolvedValue({
|
||||
response: new Response("bad request", { status: 400 }),
|
||||
release,
|
||||
});
|
||||
@@ -112,7 +115,7 @@ describe("twilioApiRequest", () => {
|
||||
|
||||
it("exposes structured Twilio error codes from json error bodies", async () => {
|
||||
const release = vi.fn(async () => {});
|
||||
apiMocks.fetchWithSsrFGuard.mockResolvedValue({
|
||||
fetchWithSsrFGuardMock.mockResolvedValue({
|
||||
response: new Response(
|
||||
JSON.stringify({
|
||||
code: 21220,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
|
||||
import { fetchWithSsrFGuard } from "../../../api.js";
|
||||
|
||||
type ParsedTwilioApiError = {
|
||||
code?: number;
|
||||
@@ -61,8 +61,9 @@ export async function twilioApiRequest<T = unknown>(params: {
|
||||
return acc;
|
||||
}, new URLSearchParams());
|
||||
|
||||
const requestUrl = `${params.baseUrl}${params.endpoint}`;
|
||||
const { response, release } = await fetchWithSsrFGuard({
|
||||
url: `${params.baseUrl}${params.endpoint}`,
|
||||
url: requestUrl,
|
||||
init: {
|
||||
method: "POST",
|
||||
headers: {
|
||||
|
||||
Reference in New Issue
Block a user