mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-06 05:51:15 +08:00
fix(agents): guard session tool call metadata errors
This commit is contained in:
@@ -508,10 +508,12 @@ export class AgentSession {
|
||||
input: args as Record<string, unknown>,
|
||||
});
|
||||
} catch (err) {
|
||||
if (err instanceof Error) {
|
||||
throw err;
|
||||
}
|
||||
throw new Error(`Extension failed, blocking execution: ${String(err)}`, { cause: err });
|
||||
throw new Error(
|
||||
`Extension failed, blocking execution: ${describeSessionExtensionError(err)}`,
|
||||
{
|
||||
cause: err,
|
||||
},
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Agent session SDK tests cover default tool wiring, prompt preservation, and
|
||||
// session write-lock behavior.
|
||||
import { Type } from "typebox";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import type { Model } from "../../llm/types.js";
|
||||
import { AuthStorage } from "./auth-storage.js";
|
||||
import { createExtensionRuntime } from "./extensions/loader.js";
|
||||
@@ -395,6 +395,72 @@ describe("createAgentSession tool defaults", () => {
|
||||
expect(events).toEqual(["lock:start", "hook", "lock:end"]);
|
||||
});
|
||||
|
||||
it("blocks hostile tool call metadata without trusting thrown stringification", async () => {
|
||||
const hostileError = new Error("metadata denied");
|
||||
Object.defineProperty(hostileError, "message", {
|
||||
get() {
|
||||
throw new Error("message denied");
|
||||
},
|
||||
});
|
||||
const handler = vi.fn(async () => undefined);
|
||||
const handlers = new Map<string, Array<(...args: unknown[]) => Promise<unknown>>>([
|
||||
["tool_call", [handler]],
|
||||
]);
|
||||
const hostileToolCall = Object.defineProperties(
|
||||
{
|
||||
type: "toolCall",
|
||||
id: "call_1",
|
||||
arguments: {},
|
||||
},
|
||||
{
|
||||
name: {
|
||||
enumerable: true,
|
||||
get() {
|
||||
throw hostileError;
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const { session } = await createAgentSession({
|
||||
model: testModel,
|
||||
resourceLoader: createResourceLoaderWithHandlers(handlers),
|
||||
sessionManager: SessionManager.inMemory(),
|
||||
settingsManager: SettingsManager.inMemory(),
|
||||
modelRegistry: ModelRegistry.inMemory(AuthStorage.inMemory()),
|
||||
});
|
||||
|
||||
await expect(
|
||||
session.agent.beforeToolCall?.({
|
||||
assistantMessage: {
|
||||
role: "assistant",
|
||||
content: [],
|
||||
api: testModel.api,
|
||||
provider: testModel.provider,
|
||||
model: testModel.id,
|
||||
usage: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
totalTokens: 0,
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
||||
},
|
||||
stopReason: "toolUse",
|
||||
timestamp: Date.now(),
|
||||
},
|
||||
toolCall: hostileToolCall as never,
|
||||
args: {},
|
||||
context: {
|
||||
systemPrompt: "",
|
||||
messages: [],
|
||||
tools: [],
|
||||
},
|
||||
}),
|
||||
).rejects.toThrow("Extension failed, blocking execution: Unknown session extension error");
|
||||
expect(handler).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("fences tool execution when no extension hook is registered", async () => {
|
||||
// Write-capable tools still enter the lock even without hooks; the lock is
|
||||
// about shared session state, not just extension execution.
|
||||
|
||||
Reference in New Issue
Block a user