fix(sessions): reject malformed history offsets (#103594)

* fix(sessions): reject malformed history offsets

* test(sessions): prove invalid offsets stop before reads

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
qingminlong
2026-07-10 18:00:31 +01:00
committed by GitHub
co-authored by Peter Steinberger
parent 586df58bcc
commit bf02b611eb
4 changed files with 22 additions and 12 deletions
@@ -504,6 +504,14 @@ describe("embedded gateway stub", () => {
params: { sessionKey: "agent:main:main", offset: 1.5 },
}),
).rejects.toThrow("offset must be a non-negative integer");
await expect(
callGateway({
method: "chat.history",
params: { sessionKey: "agent:main:main", offset: "1abc" },
}),
).rejects.toThrow("offset must be a non-negative integer");
expect(runtime.readSessionMessagesAsync).not.toHaveBeenCalled();
expect(runtime.readRecentSessionMessagesWithStatsAsync).not.toHaveBeenCalled();
expect(runtime.readSessionMessagesPageWithStatsAsync).not.toHaveBeenCalled();
});
});
+2 -5
View File
@@ -17,7 +17,7 @@ import type {
import type { SessionsListResult } from "../../gateway/session-utils.types.js";
import type { SessionsResolveResult } from "../../gateway/sessions-resolve.js";
import { parseAgentSessionKey } from "../../routing/session-key.js";
import { readNumberParam, readPositiveIntegerParam } from "./common.js";
import { readNonNegativeIntegerParam, readPositiveIntegerParam } from "./common.js";
type EmbeddedCallGateway = <T = Record<string, unknown>>(opts: CallGatewayOptions) => Promise<T>;
@@ -109,10 +109,7 @@ async function getRuntime(): Promise<EmbeddedGatewayRuntime> {
}
function readOffsetParam(params: Record<string, unknown>): number | undefined {
const offset = readNumberParam(params, "offset", {
integer: true,
nonNegativeInteger: true,
});
const offset = readNonNegativeIntegerParam(params, "offset");
if (params.offset !== undefined && offset === undefined) {
throw new Error("offset must be a non-negative integer");
}
+10 -2
View File
@@ -118,12 +118,20 @@ describe("sessions_history redaction", () => {
);
});
it.each([-1, 1.5])("rejects invalid offset value %s", async (offset) => {
const tool = createHistoryToolWithMessage("hello");
it.each([-1, 1.5, "1abc"])("rejects invalid offset value %s", async (offset) => {
const requests: CallGatewayRequest[] = [];
const tool = createSessionsHistoryTool({
config: {},
callGateway: async <T = Record<string, unknown>>(request: CallGatewayRequest): Promise<T> => {
requests.push(request);
return { messages: [] } as T;
},
});
await expect(tool.execute("call-1", { sessionKey: "main", offset })).rejects.toThrow(
"offset must be a non-negative integer",
);
expect(requests).toEqual([]);
});
it("preserves the bounded default history request", async () => {
+2 -5
View File
@@ -21,7 +21,7 @@ import { stripToolMessages } from "./chat-history-text.js";
import type { AnyAgentTool } from "./common.js";
import {
jsonResult,
readNumberParam,
readNonNegativeIntegerParam,
readPositiveIntegerParam,
readStringParam,
ToolInputError,
@@ -53,10 +53,7 @@ type ChatHistoryPaginationMetadata = {
};
function readOffsetParam(params: Record<string, unknown>): number | undefined {
const offset = readNumberParam(params, "offset", {
integer: true,
nonNegativeInteger: true,
});
const offset = readNonNegativeIntegerParam(params, "offset");
if (params.offset !== undefined && offset === undefined) {
throw new ToolInputError("offset must be a non-negative integer");
}