mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 05:31:23 +00:00
fix(security): harden gateway command/audit guardrails
This commit is contained in:
@@ -21,6 +21,13 @@ function normalizePart(value: unknown, fallback: string): string {
|
||||
export function resolveControlPlaneRateLimitKey(client: GatewayClient | null): string {
|
||||
const deviceId = normalizePart(client?.connect?.device?.id, "unknown-device");
|
||||
const clientIp = normalizePart(client?.clientIp, "unknown-ip");
|
||||
if (deviceId === "unknown-device" && clientIp === "unknown-ip") {
|
||||
// Last-resort fallback: avoid cross-client contention when upstream identity is missing.
|
||||
const connId = normalizePart(client?.connId, "");
|
||||
if (connId) {
|
||||
return `${deviceId}|${clientIp}|conn=${connId}`;
|
||||
}
|
||||
}
|
||||
return `${deviceId}|${clientIp}`;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { __testing as controlPlaneRateLimitTesting } from "./control-plane-rate-limit.js";
|
||||
import {
|
||||
__testing as controlPlaneRateLimitTesting,
|
||||
resolveControlPlaneRateLimitKey,
|
||||
} from "./control-plane-rate-limit.js";
|
||||
import { handleGatewayRequest } from "./server-methods.js";
|
||||
import type { GatewayRequestHandler } from "./server-methods/types.js";
|
||||
|
||||
@@ -121,4 +124,43 @@ describe("gateway control-plane write rate limit", () => {
|
||||
expect(allowed).toHaveBeenCalledWith(true, undefined, undefined);
|
||||
expect(handlerCalls).toHaveBeenCalledTimes(4);
|
||||
});
|
||||
|
||||
it("uses connId fallback when both device and client IP are unknown", () => {
|
||||
const key = resolveControlPlaneRateLimitKey({
|
||||
connect: {
|
||||
role: "operator",
|
||||
scopes: ["operator.admin"],
|
||||
client: {
|
||||
id: "openclaw-control-ui",
|
||||
version: "1.0.0",
|
||||
platform: "darwin",
|
||||
mode: "ui",
|
||||
},
|
||||
minProtocol: 1,
|
||||
maxProtocol: 1,
|
||||
},
|
||||
connId: "conn-fallback",
|
||||
});
|
||||
expect(key).toBe("unknown-device|unknown-ip|conn=conn-fallback");
|
||||
});
|
||||
|
||||
it("keeps device/IP-based key when identity is present", () => {
|
||||
const key = resolveControlPlaneRateLimitKey({
|
||||
connect: {
|
||||
role: "operator",
|
||||
scopes: ["operator.admin"],
|
||||
client: {
|
||||
id: "openclaw-control-ui",
|
||||
version: "1.0.0",
|
||||
platform: "darwin",
|
||||
mode: "ui",
|
||||
},
|
||||
minProtocol: 1,
|
||||
maxProtocol: 1,
|
||||
},
|
||||
connId: "conn-fallback",
|
||||
clientIp: "10.0.0.10",
|
||||
});
|
||||
expect(key).toBe("unknown-device|10.0.0.10");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user