mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-30 19:30:21 +00:00
fix(agents): enforce session_status guard after sessionId resolution (#55105)
* fix(agents): enforce visibility guard after sessionId resolution in session_status
When a sessionId (rather than an explicit agent key) is passed to the
session_status tool, the sessionId resolution block rewrites
requestedKeyRaw to an explicit "agent:..." key. The subsequent
visibility guard check at line 375 tested
`!requestedKeyRaw.startsWith("agent:")`, which was now always false
after resolution — skipping the visibility check entirely.
This meant a sandboxed agent could bypass visibility restrictions by
providing a sessionId instead of an explicit session key.
Fix: use the original `isExplicitAgentKey` flag (captured before
resolution) instead of re-checking the dynamic requestedKeyRaw.
This ensures the visibility guard runs for sessionId inputs while
still skipping the redundant check for inputs that were already
validated at the earlier explicit-key check (lines 281-286).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: cover session status sessionId guard
* test: align parent sessionId guard coverage
---------
Co-authored-by: Kevin Sheng <shenghuikevin@github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -587,6 +587,57 @@ describe("session_status tool", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("blocks sandboxed child session_status parent sessionId access outside its tree", async () => {
|
||||
resetSessionStore({
|
||||
"agent:main:subagent:child": {
|
||||
sessionId: "s-child",
|
||||
updatedAt: 20,
|
||||
},
|
||||
"agent:main:main": {
|
||||
sessionId: "s-parent",
|
||||
updatedAt: 10,
|
||||
},
|
||||
});
|
||||
installSandboxedSessionStatusConfig();
|
||||
mockSpawnedSessionList(() => []);
|
||||
|
||||
const tool = getSessionStatusTool("agent:main:subagent:child", {
|
||||
sandboxed: true,
|
||||
});
|
||||
|
||||
await expect(
|
||||
tool.execute("call7-parent-session-id", {
|
||||
sessionKey: "s-parent",
|
||||
}),
|
||||
).rejects.toThrow("Session status visibility is restricted to the current session tree");
|
||||
|
||||
expect(loadSessionStoreMock).toHaveBeenCalledTimes(1);
|
||||
expect(loadSessionStoreMock).toHaveBeenCalledWith("/tmp/main/sessions.json");
|
||||
expect(updateSessionStoreMock).not.toHaveBeenCalled();
|
||||
expect(callGatewayMock).toHaveBeenCalledTimes(3);
|
||||
expect(callGatewayMock.mock.calls).toContainEqual([
|
||||
{
|
||||
method: "sessions.resolve",
|
||||
params: {
|
||||
sessionId: "s-parent",
|
||||
spawnedBy: "agent:main:subagent:child",
|
||||
includeGlobal: false,
|
||||
includeUnknown: false,
|
||||
},
|
||||
},
|
||||
]);
|
||||
expect(callGatewayMock.mock.calls).toContainEqual([
|
||||
{
|
||||
method: "sessions.list",
|
||||
params: {
|
||||
includeGlobal: false,
|
||||
includeUnknown: false,
|
||||
spawnedBy: "agent:main:subagent:child",
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("keeps legacy main requester keys for sandboxed session tree checks", async () => {
|
||||
resetSessionStore({
|
||||
"agent:main:main": {
|
||||
|
||||
@@ -367,7 +367,7 @@ export function createSessionStatusTool(opts?: {
|
||||
throw new Error(`Unknown ${kind}: ${requestedKeyRaw}`);
|
||||
}
|
||||
|
||||
if (visibilityGuard && !requestedKeyRaw.startsWith("agent:")) {
|
||||
if (visibilityGuard && !isExplicitAgentKey) {
|
||||
const access = visibilityGuard.check(
|
||||
normalizeVisibilityTargetSessionKey(resolved.key, agentId),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user