fix(secrets): enforce file provider read timeouts
This commit is contained in:
committed by
Peter Steinberger
parent
67e9554645
commit
86622ebea9
@@ -1,7 +1,7 @@
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { resolveSecretRefString, resolveSecretRefValue } from "./resolve.js";
|
||||
|
||||
@@ -15,6 +15,7 @@ describe("secret ref resolver", () => {
|
||||
const cleanupRoots: string[] = [];
|
||||
|
||||
afterEach(async () => {
|
||||
vi.restoreAllMocks();
|
||||
while (cleanupRoots.length > 0) {
|
||||
const root = cleanupRoots.pop();
|
||||
if (!root) {
|
||||
@@ -280,6 +281,56 @@ describe("secret ref resolver", () => {
|
||||
expect(value).toBe("raw-token-value");
|
||||
});
|
||||
|
||||
it("times out file provider reads when timeoutMs elapses", async () => {
|
||||
if (process.platform === "win32") {
|
||||
return;
|
||||
}
|
||||
const root = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-secrets-resolve-timeout-"));
|
||||
cleanupRoots.push(root);
|
||||
const filePath = path.join(root, "secrets.json");
|
||||
await writeSecureFile(
|
||||
filePath,
|
||||
JSON.stringify({
|
||||
providers: {
|
||||
openai: {
|
||||
apiKey: "sk-file-value",
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
const originalReadFile = fs.readFile.bind(fs);
|
||||
vi.spyOn(fs, "readFile").mockImplementation(((
|
||||
targetPath: Parameters<typeof fs.readFile>[0],
|
||||
options?: Parameters<typeof fs.readFile>[1],
|
||||
) => {
|
||||
if (typeof targetPath === "string" && targetPath === filePath) {
|
||||
return new Promise<Buffer>(() => {});
|
||||
}
|
||||
return originalReadFile(targetPath, options);
|
||||
}) as typeof fs.readFile);
|
||||
|
||||
await expect(
|
||||
resolveSecretRefString(
|
||||
{ source: "file", provider: "filemain", id: "/providers/openai/apiKey" },
|
||||
{
|
||||
config: {
|
||||
secrets: {
|
||||
providers: {
|
||||
filemain: {
|
||||
source: "file",
|
||||
path: filePath,
|
||||
mode: "jsonPointer",
|
||||
timeoutMs: 5,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
).rejects.toThrow('File provider "filemain" timed out');
|
||||
});
|
||||
|
||||
it("rejects misconfigured provider source mismatches", async () => {
|
||||
await expect(
|
||||
resolveSecretRefValue(
|
||||
|
||||
@@ -188,11 +188,20 @@ async function readFileProviderPayload(params: {
|
||||
DEFAULT_FILE_TIMEOUT_MS,
|
||||
);
|
||||
const maxBytes = normalizePositiveInt(params.providerConfig.maxBytes, DEFAULT_FILE_MAX_BYTES);
|
||||
const timeoutHandle = setTimeout(() => {
|
||||
// noop marker to keep timeout behavior explicit and deterministic
|
||||
}, timeoutMs);
|
||||
const abortController = new AbortController();
|
||||
const timeoutErrorMessage = `File provider "${params.providerName}" timed out after ${timeoutMs}ms.`;
|
||||
let timeoutHandle: NodeJS.Timeout | null = null;
|
||||
const timeoutPromise = new Promise<never>((_resolve, reject) => {
|
||||
timeoutHandle = setTimeout(() => {
|
||||
abortController.abort();
|
||||
reject(new Error(timeoutErrorMessage));
|
||||
}, timeoutMs);
|
||||
});
|
||||
try {
|
||||
const payload = await fs.readFile(filePath);
|
||||
const payload = await Promise.race([
|
||||
fs.readFile(filePath, { signal: abortController.signal }),
|
||||
timeoutPromise,
|
||||
]);
|
||||
if (payload.byteLength > maxBytes) {
|
||||
throw new Error(`File provider "${params.providerName}" exceeded maxBytes (${maxBytes}).`);
|
||||
}
|
||||
@@ -205,8 +214,15 @@ async function readFileProviderPayload(params: {
|
||||
throw new Error(`File provider "${params.providerName}" payload is not a JSON object.`);
|
||||
}
|
||||
return parsed;
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.name === "AbortError") {
|
||||
throw new Error(timeoutErrorMessage, { cause: error });
|
||||
}
|
||||
throw error;
|
||||
} finally {
|
||||
clearTimeout(timeoutHandle);
|
||||
if (timeoutHandle) {
|
||||
clearTimeout(timeoutHandle);
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user