Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/JAVA/Openclaw/src/gateway/   (KI Agentensystem Version 22©)  Datei vom 26.3.2026 mit Größe 25 kB image not shown  

Quelle  tools-invoke-http.test.ts

  Sprache: JAVA
 

Spracherkennung für: .ts vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]

import { createServer, type IncomingMessage, type ServerResponse } from "node:http";
import type { AddressInfo } from "node:net";
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import type { runBeforeToolCallHook as runBeforeToolCallHookType } from "../agents/pi-tools.before-tool-call.js";

type RunBeforeToolCallHook = typeof runBeforeToolCallHookType;
type RunBeforeToolCallHookArgs = Parameters<RunBeforeToolCallHook>[0];
type RunBeforeToolCallHookResult = Awaited<ReturnType<RunBeforeToolCallHook>>;

const hookMocks = vi.hoisted(() => ({
  resolveToolLoopDetectionConfig: vi.fn(() => ({ warnAt: 3 })),
  runBeforeToolCallHook: vi.fn(
    async (args: RunBeforeToolCallHookArgs): Promise<RunBeforeToolCallHookResult> => ({
      blocked: false,
      params: args.params,
    }),
  ),
}));

let cfg: Record<string, unknown> = {};
let lastCreateOpenClawToolsContext: Record<string, unknown> | undefined;

// Perf: keep this suite pure unit. Mock heavyweight config/session modules.
vi.mock("../config/config.js", () => ({
  loadConfig: () => cfg,
}));

vi.mock("../config/sessions.js", () => ({
  resolveMainSessionKey: (params?: {
    session?: { scope?: string; mainKey?: string };
    agents?: { list?: Array<{ id?: string; default?: boolean }> };
  }) => {
    if (params?.session?.scope === "global") {
      return "global";
    }
    const agents = params?.agents?.list ?? [];
    const rawDefault = agents.find((agent) => agent?.default)?.id ?? agents[0]?.id ?? "main";
    const agentId = rawDefault.trim().toLowerCase() || "main";
    const mainKeyRaw = (params?.session?.mainKey ?? "main").trim().toLowerCase();
    const mainKey = mainKeyRaw || "main";
    return `agent:${agentId}:${mainKey}`;
  },
}));

vi.mock("./auth.js", () => ({
  authorizeHttpGatewayConnect: vi.fn(async () => ({ ok: true })),
}));

vi.mock("../logger.js", () => ({
  logWarn: () => {},
}));

vi.mock("../plugins/config-state.js", async (importOriginal) => {
  const actual = await importOriginal<typeof import("../plugins/config-state.js")>();
  return {
    ...actual,
    isTestDefaultMemorySlotDisabled: () => false,
  };
});

vi.mock("../plugins/tools.js", () => ({
  getPluginToolMeta: () => undefined,
}));

