From 2ddedad1d03eae4fafbc3a550547f7af4c7923b5 Mon Sep 17 00:00:00 2001 From: wanglu241 Date: Sat, 13 Jun 2026 01:29:02 +0800 Subject: [PATCH] fix(sessions): tighten gateway model-run key predicate The model-run prune predicate fell back to testing the raw sessionKey when parseAgentSessionKey returned null, so unscoped keys like `explicit:model-run-` and shapes with empty agent ids were eligible for the new default 24h cleanup. Restrict matching to keys that successfully parse as agent-scoped with a non-empty agent id, and add negative tests covering unscoped, empty-agent, extra-segment, and whitespace-padded keys. Refs #88632 (review feedback before merge). --- src/config/sessions/store-maintenance.ts | 12 +++++- src/config/sessions/store.pruning.test.ts | 52 +++++++++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/src/config/sessions/store-maintenance.ts b/src/config/sessions/store-maintenance.ts index 2e22d1d3037c..8a7c7d72388d 100644 --- a/src/config/sessions/store-maintenance.ts +++ b/src/config/sessions/store-maintenance.ts @@ -190,8 +190,18 @@ export function shouldRunSessionEntryMaintenance(params: { } export function isGatewayModelRunSessionKey(sessionKey: string): boolean { + if ( + !/^agent:[^:]+:explicit:model-run-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test( + sessionKey, + ) + ) { + return false; + } const parsed = parseAgentSessionKey(sessionKey); - const rest = normalizeLowercaseStringOrEmpty(parsed?.rest ?? sessionKey); + if (!parsed || !parsed.agentId.trim()) { + return false; + } + const rest = normalizeLowercaseStringOrEmpty(parsed.rest); return /^explicit:model-run-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test( rest, ); diff --git a/src/config/sessions/store.pruning.test.ts b/src/config/sessions/store.pruning.test.ts index 0a98b44fdc2d..dcc63b7222df 100644 --- a/src/config/sessions/store.pruning.test.ts +++ b/src/config/sessions/store.pruning.test.ts @@ -278,6 +278,58 @@ describe("pruneStaleModelRunEntries", () => { ), ).toBe(false); }); + + it("rejects non-canonical session keys that do not parse as agent-scoped", () => { + // Unscoped: missing `agent::` prefix — parseAgentSessionKey returns null. + expect( + isGatewayModelRunSessionKey("explicit:model-run-123e4567-e89b-12d3-a456-426614174000"), + ).toBe(false); + // Empty agent id segment: not a canonical `agent::` scoped key. + expect( + isGatewayModelRunSessionKey("agent::explicit:model-run-123e4567-e89b-12d3-a456-426614174000"), + ).toBe(false); + // Extra colon segment between agent id and `explicit:` — rest starts + // with `extra:` and fails the predicate's regex. + expect( + isGatewayModelRunSessionKey( + "agent:main:extra:explicit:model-run-123e4567-e89b-12d3-a456-426614174000", + ), + ).toBe(false); + // Whitespace-padded keys are non-canonical even though parseAgentSessionKey + // trims before normalizing; the predicate intentionally checks the original + // key shape before accepting a model-run key. + expect( + isGatewayModelRunSessionKey( + " agent:main:explicit:model-run-123e4567-e89b-12d3-a456-426614174000", + ), + ).toBe(false); + expect( + isGatewayModelRunSessionKey( + "agent:main:explicit:model-run-123e4567-e89b-12d3-a456-426614174000 ", + ), + ).toBe(false); + }); + + it("matches canonical keys whose agent id begins with model-run-", () => { + // Guards against an over-tight fix that confuses the agent id segment + // with the `explicit:model-run-` rest segment. + expect( + isGatewayModelRunSessionKey( + "agent:model-run-foo:explicit:model-run-123e4567-e89b-12d3-a456-426614174000", + ), + ).toBe(true); + }); + + it("preserves case-insensitive matching for canonical keys", () => { + // normalizeLowercaseStringOrEmpty + parseAgentSessionKey's normalization + // lower-case everything outside opaque peer IDs, so a mixed-case + // canonical key still matches. + expect( + isGatewayModelRunSessionKey( + "agent:Main:Explicit:Model-Run-123E4567-E89B-12D3-A456-426614174000", + ), + ).toBe(true); + }); }); describe("capEntryCount", () => {