Fix/agent session key normalization (openclaw#15707) thanks @rodrigouroz

Verified:
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: rodrigouroz <384037+rodrigouroz@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
This commit is contained in:
Rodrigo Uroz
2026-02-15 12:46:14 -03:00
committed by GitHub
parent 75d22b2164
commit df95ddc771
4 changed files with 91 additions and 1 deletions

View File

@@ -336,4 +336,54 @@ describe("gateway agent handler", () => {
expect(call?.message).toBe(BARE_SESSION_RESET_PROMPT);
expect(call?.sessionId).toBe("reset-session-id");
});
it("rejects malformed agent session keys early in agent handler", async () => {
mocks.agentCommand.mockClear();
const respond = vi.fn();
await agentHandlers.agent({
params: {
message: "test",
sessionKey: "agent:main",
idempotencyKey: "test-malformed-session-key",
},
respond,
context: makeContext(),
req: { type: "req", id: "4", method: "agent" },
client: null,
isWebchatConnect: () => false,
});
expect(mocks.agentCommand).not.toHaveBeenCalled();
expect(respond).toHaveBeenCalledWith(
false,
undefined,
expect.objectContaining({
message: expect.stringContaining("malformed session key"),
}),
);
});
it("rejects malformed session keys in agent.identity.get", async () => {
const respond = vi.fn();
await agentHandlers["agent.identity.get"]({
params: {
sessionKey: "agent:main",
},
respond,
context: makeContext(),
req: { type: "req", id: "5", method: "agent.identity.get" },
client: null,
isWebchatConnect: () => false,
});
expect(respond).toHaveBeenCalledWith(
false,
undefined,
expect.objectContaining({
message: expect.stringContaining("malformed session key"),
}),
);
});
});

View File

@@ -16,7 +16,7 @@ import {
resolveAgentDeliveryPlan,
resolveAgentOutboundTarget,
} from "../../infra/outbound/agent-delivery.js";
import { normalizeAgentId } from "../../routing/session-key.js";
import { classifySessionKeyShape, normalizeAgentId } from "../../routing/session-key.js";
import { defaultRuntime } from "../../runtime.js";
import { normalizeInputProvenance, type InputProvenance } from "../../sessions/input-provenance.js";
import { resolveSendPolicy } from "../../sessions/send-policy.js";
@@ -273,6 +273,20 @@ export const agentHandlers: GatewayRequestHandlers = {
typeof request.sessionKey === "string" && request.sessionKey.trim()
? request.sessionKey.trim()
: undefined;
if (
requestedSessionKeyRaw &&
classifySessionKeyShape(requestedSessionKeyRaw) === "malformed_agent"
) {
respond(
false,
undefined,
errorShape(
ErrorCodes.INVALID_REQUEST,
`invalid agent params: malformed session key "${requestedSessionKeyRaw}"`,
),
);
return;
}
let requestedSessionKey =
requestedSessionKeyRaw ??
resolveExplicitAgentSessionKey({
@@ -601,6 +615,17 @@ export const agentHandlers: GatewayRequestHandlers = {
const sessionKeyRaw = typeof p.sessionKey === "string" ? p.sessionKey.trim() : "";
let agentId = agentIdRaw ? normalizeAgentId(agentIdRaw) : undefined;
if (sessionKeyRaw) {
if (classifySessionKeyShape(sessionKeyRaw) === "malformed_agent") {
respond(
false,
undefined,
errorShape(
ErrorCodes.INVALID_REQUEST,
`invalid agent.identity.get params: malformed session key "${sessionKeyRaw}"`,
),
);
return;
}
const resolved = resolveAgentIdFromSessionKey(sessionKeyRaw);
if (agentId && resolved !== agentId) {
respond(

View File

@@ -285,6 +285,20 @@ describe("gateway server agent", () => {
expect(spy).not.toHaveBeenCalled();
});
test("agent rejects malformed agent-prefixed session keys", async () => {
setRegistry(defaultRegistry);
const res = await rpcReq(ws, "agent", {
message: "hi",
sessionKey: "agent:main",
idempotencyKey: "idem-agent-malformed-key",
});
expect(res.ok).toBe(false);
expect(res.error?.message).toContain("malformed session key");
const spy = vi.mocked(agentCommand);
expect(spy).not.toHaveBeenCalled();
});
test("agent forwards accountId to agentCommand", async () => {
setRegistry(defaultRegistry);
testState.allowFrom = ["+1555"];