// Perf: the real tool factory instantiates many tools per request; for these HTTP
// routing/policy tests we only need a small set of tool names.
vi.mock("../agents/openclaw-tools.js", () => {
  const toolInputError = (message: string) => {
    const err = new Error(message);
    err.name = "ToolInputError";
    return err;
  };
  const toolAuthorizationError = (message: string) => {
    const err = new Error(message) as Error & { status?: number };
    err.name = "ToolAuthorizationError";
    err.status = 403;
    return err;
  };

  const tools = [
    {
      name: "session_status",
      parameters: { type: "object", properties: {} },
      execute: async () => ({ ok: true }),
    },
    {
      name: "agents_list",
      parameters: { type: "object", properties: { action: { type: "string" } } },
      execute: async () => ({ ok: true, result: [] }),
    },
    {
      name: "sessions_spawn",
      parameters: { type: "object", properties: {} },
      execute: async () => ({
        ok: true,
        route: {
          agentTo: lastCreateOpenClawToolsContext?.agentTo,
          agentThreadId: lastCreateOpenClawToolsContext?.agentThreadId,
        },
      }),
    },
    {
      name: "sessions_send",
      parameters: { type: "object", properties: {} },
      execute: async () => ({ ok: true }),
    },
    {
      name: "gateway",
      parameters: { type: "object", properties: {} },
      execute: async () => {
        throw toolInputError("invalid args");
      },
    },
    {
      name: "exec",
      parameters: { type: "object", properties: {} },
      execute: async () => ({ ok: true, result: "exec" }),
    },
    {
      name: "apply_patch",
      parameters: { type: "object", properties: {} },
      execute: async () => ({ ok: true, result: "apply_patch" }),
    },
    {
      name: "nodes",
      ownerOnly: true,
      parameters: { type: "object", properties: {} },
      execute: async () => ({ ok: true, result: "nodes" }),
    },
    {
      name: "browser",
      parameters: { type: "object", properties: {} },
      execute: async () => ({ ok: true, result: "browser" }),
    },
    {
      name: "owner_only_test",
      ownerOnly: true,
      parameters: { type: "object", properties: {} },
      execute: async () => ({ ok: true, result: "owner-only" }),
    },
    {
      name: "tools_invoke_test",
      parameters: {
        type: "object",
        properties: {
          mode: { type: "string" },
        },
        required: ["mode"],
        additionalProperties: false,
      },
      execute: async (_toolCallId: string, args: unknown) => {
        const mode = (args as { mode?: unknown })?.mode;
        if (mode === "input") {
          throw toolInputError("mode invalid");
        }
        if (mode === "auth") {
          throw toolAuthorizationError("mode forbidden");
        }
        if (mode === "crash") {
          throw new Error("boom");
        }
        return { ok: true };
      },
    },
    {
      name: "diffs_compat_test",
      parameters: {
        type: "object",
        properties: {
          mode: { type: "string" },
          fileFormat: { type: "string" },
        },
        additionalProperties: false,
      },
      execute: async (_toolCallId: string, args: unknown) => {
        const input = (args ?? {}) as Record<string, unknown>;
        return {
          ok: true,
          observedFormat: input.format,
          observedFileFormat: input.fileFormat,
        };
      },
    },
  ];

  return {
    createOpenClawTools: (ctx: Record<string, unknown>) => {
      lastCreateOpenClawToolsContext = ctx;
      return ctx.disablePluginTools ? tools.filter((tool) => tool.name !== "browser") : tools;
    },
  };
});

vi.mock("../agents/pi-tools.js", () => ({
  resolveToolLoopDetectionConfig: hookMocks.resolveToolLoopDetectionConfig,
}));

vi.mock("../agents/pi-tools.before-tool-call.js", () => ({
  runBeforeToolCallHook: hookMocks.runBeforeToolCallHook,
}));

const { authorizeHttpGatewayConnect } = await import("./auth.js");
const { handleToolsInvokeHttpRequest } = await import("./tools-invoke-http.js");

let pluginHttpHandlers: Array<(req: IncomingMessage, res: ServerResponse) => Promise<boolean>> = [];

let sharedPort = 0;
let sharedServer: ReturnType<typeof createServer> | undefined;

beforeAll(async () => {
  sharedServer = createServer((req, res) => {
    void (async () => {
      const handled = await handleToolsInvokeHttpRequest(req, res, {
        auth: { mode: "none", allowTailscale: false },
      });
      if (handled) {
        return;
      }
      for (const handler of pluginHttpHandlers) {
        if (await handler(req, res)) {
          return;
        }
      }
      res.statusCode = 404;
      res.end("not found");
    })().catch((err) => {
      res.statusCode = 500;
      res.end(String(err));
    });
  });

  await new Promise<void>((resolve, reject) => {
    sharedServer?.once("error", reject);
    sharedServer?.listen(0, "127.0.0.1", () => {
      const address = sharedServer?.address() as AddressInfo | null;
      sharedPort = address?.port ?? 0;
      resolve();
    });
  });
});

