fix: enforce spawned session visibility in key resolve

This commit is contained in:
Tak Hoffman
2026-03-24 20:22:02 -05:00
parent 154e14f18f
commit 57fd0a9b23
2 changed files with 73 additions and 0 deletions

View File

@@ -1116,6 +1116,41 @@ describe("gateway server sessions", () => {
expect(resolved.payload?.key).toBe("agent:main:subagent:target");
});
test("sessions.resolve by key respects spawnedBy visibility filters", async () => {
await createSessionStoreDir();
const now = Date.now();
await writeSessionStore({
entries: {
"agent:main:subagent:visible-parent": {
sessionId: "sess-visible-parent",
updatedAt: now - 3_000,
spawnedBy: "agent:main:main",
},
"agent:main:subagent:hidden-parent": {
sessionId: "sess-hidden-parent",
updatedAt: now - 2_000,
spawnedBy: "agent:main:main",
},
"agent:main:subagent:shared-child-key-filter": {
sessionId: "sess-shared-child-key-filter",
updatedAt: now - 1_000,
spawnedBy: "agent:main:subagent:hidden-parent",
},
},
});
const { ws } = await openClient();
const resolved = await rpcReq(ws, "sessions.resolve", {
key: "agent:main:subagent:shared-child-key-filter",
spawnedBy: "agent:main:subagent:visible-parent",
});
expect(resolved.ok).toBe(false);
expect(resolved.error?.message).toContain(
"No session found: agent:main:subagent:shared-child-key-filter",
);
});
test("sessions.delete rejects main and aborts active runs", async () => {
const { dir } = await createSessionStoreDir();
await writeSingleLineSession(dir, "sess-main", "hello");

View File

@@ -48,6 +48,25 @@ export async function resolveSessionKeyFromResolveParams(params: {
const target = resolveGatewaySessionStoreTarget({ cfg, key });
const store = loadSessionStore(target.storePath);
if (store[target.canonicalKey]) {
if (typeof p.spawnedBy === "string" && p.spawnedBy.trim().length > 0) {
const visible = listSessionsFromStore({
cfg,
storePath: target.storePath,
store,
opts: {
includeGlobal: p.includeGlobal === true,
includeUnknown: p.includeUnknown === true,
spawnedBy: p.spawnedBy,
agentId: p.agentId,
},
}).sessions.some((session) => session.key === target.canonicalKey);
if (!visible) {
return {
ok: false,
error: errorShape(ErrorCodes.INVALID_REQUEST, `No session found: ${key}`),
};
}
}
return { ok: true, key: target.canonicalKey };
}
const legacyKey = target.storeKeys.find((candidate) => store[candidate]);
@@ -63,6 +82,25 @@ export async function resolveSessionKeyFromResolveParams(params: {
s[primaryKey] = s[legacyKey];
}
});
if (typeof p.spawnedBy === "string" && p.spawnedBy.trim().length > 0) {
const visible = listSessionsFromStore({
cfg,
storePath: target.storePath,
store: loadSessionStore(target.storePath),
opts: {
includeGlobal: p.includeGlobal === true,
includeUnknown: p.includeUnknown === true,
spawnedBy: p.spawnedBy,
agentId: p.agentId,
},
}).sessions.some((session) => session.key === target.canonicalKey);
if (!visible) {
return {
ok: false,
error: errorShape(ErrorCodes.INVALID_REQUEST, `No session found: ${key}`),
};
}
}
return { ok: true, key: target.canonicalKey };
}