fix: address review feedback

This commit is contained in:
Pavan Kumar Gondhi
2026-04-02 16:52:49 +00:00
committed by Peter Steinberger
parent b02b2c3a0b
commit 74270762ff
4 changed files with 59 additions and 18 deletions

View File

@@ -320,7 +320,6 @@ function buildPluginRequestStages(params: {
return [];
}
let pluginGatewayAuthSatisfied = false;
let pluginRequestAuth: AuthorizedGatewayHttpRequest | undefined;
let pluginRequestOperatorScopes: string[] | undefined;
return [
{
@@ -350,7 +349,6 @@ function buildPluginRequestStages(params: {
return true;
}
pluginGatewayAuthSatisfied = true;
pluginRequestAuth = requestAuth;
pluginRequestOperatorScopes = resolvePluginRouteRuntimeOperatorScopes(
params.req,
requestAuth,
@@ -366,7 +364,6 @@ function buildPluginRequestStages(params: {
return (
params.handlePluginRequest?.(params.req, params.res, pathContext, {
gatewayAuthSatisfied: pluginGatewayAuthSatisfied,
gatewayRequestAuth: pluginRequestAuth,
gatewayRequestOperatorScopes: pluginRequestOperatorScopes,
}) ?? false
);

View File

@@ -320,6 +320,64 @@ describe("gateway plugin HTTP auth boundary", () => {
expect(writeAllowedResults).toEqual([false]);
});
test("keeps write runtime scopes for shared-secret bearer gateway-auth plugin routes", async () => {
const observedRuntimeScopes: string[][] = [];
const writeAllowedResults: boolean[] = [];
const handlePluginRequest = createGatewayPluginRequestHandler({
registry: createTestRegistry({
httpRoutes: [
{
pluginId: "runtime-scope-bearer",
source: "runtime-scope-bearer",
path: "/secure-hook",
auth: "gateway",
match: "exact",
handler: async (_req: IncomingMessage, res: ServerResponse) => {
const runtimeScopes =
getPluginRuntimeGatewayRequestScope()?.client?.connect?.scopes?.slice() ?? [];
observedRuntimeScopes.push(runtimeScopes);
const writeAuth = authorizeOperatorScopesForMethod("node.invoke", runtimeScopes);
writeAllowedResults.push(writeAuth.allowed);
res.statusCode = 200;
res.end("ok");
return true;
},
},
],
}),
log: { warn: vi.fn() } as Parameters<typeof createGatewayPluginRequestHandler>[0]["log"],
});
await withGatewayServer({
prefix: "openclaw-plugin-http-runtime-scope-bearer-test-",
resolvedAuth: AUTH_TOKEN,
overrides: {
handlePluginRequest,
shouldEnforcePluginGatewayAuth: (pathContext) => pathContext.pathname === "/secure-hook",
},
run: async (server) => {
const response = createResponse();
await dispatchRequest(
server,
createRequest({
path: "/secure-hook",
authorization: "Bearer test-token",
headers: {
"x-openclaw-scopes": "operator.read",
},
}),
response.res,
);
expect(response.res.statusCode).toBe(200);
expect(response.getBody()).toBe("ok");
},
});
expect(observedRuntimeScopes).toEqual([["operator.write"]]);
expect(writeAllowedResults).toEqual([true]);
});
test("allows unauthenticated Mattermost slash callback routes while keeping other channel routes protected", async () => {
const handlePluginRequest = vi.fn(async (req: IncomingMessage, res: ServerResponse) => {
const pathname = new URL(req.url ?? "/", "http://localhost").pathname;

View File

@@ -1,4 +1,4 @@
import type { IncomingMessage, ServerResponse } from "node:http";
import type { IncomingMessage } from "node:http";
import { A2UI_PATH, CANVAS_HOST_PATH, CANVAS_WS_PATH } from "../../canvas-host/a2ui.js";
import { safeEqualSecret } from "../../security/secret-equal.js";
import type { AuthRateLimiter } from "../auth-rate-limit.js";
@@ -8,7 +8,6 @@ import {
type ResolvedGatewayAuth,
} from "../auth.js";
import { CANVAS_CAPABILITY_TTL_MS } from "../canvas-capability.js";
import { authorizeGatewayBearerRequestOrReply } from "../http-auth-helpers.js";
import { getBearerToken, resolveHttpBrowserOriginPolicy } from "../http-utils.js";
import { GATEWAY_CLIENT_MODES, normalizeGatewayClientMode } from "../protocol/client-info.js";
import type { GatewayWsClient } from "./ws-types.js";
@@ -101,14 +100,3 @@ export async function authorizeCanvasRequest(params: {
}
return lastAuthFailure ?? { ok: false, reason: "unauthorized" };
}
export async function enforcePluginRouteGatewayAuth(params: {
req: IncomingMessage;
res: ServerResponse;
auth: ResolvedGatewayAuth;
trustedProxies: string[];
allowRealIpFallback: boolean;
rateLimiter?: AuthRateLimiter;
}): Promise<boolean> {
return await authorizeGatewayBearerRequestOrReply(params);
}

View File

@@ -3,7 +3,6 @@ import type { createSubsystemLogger } from "../../logging/subsystem.js";
import type { PluginRegistry } from "../../plugins/registry.js";
import { resolveActivePluginHttpRouteRegistry } from "../../plugins/runtime.js";
import { withPluginRuntimeGatewayRequestScope } from "../../plugins/runtime/gateway-request-scope.js";
import type { AuthorizedGatewayHttpRequest } from "../http-utils.js";
import { GATEWAY_CLIENT_IDS, GATEWAY_CLIENT_MODES } from "../protocol/client-info.js";
import { PROTOCOL_VERSION } from "../protocol/index.js";
import type { GatewayRequestOptions } from "../server-methods/types.js";
@@ -48,7 +47,6 @@ function createPluginRouteRuntimeClient(
export type PluginRouteDispatchContext = {
gatewayAuthSatisfied?: boolean;
gatewayRequestAuth?: AuthorizedGatewayHttpRequest;
gatewayRequestOperatorScopes?: readonly string[];
};