afterAll(async () => {
  const server = sharedServer;
  if (!server) {
    return;
  }
  await new Promise<void>((resolve) => server.close(() => resolve()));
  sharedServer = undefined;
});

beforeEach(() => {
  delete process.env.OPENCLAW_GATEWAY_TOKEN;
  delete process.env.OPENCLAW_GATEWAY_PASSWORD;
  pluginHttpHandlers = [];
  cfg = {};
  lastCreateOpenClawToolsContext = undefined;
  hookMocks.resolveToolLoopDetectionConfig.mockClear();
  hookMocks.resolveToolLoopDetectionConfig.mockImplementation(() => ({ warnAt: 3 }));
  hookMocks.runBeforeToolCallHook.mockClear();
  hookMocks.runBeforeToolCallHook.mockImplementation(
    async (args: RunBeforeToolCallHookArgs): Promise<RunBeforeToolCallHookResult> => ({
      blocked: false,
      params: args.params,
    }),
  );
  vi.mocked(authorizeHttpGatewayConnect).mockResolvedValue({ ok: true });
});

const gatewayAuthHeaders = () => ({ "x-openclaw-scopes": "operator.write" });
const gatewayAdminHeaders = () => ({ "x-openclaw-scopes": "operator.admin" });

const allowAgentsListForMain = () => {
  cfg = {
    ...cfg,
    agents: {
      list: [
        {
          id: "main",
          default: true,
          tools: {
            allow: ["agents_list"],
          },
        },
      ],
    },
  };
};

const postToolsInvoke = async (params: {
  port: number;
  headers?: Record<string, string>;
  body: Record<string, unknown>;
}) =>
  await fetch(`http://127.0.0.1:${params.port}/tools/invoke`, {
    method: "POST",
    headers: { "content-type": "application/json", ...params.headers },
    body: JSON.stringify(params.body),
  });

const withOptionalSessionKey = (body: Record<string, unknown>, sessionKey?: string) => ({
  ...body,
  ...(sessionKey ? { sessionKey } : {}),
});

const invokeAgentsList = async (params: {
  port: number;
  headers?: Record<string, string>;
  sessionKey?: string;
}) => {
  const body = withOptionalSessionKey(
    { tool: "agents_list", action: "json", args: {} },
    params.sessionKey,
  );
  return await postToolsInvoke({ port: params.port, headers: params.headers, body });
};

const invokeTool = async (params: {
  port: number;
  tool: string;
  args?: Record<string, unknown>;
  action?: string;
  headers?: Record<string, string>;
  sessionKey?: string;
}) => {
  const body: Record<string, unknown> = withOptionalSessionKey(
    {
      tool: params.tool,
      args: params.args ?? {},
    },
    params.sessionKey,
  );
  if (params.action) {
    body.action = params.action;
  }
  return await postToolsInvoke({ port: params.port, headers: params.headers, body });
};

const invokeAgentsListAuthed = async (params: { sessionKey?: string } = {}) =>
  invokeAgentsList({
    port: sharedPort,
    headers: gatewayAuthHeaders(),
    sessionKey: params.sessionKey,
  });

const invokeAgentsListBearer = async () =>
  await postToolsInvoke({
    port: sharedPort,
    headers: {
      authorization: "Bearer secret",
      "content-type": "application/json",
    },
    body: {
      tool: "agents_list",
      action: "json",
      args: {},
      sessionKey: "main",
    },
  });

const invokeToolAuthed = async (params: {
  tool: string;
  args?: Record<string, unknown>;
  action?: string;
  sessionKey?: string;
}) =>
  invokeTool({
    port: sharedPort,
    headers: gatewayAuthHeaders(),
    ...params,
  });

const expectOkInvokeResponse = async (res: Response) => {
  expect(res.status).toBe(200);
  const body = await res.json();
  expect(body.ok).toBe(true);
  return body as { ok: boolean; result?: Record<string, unknown> };
};

