Skip to content

Commit a379ac0

Browse files
committed
fix: guard plugin HTTP calls in CI
1 parent d0dac32 commit a379ac0

3 files changed

Lines changed: 81 additions & 54 deletions

File tree

extensions/kilocode/provider-models.ts

Lines changed: 44 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { ModelDefinitionConfig } from "openclaw/plugin-sdk/provider-model-shared";
22
import { createSubsystemLogger } from "openclaw/plugin-sdk/runtime-env";
3+
import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
34
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
45

56
const log = createSubsystemLogger("kilocode-models");
@@ -135,49 +136,56 @@ export async function discoverKilocodeModels(): Promise<ModelDefinitionConfig[]>
135136
}
136137

137138
try {
138-
const response = await fetch(KILOCODE_MODELS_URL, {
139-
headers: { Accept: "application/json" },
140-
signal: AbortSignal.timeout(DISCOVERY_TIMEOUT_MS),
139+
const { response, release } = await fetchWithSsrFGuard({
140+
url: KILOCODE_MODELS_URL,
141+
init: {
142+
headers: { Accept: "application/json" },
143+
},
144+
timeoutMs: DISCOVERY_TIMEOUT_MS,
145+
auditContext: "kilocode.model_discovery",
141146
});
142-
143-
if (!response.ok) {
144-
log.warn(`Failed to discover models: HTTP ${response.status}, using static catalog`);
145-
return buildStaticCatalog();
146-
}
147-
148-
const data = (await response.json()) as GatewayModelsResponse;
149-
if (!Array.isArray(data.data) || data.data.length === 0) {
150-
log.warn("No models found from gateway API, using static catalog");
151-
return buildStaticCatalog();
152-
}
153-
154-
const models: ModelDefinitionConfig[] = [];
155-
const discoveredIds = new Set<string>();
156-
157-
for (const entry of data.data) {
158-
if (!entry || typeof entry !== "object") {
159-
continue;
147+
try {
148+
if (!response.ok) {
149+
log.warn(`Failed to discover models: HTTP ${response.status}, using static catalog`);
150+
return buildStaticCatalog();
160151
}
161-
const id = typeof entry.id === "string" ? entry.id.trim() : "";
162-
if (!id || discoveredIds.has(id)) {
163-
continue;
152+
153+
const data = (await response.json()) as GatewayModelsResponse;
154+
if (!Array.isArray(data.data) || data.data.length === 0) {
155+
log.warn("No models found from gateway API, using static catalog");
156+
return buildStaticCatalog();
164157
}
165-
try {
166-
models.push(toModelDefinition(entry));
167-
discoveredIds.add(id);
168-
} catch (e) {
169-
log.warn(`Skipping malformed model entry "${id}": ${String(e)}`);
158+
159+
const models: ModelDefinitionConfig[] = [];
160+
const discoveredIds = new Set<string>();
161+
162+
for (const entry of data.data) {
163+
if (!entry || typeof entry !== "object") {
164+
continue;
165+
}
166+
const id = typeof entry.id === "string" ? entry.id.trim() : "";
167+
if (!id || discoveredIds.has(id)) {
168+
continue;
169+
}
170+
try {
171+
models.push(toModelDefinition(entry));
172+
discoveredIds.add(id);
173+
} catch (e) {
174+
log.warn(`Skipping malformed model entry "${id}": ${String(e)}`);
175+
}
170176
}
171-
}
172177

173-
const staticModels = buildStaticCatalog();
174-
for (const staticModel of staticModels) {
175-
if (!discoveredIds.has(staticModel.id)) {
176-
models.unshift(staticModel);
178+
const staticModels = buildStaticCatalog();
179+
for (const staticModel of staticModels) {
180+
if (!discoveredIds.has(staticModel.id)) {
181+
models.unshift(staticModel);
182+
}
177183
}
178-
}
179184

180-
return models.length > 0 ? models : buildStaticCatalog();
185+
return models.length > 0 ? models : buildStaticCatalog();
186+
} finally {
187+
await release();
188+
}
181189
} catch (error) {
182190
log.warn(`Discovery failed: ${String(error)}, using static catalog`);
183191
return buildStaticCatalog();

extensions/voice-call/src/providers/twilio/api.ts

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1+
import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
2+
13
type ParsedTwilioApiError = {
24
code?: number;
35
message?: string;
46
};
57

8+
const TWILIO_API_TIMEOUT_MS = 30_000;
9+
610
function parseTwilioApiError(text: string): ParsedTwilioApiError {
711
try {
812
const parsed: unknown = JSON.parse(text);
@@ -57,23 +61,31 @@ export async function twilioApiRequest<T = unknown>(params: {
5761
return acc;
5862
}, new URLSearchParams());
5963

60-
const response = await fetch(`${params.baseUrl}${params.endpoint}`, {
61-
method: "POST",
62-
headers: {
63-
Authorization: `Basic ${Buffer.from(`${params.accountSid}:${params.authToken}`).toString("base64")}`,
64-
"Content-Type": "application/x-www-form-urlencoded",
64+
const { response, release } = await fetchWithSsrFGuard({
65+
url: `${params.baseUrl}${params.endpoint}`,
66+
init: {
67+
method: "POST",
68+
headers: {
69+
Authorization: `Basic ${Buffer.from(`${params.accountSid}:${params.authToken}`).toString("base64")}`,
70+
"Content-Type": "application/x-www-form-urlencoded",
71+
},
72+
body: bodyParams,
6573
},
66-
body: bodyParams,
74+
timeoutMs: TWILIO_API_TIMEOUT_MS,
75+
auditContext: "voice-call.twilio_api",
6776
});
68-
69-
if (!response.ok) {
70-
if (params.allowNotFound && response.status === 404) {
71-
return undefined as T;
77+
try {
78+
if (!response.ok) {
79+
if (params.allowNotFound && response.status === 404) {
80+
return undefined as T;
81+
}
82+
const errorText = await response.text();
83+
throw new TwilioApiError(response.status, errorText);
7284
}
73-
const errorText = await response.text();
74-
throw new TwilioApiError(response.status, errorText);
75-
}
7685

77-
const text = await response.text();
78-
return text ? (JSON.parse(text) as T) : (undefined as T);
86+
const text = await response.text();
87+
return text ? (JSON.parse(text) as T) : (undefined as T);
88+
} finally {
89+
await release();
90+
}
7991
}

src/cli/gateway-cli/run.option-collisions.test.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import path from "node:path";
22
import { Command } from "commander";
33
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
4+
import { SUPERVISOR_HINT_ENV_VARS } from "../../infra/supervisor-markers.js";
5+
import { withEnvAsync } from "../../test-utils/env.js";
46
import { withTempSecretFiles } from "../../test-utils/secret-file-fixture.js";
57
import { createCliRuntimeCapture } from "../test-runtime-capture.js";
68

@@ -42,6 +44,9 @@ const writeDiagnosticStabilityBundleForFailureSync = vi.fn((_reason: string, _er
4244
const controlUiState = vi.hoisted(() => ({
4345
root: "/tmp/openclaw-control-ui" as string | null,
4446
}));
47+
const withoutSupervisorEnv = Object.fromEntries(
48+
SUPERVISOR_HINT_ENV_VARS.map((key) => [key, undefined]),
49+
) as Record<string, string | undefined>;
4550

4651
const { runtimeErrors, defaultRuntime, resetRuntimeCapture } = createCliRuntimeCapture();
4752

@@ -317,9 +322,11 @@ describe("gateway run option collisions", () => {
317322
});
318323
startGatewayServer.mockRejectedValueOnce(err);
319324

320-
await expect(runGatewayCli(["gateway", "run", "--allow-unconfigured"])).rejects.toThrow(
321-
"__exit__:0",
322-
);
325+
await withEnvAsync(withoutSupervisorEnv, async () => {
326+
await expect(runGatewayCli(["gateway", "run", "--allow-unconfigured"])).rejects.toThrow(
327+
"__exit__:0",
328+
);
329+
});
323330

324331
expect(writeDiagnosticStabilityBundleForFailureSync).not.toHaveBeenCalled();
325332
});

0 commit comments

Comments
 (0)