fix(gateway): restore shared-secret HTTP tool invoke auth

This commit is contained in:
Peter Steinberger
2026-03-31 22:54:38 +09:00
parent 0c83754246
commit cbfeecfab4
6 changed files with 75 additions and 54 deletions

View File

@@ -433,7 +433,8 @@ describe("POST /tools/invoke", () => {
});
});
it("blocks trusted-proxy local-direct token fallback from invoking tools over HTTP", async () => {
it("accepts shared-secret bearer auth on the HTTP tools surface", async () => {
allowAgentsListForMain();
vi.mocked(authorizeHttpGatewayConnect).mockResolvedValueOnce({
ok: true,
method: "token",
@@ -453,14 +454,8 @@ describe("POST /tools/invoke", () => {
},
});
expect(res.status).toBe(403);
await expect(res.json()).resolves.toMatchObject({
ok: false,
error: {
type: "forbidden",
message: "gateway bearer auth cannot invoke tools over HTTP",
},
});
const body = await expectOkInvokeResponse(res);
expect(body.result).toEqual({ ok: true, result: [] });
});
it("uses before_tool_call adjusted params for HTTP tool execution", async () => {
@@ -632,7 +627,10 @@ describe("POST /tools/invoke", () => {
sessionKey: "main",
});
expect(res.status).toBe(404);
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 () => {
@@ -756,7 +754,8 @@ describe("POST /tools/invoke", () => {
});
});
it("blocks trusted-proxy local-direct token fallback from invoking tools over HTTP", async () => {
it("treats shared-secret bearer auth as full operator access on /tools/invoke", async () => {
allowAgentsListForMain();
vi.mocked(authorizeHttpGatewayConnect).mockResolvedValueOnce({
ok: true,
method: "token",
@@ -776,14 +775,8 @@ describe("POST /tools/invoke", () => {
},
});
expect(res.status).toBe(403);
await expect(res.json()).resolves.toMatchObject({
ok: false,
error: {
type: "forbidden",
message: "gateway bearer auth cannot invoke tools over HTTP",
},
});
const body = await expectOkInvokeResponse(res);
expect(body.result).toEqual({ ok: true, result: [] });
});
it("applies owner-only tool policy on the HTTP path", async () => {
@@ -801,7 +794,29 @@ describe("POST /tools/invoke", () => {
tool: "owner_only_test",
sessionKey: "main",
});
expect(allowedRes.status).toBe(404);
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 () => {