const setMainAllowedTools = (params: {
  allow: string[];
  gatewayAllow?: string[];
  gatewayDeny?: string[];
}) => {
  cfg = {
    ...cfg,
    agents: {
      list: [{ id: "main", default: true, tools: { allow: params.allow } }],
    },
    ...(params.gatewayAllow || params.gatewayDeny
      ? {
          gateway: {
            tools: {
              ...(params.gatewayAllow ? { allow: params.gatewayAllow } : {}),
              ...(params.gatewayDeny ? { deny: params.gatewayDeny } : {}),
            },
          },
        }
      : {}),
  };
};

describe("POST /tools/invoke", () => {
  it("invokes a tool and returns {ok:true,result}", async () => {
    allowAgentsListForMain();
    const res = await invokeAgentsListAuthed({ sessionKey: "main" });

    expect(res.status).toBe(200);
    const body = await res.json();
    expect(body.ok).toBe(true);
    expect(body).toHaveProperty("result");
    expect(lastCreateOpenClawToolsContext?.allowMediaInvokeCommands).toBe(true);
    expect(lastCreateOpenClawToolsContext?.disablePluginTools).toBe(true);
    expect(hookMocks.runBeforeToolCallHook).toHaveBeenCalledWith(
      expect.objectContaining({
        toolName: "agents_list",
        ctx: expect.objectContaining({
          agentId: "main",
          sessionKey: "agent:main:main",
          loopDetection: { warnAt: 3 },
        }),
      }),
    );
  });

  it("opts direct gateway tool invocation into gateway subagent binding", async () => {
    allowAgentsListForMain();
    const res = await invokeAgentsListAuthed({ sessionKey: "main" });

    expect(res.status).toBe(200);
    expect(lastCreateOpenClawToolsContext?.allowGatewaySubagentBinding).toBe(true);
  });

  it("keeps plugin tools enabled for non-core tool invokes", async () => {
    setMainAllowedTools({ allow: ["tools_invoke_test"] });

    const res = await invokeToolAuthed({
      tool: "tools_invoke_test",
      args: { mode: "ok" },
      sessionKey: "main",
    });

    expect(res.status).toBe(200);
    expect(lastCreateOpenClawToolsContext?.disablePluginTools).toBe(false);
  });

  it("blocks tool execution when before_tool_call rejects the invoke", async () => {
    setMainAllowedTools({ allow: ["tools_invoke_test"] });
    hookMocks.runBeforeToolCallHook.mockResolvedValueOnce({
      blocked: true,
      reason: "blocked by test hook",
    });

    const res = await invokeToolAuthed({
      tool: "tools_invoke_test",
      args: { mode: "ok" },
      sessionKey: "main",
    });

    expect(res.status).toBe(403);
    await expect(res.json()).resolves.toMatchObject({
      ok: false,
      error: {
        type: "tool_call_blocked",
        message: "blocked by test hook",
      },
    });
  });

  it("accepts shared-secret bearer auth on the HTTP tools surface", async () => {
    allowAgentsListForMain();
    vi.mocked(authorizeHttpGatewayConnect).mockResolvedValueOnce({
      ok: true,
      method: "token",
    });

    const res = await invokeAgentsListBearer();

    const body = await expectOkInvokeResponse(res);
    expect(body.result).toEqual({ ok: true, result: [] });
  });

  it("threads senderIsOwner into tool creation before owner-only filtering", async () => {
    setMainAllowedTools({ allow: ["session_status", "owner_only_test"] });

    const writeRes = await invokeTool({
      port: sharedPort,
      headers: gatewayAuthHeaders(),
      tool: "session_status",
      sessionKey: "main",
    });
    expect(writeRes.status).toBe(200);
    expect(lastCreateOpenClawToolsContext?.senderIsOwner).toBe(false);

    const adminRes = await invokeTool({
      port: sharedPort,
      headers: gatewayAdminHeaders(),
      tool: "session_status",
      sessionKey: "main",
    });
    expect(adminRes.status).toBe(200);
    expect(lastCreateOpenClawToolsContext?.senderIsOwner).toBe(true);
  });

  it("uses before_tool_call adjusted params for HTTP tool execution", async () => {
    setMainAllowedTools({ allow: ["tools_invoke_test"] });
    hookMocks.runBeforeToolCallHook.mockImplementationOnce(async () => ({
      blocked: false,
      params: { mode: "rewritten" },
    }));

    const res = await invokeToolAuthed({
      tool: "tools_invoke_test",
      args: { mode: "input" },
      sessionKey: "main",
    });

    const body = await expectOkInvokeResponse(res);
    expect(body.result).toMatchObject({ ok: true });
  });

  it("supports tools.alsoAllow in profile and implicit modes", async () => {
    cfg = {
      ...cfg,
      agents: { list: [{ id: "main", default: true }] },
      tools: { profile: "minimal", alsoAllow: ["agents_list"] },
    };

    const resProfile = await invokeAgentsListAuthed({ sessionKey: "main" });

    expect(resProfile.status).toBe(200);
    const profileBody = await resProfile.json();
    expect(profileBody.ok).toBe(true);

    cfg = {
      ...cfg,
      tools: { alsoAllow: ["agents_list"] },
    };

    const resImplicit = await invokeAgentsListAuthed({ sessionKey: "main" });
    expect(resImplicit.status).toBe(200);
    const implicitBody = await resImplicit.json();
    expect(implicitBody.ok).toBe(true);
  });

  it("routes tools invoke before plugin HTTP handlers", async () => {
    const pluginHandler = vi.fn(async (_req: IncomingMessage, res: ServerResponse) => {
      res.statusCode = 418;
      res.end("plugin");
      return true;
    });
    allowAgentsListForMain();
    pluginHttpHandlers = [async (req, res) => pluginHandler(req, res)];

    const res = await invokeAgentsListAuthed({ sessionKey: "main" });

    expect(res.status).toBe(200);
    expect(pluginHandler).not.toHaveBeenCalled();
  });

  it("returns 404 when denylisted or blocked by tools.profile", async () => {
    cfg = {
      ...cfg,
      agents: {
        list: [
          {
            id: "main",
            default: true,
            tools: {
              deny: ["agents_list"],
            },
          },
        ],
      },
    };
    const denyRes = await invokeAgentsListAuthed({ sessionKey: "main" });
    expect(denyRes.status).toBe(404);

    allowAgentsListForMain();
    cfg = {
      ...cfg,
      tools: { profile: "minimal" },
    };

    const profileRes = await invokeAgentsListAuthed({ sessionKey: "main" });
    expect(profileRes.status).toBe(404);
  });

  it("denies sessions_spawn via HTTP even when agent policy allows", async () => {
    cfg = {
      ...cfg,
      agents: {
        list: [
          {
            id: "main",
            default: true,
            tools: { allow: ["sessions_spawn"] },
          },
        ],
      },
    };

    const res = await invokeToolAuthed({
      tool: "sessions_spawn",
      args: { task: "test" },
      sessionKey: "main",
    });

    expect(res.status).toBe(404);
    const body = await res.json();
    expect(body.ok).toBe(false);
    expect(body.error.type).toBe("not_found");
  });

  it("propagates message target/thread headers into tools context for sessions_spawn", async () => {
    cfg = {
      ...cfg,
      agents: {
        list: [{ id: "main", default: true, tools: { allow: ["sessions_spawn"] } }],
      },
      gateway: { tools: { allow: ["sessions_spawn"] } },
    };

    const res = await invokeTool({
      port: sharedPort,
      headers: {
        ...gatewayAuthHeaders(),
        "x-openclaw-message-to": "channel:24514",
        "x-openclaw-thread-id": "thread-24514",
      },
      tool: "sessions_spawn",
      sessionKey: "main",
    });

    const body = await expectOkInvokeResponse(res);
    expect(body.result?.route).toEqual({
      agentTo: "channel:24514",
      agentThreadId: "thread-24514",
    });
  });

  it("denies sessions_send via HTTP gateway", async () => {
    setMainAllowedTools({ allow: ["sessions_send"] });

    const res = await invokeToolAuthed({
      tool: "sessions_send",
      sessionKey: "main",
    });

    expect(res.status).toBe(404);
  });

  it("denies gateway tool via HTTP", async () => {
    setMainAllowedTools({ allow: ["gateway"] });

    const res = await invokeToolAuthed({
      tool: "gateway",
      sessionKey: "main",
    });

    expect(res.status).toBe(404);
  });

  it("allows gateway tool via HTTP when explicitly enabled in gateway.tools.allow", async () => {
    setMainAllowedTools({ allow: ["gateway"], gatewayAllow: ["gateway"] });

    const res = await invokeTool({
      port: sharedPort,
      headers: gatewayAdminHeaders(),
      tool: "gateway",
      sessionKey: "main",
    });

    expect(res.status).toBe(400);
    const body = await res.json();
    expect(body.ok).toBe(false);
    expect(body.error?.type).toBe("tool_error");
  });

  it("treats gateway.tools.deny as higher priority than gateway.tools.allow", async () => {
    setMainAllowedTools({
      allow: ["gateway"],
      gatewayAllow: ["gateway"],
      gatewayDeny: ["gateway"],
    });

    const res = await invokeToolAuthed({
      tool: "gateway",
      sessionKey: "main",
    });

    expect(res.status).toBe(404);
  });

  it("uses the configured main session key when sessionKey is missing or main", async () => {
    cfg = {
      ...cfg,
      agents: {
        list: [
          {
            id: "main",
            tools: {
              deny: ["agents_list"],
            },
          },
          {
            id: "ops",
            default: true,
            tools: {
              allow: ["agents_list"],
            },
          },
        ],
      },
      session: { mainKey: "primary" },
    };

    const resDefault = await invokeAgentsListAuthed();
    expect(resDefault.status).toBe(200);

    const resMain = await invokeAgentsListAuthed({ sessionKey: "main" });
    expect(resMain.status).toBe(200);
  });

  it("maps tool input/auth errors to 400/403 and unexpected execution errors to 500", async () => {
    cfg = {
      ...cfg,
      agents: {
        list: [{ id: "main", default: true, tools: { allow: ["tools_invoke_test"] } }],
      },
    };

    const inputRes = await invokeToolAuthed({
      tool: "tools_invoke_test",
      args: { mode: "input" },
      sessionKey: "main",
    });
    expect(inputRes.status).toBe(400);
    const inputBody = await inputRes.json();
    expect(inputBody.ok).toBe(false);
    expect(inputBody.error?.type).toBe("tool_error");
    expect(inputBody.error?.message).toBe("mode invalid");

    const authRes = await invokeToolAuthed({
      tool: "tools_invoke_test",
      args: { mode: "auth" },
      sessionKey: "main",
    });
    expect(authRes.status).toBe(403);
    const authBody = await authRes.json();
    expect(authBody.ok).toBe(false);
    expect(authBody.error?.type).toBe("tool_error");
    expect(authBody.error?.message).toBe("mode forbidden");

    const crashRes = await invokeToolAuthed({
      tool: "tools_invoke_test",
      args: { mode: "crash" },
      sessionKey: "main",
    });
    expect(crashRes.status).toBe(500);
    const crashBody = await crashRes.json();
    expect(crashBody.ok).toBe(false);
    expect(crashBody.error?.type).toBe("tool_error");
    expect(crashBody.error?.message).toBe("tool execution failed");
  });

  it("passes deprecated format alias through invoke payloads even when schema omits it", async () => {
    setMainAllowedTools({ allow: ["diffs_compat_test"] });

    const res = await invokeToolAuthed({
      tool: "diffs_compat_test",
      args: { mode: "file", format: "pdf" },
      sessionKey: "main",
    });

    const body = await expectOkInvokeResponse(res);
    expect(body.result?.observedFormat).toBe("pdf");
    expect(body.result?.observedFileFormat).toBeUndefined();
  });

  it("requires operator.write scope for HTTP tool invocation", async () => {
    allowAgentsListForMain();
    vi.mocked(authorizeHttpGatewayConnect).mockResolvedValueOnce({
      ok: true,
      method: "trusted-proxy",
    });

    const res = await invokeTool({
      port: sharedPort,
      headers: {
        "x-openclaw-scopes": "",
      },
      tool: "agents_list",
      sessionKey: "main",
    });

    expect(res.status).toBe(403);
    await expect(res.json()).resolves.toMatchObject({
      ok: false,
      error: {
        type: "forbidden",
        message: "missing scope: operator.write",
      },
    });
  });

  it("treats shared-secret bearer auth as full operator access on /tools/invoke", async () => {
    allowAgentsListForMain();
    vi.mocked(authorizeHttpGatewayConnect).mockResolvedValueOnce({
      ok: true,
      method: "token",
    });

    const res = await invokeAgentsListBearer();

    const body = await expectOkInvokeResponse(res);
    expect(body.result).toEqual({ ok: true, result: [] });
  });

  it("applies owner-only tool policy on the HTTP path", async () => {
    setMainAllowedTools({ allow: ["owner_only_test"] });

    const deniedRes = await invokeToolAuthed({
      tool: "owner_only_test",
      sessionKey: "main",
    });
    expect(deniedRes.status).toBe(404);

    const allowedRes = await invokeTool({
      port: sharedPort,
      headers: gatewayAdminHeaders(),
      tool: "owner_only_test",
      sessionKey: "main",
    });
    const allowedBody = await expectOkInvokeResponse(allowedRes);
    expect(allowedBody.result).toEqual({ ok: true, result: "owner-only" });
  });

  it("treats shared-secret bearer auth as owner on /tools/invoke", async () => {
    setMainAllowedTools({ allow: ["owner_only_test"] });
    vi.mocked(authorizeHttpGatewayConnect).mockResolvedValueOnce({
      ok: true,
      method: "token",
    });

    const res = await invokeTool({
      port: sharedPort,
      headers: {
        authorization: "Bearer secret",
        "x-openclaw-scopes": "operator.approvals",
      },
      tool: "owner_only_test",
      sessionKey: "main",
    });

    const body = await expectOkInvokeResponse(res);
    expect(body.result).toEqual({ ok: true, result: "owner-only" });
  });

  it("extends the HTTP deny list to high-risk execution and file tools", async () => {
    setMainAllowedTools({ allow: ["exec", "apply_patch", "nodes"] });

    const execRes = await invokeToolAuthed({
      tool: "exec",
      sessionKey: "main",
    });
    const patchRes = await invokeToolAuthed({
      tool: "apply_patch",
      sessionKey: "main",
    });
    const nodesRes = await invokeToolAuthed({
      tool: "nodes",
      sessionKey: "main",
    });
    const nodesAdminRes = await invokeTool({
      port: sharedPort,
      headers: gatewayAdminHeaders(),
      tool: "nodes",
      sessionKey: "main",
    });

    expect(execRes.status).toBe(404);
    expect(patchRes.status).toBe(404);
    expect(nodesRes.status).toBe(404);
    expect(nodesAdminRes.status).toBe(404);
  });

  it("falls back to plugin-backed tools when a cataloged core tool has no core implementation", async () => {
    setMainAllowedTools({ allow: ["browser"] });

    const res = await invokeToolAuthed({
      tool: "browser",
      sessionKey: "main",
    });

    const body = await expectOkInvokeResponse(res);
    expect(body.result).toEqual({ ok: true, result: "browser" });
    expect(lastCreateOpenClawToolsContext?.disablePluginTools).toBe(false);
  });
});

¤ Dauer der Verarbeitung: 0.3 Sekunden  (vorverarbeitet am  2026-04-27) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.