mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-24 22:24:27 +00:00
Gateway: keep spawned workspace overrides internal (#43801)
* Gateway: keep spawned workspace overrides internal * Changelog: note GHSA-2rqg agent boundary fix * Gateway: persist spawned workspace inheritance in sessions * Agents: clean failed lineage spawn state * Tests: cover lineage attachment cleanup * Tests: cover lineage thread cleanup
This commit is contained in:
@@ -405,30 +405,53 @@ describe("gateway agent handler", () => {
|
||||
expect(callArgs.bestEffortDeliver).toBe(false);
|
||||
});
|
||||
|
||||
it("only forwards workspaceDir for spawned subagent runs", async () => {
|
||||
it("rejects public spawned-run metadata fields", async () => {
|
||||
primeMainAgentRun();
|
||||
mocks.agentCommand.mockClear();
|
||||
|
||||
await invokeAgent(
|
||||
{
|
||||
message: "normal run",
|
||||
sessionKey: "agent:main:main",
|
||||
workspaceDir: "/tmp/ignored",
|
||||
idempotencyKey: "workspace-ignored",
|
||||
},
|
||||
{ reqId: "workspace-ignored-1" },
|
||||
);
|
||||
await vi.waitFor(() => expect(mocks.agentCommand).toHaveBeenCalled());
|
||||
const normalCall = mocks.agentCommand.mock.calls.at(-1)?.[0] as { workspaceDir?: string };
|
||||
expect(normalCall.workspaceDir).toBeUndefined();
|
||||
mocks.agentCommand.mockClear();
|
||||
const respond = vi.fn();
|
||||
|
||||
await invokeAgent(
|
||||
{
|
||||
message: "spawned run",
|
||||
sessionKey: "agent:main:main",
|
||||
spawnedBy: "agent:main:subagent:parent",
|
||||
workspaceDir: "/tmp/inherited",
|
||||
workspaceDir: "/tmp/injected",
|
||||
idempotencyKey: "workspace-rejected",
|
||||
} as AgentParams,
|
||||
{ reqId: "workspace-rejected-1", respond },
|
||||
);
|
||||
|
||||
expect(mocks.agentCommand).not.toHaveBeenCalled();
|
||||
expect(respond).toHaveBeenCalledWith(
|
||||
false,
|
||||
undefined,
|
||||
expect.objectContaining({
|
||||
message: expect.stringContaining("invalid agent params"),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("only forwards workspaceDir for spawned sessions with stored workspace inheritance", async () => {
|
||||
primeMainAgentRun();
|
||||
mockMainSessionEntry({
|
||||
spawnedBy: "agent:main:subagent:parent",
|
||||
spawnedWorkspaceDir: "/tmp/inherited",
|
||||
});
|
||||
mocks.updateSessionStore.mockImplementation(async (_path, updater) => {
|
||||
const store: Record<string, unknown> = {
|
||||
"agent:main:main": buildExistingMainStoreEntry({
|
||||
spawnedBy: "agent:main:subagent:parent",
|
||||
spawnedWorkspaceDir: "/tmp/inherited",
|
||||
}),
|
||||
};
|
||||
return await updater(store);
|
||||
});
|
||||
mocks.agentCommand.mockClear();
|
||||
|
||||
await invokeAgent(
|
||||
{
|
||||
message: "spawned run",
|
||||
sessionKey: "agent:main:main",
|
||||
idempotencyKey: "workspace-forwarded",
|
||||
},
|
||||
{ reqId: "workspace-forwarded-1" },
|
||||
|
||||
@@ -190,24 +190,20 @@ export const agentHandlers: GatewayRequestHandlers = {
|
||||
timeout?: number;
|
||||
bestEffortDeliver?: boolean;
|
||||
label?: string;
|
||||
spawnedBy?: string;
|
||||
inputProvenance?: InputProvenance;
|
||||
workspaceDir?: string;
|
||||
};
|
||||
const senderIsOwner = resolveSenderIsOwnerFromClient(client);
|
||||
const cfg = loadConfig();
|
||||
const idem = request.idempotencyKey;
|
||||
const normalizedSpawned = normalizeSpawnedRunMetadata({
|
||||
spawnedBy: request.spawnedBy,
|
||||
groupId: request.groupId,
|
||||
groupChannel: request.groupChannel,
|
||||
groupSpace: request.groupSpace,
|
||||
workspaceDir: request.workspaceDir,
|
||||
});
|
||||
let resolvedGroupId: string | undefined = normalizedSpawned.groupId;
|
||||
let resolvedGroupChannel: string | undefined = normalizedSpawned.groupChannel;
|
||||
let resolvedGroupSpace: string | undefined = normalizedSpawned.groupSpace;
|
||||
let spawnedByValue = normalizedSpawned.spawnedBy;
|
||||
let spawnedByValue: string | undefined;
|
||||
const inputProvenance = normalizeInputProvenance(request.inputProvenance);
|
||||
const cached = context.dedupe.get(`agent:${idem}`);
|
||||
if (cached) {
|
||||
@@ -359,11 +355,7 @@ export const agentHandlers: GatewayRequestHandlers = {
|
||||
const sessionId = entry?.sessionId ?? randomUUID();
|
||||
const labelValue = request.label?.trim() || entry?.label;
|
||||
const sessionAgent = resolveAgentIdFromSessionKey(canonicalKey);
|
||||
spawnedByValue = canonicalizeSpawnedByForAgent(
|
||||
cfg,
|
||||
sessionAgent,
|
||||
spawnedByValue || entry?.spawnedBy,
|
||||
);
|
||||
spawnedByValue = canonicalizeSpawnedByForAgent(cfg, sessionAgent, entry?.spawnedBy);
|
||||
let inheritedGroup:
|
||||
| { groupId?: string; groupChannel?: string; groupSpace?: string }
|
||||
| undefined;
|
||||
@@ -400,6 +392,7 @@ export const agentHandlers: GatewayRequestHandlers = {
|
||||
providerOverride: entry?.providerOverride,
|
||||
label: labelValue,
|
||||
spawnedBy: spawnedByValue,
|
||||
spawnedWorkspaceDir: entry?.spawnedWorkspaceDir,
|
||||
spawnDepth: entry?.spawnDepth,
|
||||
channel: entry?.channel ?? request.channel?.trim(),
|
||||
groupId: resolvedGroupId ?? entry?.groupId,
|
||||
@@ -628,7 +621,7 @@ export const agentHandlers: GatewayRequestHandlers = {
|
||||
// Internal-only: allow workspace override for spawned subagent runs.
|
||||
workspaceDir: resolveIngressWorkspaceOverrideForSpawnedRun({
|
||||
spawnedBy: spawnedByValue,
|
||||
workspaceDir: request.workspaceDir,
|
||||
workspaceDir: sessionEntry?.spawnedWorkspaceDir,
|
||||
}),
|
||||
senderIsOwner,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user