mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-10 18:34:57 +00:00
fix(agents): validate sessions_spawn agentId format (#31381)
This commit is contained in:
@@ -223,7 +223,6 @@ describe("openclaw-tools: subagents (sessions_spawn allowlist)", () => {
|
|||||||
expect(details.error).toContain('sandbox="require"');
|
expect(details.error).toContain('sandbox="require"');
|
||||||
expect(callGatewayMock).not.toHaveBeenCalled();
|
expect(callGatewayMock).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// agentId format validation (#31311)
|
// agentId format validation (#31311)
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { loadConfig } from "../config/config.js";
|
|||||||
import { callGateway } from "../gateway/call.js";
|
import { callGateway } from "../gateway/call.js";
|
||||||
import { getGlobalHookRunner } from "../plugins/hook-runner-global.js";
|
import { getGlobalHookRunner } from "../plugins/hook-runner-global.js";
|
||||||
import {
|
import {
|
||||||
|
isValidAgentId,
|
||||||
isCronSessionKey,
|
isCronSessionKey,
|
||||||
normalizeAgentId,
|
normalizeAgentId,
|
||||||
parseAgentSessionKey,
|
parseAgentSessionKey,
|
||||||
@@ -31,16 +32,6 @@ export type SpawnSubagentMode = (typeof SUBAGENT_SPAWN_MODES)[number];
|
|||||||
export const SUBAGENT_SPAWN_SANDBOX_MODES = ["inherit", "require"] as const;
|
export const SUBAGENT_SPAWN_SANDBOX_MODES = ["inherit", "require"] as const;
|
||||||
export type SpawnSubagentSandboxMode = (typeof SUBAGENT_SPAWN_SANDBOX_MODES)[number];
|
export type SpawnSubagentSandboxMode = (typeof SUBAGENT_SPAWN_SANDBOX_MODES)[number];
|
||||||
|
|
||||||
/**
|
|
||||||
* Strict format gate for user-supplied agentId values.
|
|
||||||
* Rejects error-message-like strings, path traversals, and other
|
|
||||||
* values that would be silently mangled by {@link normalizeAgentId}
|
|
||||||
* and could create ghost workspace directories.
|
|
||||||
*
|
|
||||||
* Must stay in sync with the canonical `VALID_ID_RE` in session-key.ts.
|
|
||||||
*/
|
|
||||||
const STRICT_AGENT_ID_RE = /^[a-z0-9][a-z0-9_-]{0,63}$/i;
|
|
||||||
|
|
||||||
export function decodeStrictBase64(value: string, maxDecodedBytes: number): Buffer | null {
|
export function decodeStrictBase64(value: string, maxDecodedBytes: number): Buffer | null {
|
||||||
const maxEncodedBytes = Math.ceil(maxDecodedBytes / 3) * 4;
|
const maxEncodedBytes = Math.ceil(maxDecodedBytes / 3) * 4;
|
||||||
if (value.length > maxEncodedBytes * 2) {
|
if (value.length > maxEncodedBytes * 2) {
|
||||||
@@ -264,13 +255,12 @@ export async function spawnSubagentDirect(
|
|||||||
// Without this gate, error-message strings like "Agent not found: xyz" pass
|
// Without this gate, error-message strings like "Agent not found: xyz" pass
|
||||||
// through normalizeAgentId and become "agent-not-found--xyz", which later
|
// through normalizeAgentId and become "agent-not-found--xyz", which later
|
||||||
// creates ghost workspace directories and triggers cascading cron loops (#31311).
|
// creates ghost workspace directories and triggers cascading cron loops (#31311).
|
||||||
if (requestedAgentId && !STRICT_AGENT_ID_RE.test(requestedAgentId)) {
|
if (requestedAgentId && !isValidAgentId(requestedAgentId)) {
|
||||||
return {
|
return {
|
||||||
status: "error",
|
status: "error",
|
||||||
error: `Invalid agentId "${requestedAgentId}". Agent IDs must match [a-z0-9][a-z0-9_-]{0,63}. Use agents_list to discover valid targets.`,
|
error: `Invalid agentId "${requestedAgentId}". Agent IDs must match [a-z0-9][a-z0-9_-]{0,63}. Use agents_list to discover valid targets.`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const modelOverride = params.model;
|
const modelOverride = params.model;
|
||||||
const thinkingOverrideRaw = params.thinking;
|
const thinkingOverrideRaw = params.thinking;
|
||||||
const requestThreadBinding = params.thread === true;
|
const requestThreadBinding = params.thread === true;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
} from "../sessions/session-key-utils.js";
|
} from "../sessions/session-key-utils.js";
|
||||||
import {
|
import {
|
||||||
classifySessionKeyShape,
|
classifySessionKeyShape,
|
||||||
|
isValidAgentId,
|
||||||
parseAgentSessionKey,
|
parseAgentSessionKey,
|
||||||
toAgentStoreSessionKey,
|
toAgentStoreSessionKey,
|
||||||
} from "./session-key.js";
|
} from "./session-key.js";
|
||||||
@@ -115,3 +116,17 @@ describe("session key canonicalization", () => {
|
|||||||
).toBe("agent:main:main");
|
).toBe("agent:main:main");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("isValidAgentId", () => {
|
||||||
|
it("accepts valid agent ids", () => {
|
||||||
|
expect(isValidAgentId("main")).toBe(true);
|
||||||
|
expect(isValidAgentId("my-research_agent01")).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rejects malformed agent ids", () => {
|
||||||
|
expect(isValidAgentId("")).toBe(false);
|
||||||
|
expect(isValidAgentId("Agent not found: xyz")).toBe(false);
|
||||||
|
expect(isValidAgentId("../../../etc/passwd")).toBe(false);
|
||||||
|
expect(isValidAgentId("a".repeat(65))).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -99,6 +99,11 @@ export function normalizeAgentId(value: string | undefined | null): string {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isValidAgentId(value: string | undefined | null): boolean {
|
||||||
|
const trimmed = (value ?? "").trim();
|
||||||
|
return Boolean(trimmed) && VALID_ID_RE.test(trimmed);
|
||||||
|
}
|
||||||
|
|
||||||
export function sanitizeAgentId(value: string | undefined | null): string {
|
export function sanitizeAgentId(value: string | undefined | null): string {
|
||||||
return normalizeAgentId(value);
|
return normalizeAgentId(value);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user