From f4e4fdb2e47086310f9659bd31b9100067367c8a Mon Sep 17 00:00:00 2001 From: Nguyen Khac Trung Kien Date: Sun, 8 Feb 2026 08:53:54 +0700 Subject: [PATCH] fix(agent-teams): add strict identifier validation rules --- src/tools/agent-teams/name-validation.test.ts | 22 +++++++++++++- src/tools/agent-teams/name-validation.ts | 25 ++++++++++++++++ src/tools/agent-teams/types.test.ts | 29 +++++++++++++++++++ src/tools/agent-teams/types.ts | 4 ++- 4 files changed, 78 insertions(+), 2 deletions(-) create mode 100644 src/tools/agent-teams/types.test.ts diff --git a/src/tools/agent-teams/name-validation.test.ts b/src/tools/agent-teams/name-validation.test.ts index 9dd0b124..9e2c70ca 100644 --- a/src/tools/agent-teams/name-validation.test.ts +++ b/src/tools/agent-teams/name-validation.test.ts @@ -1,6 +1,11 @@ /// import { describe, expect, test } from "bun:test" -import { validateAgentName, validateTeamName } from "./name-validation" +import { + validateAgentName, + validateAgentNameOrLead, + validateTaskId, + validateTeamName, +} from "./name-validation" describe("agent-teams name validation", () => { test("accepts valid team names", () => { @@ -55,4 +60,19 @@ describe("agent-teams name validation", () => { expect(validResult).toBeNull() expect(invalidResult).toBe("agent_name_invalid") }) + + test("allows team-lead for inbox-compatible validation", () => { + //#then + expect(validateAgentNameOrLead("team-lead")).toBeNull() + expect(validateAgentNameOrLead("worker_1")).toBeNull() + expect(validateAgentNameOrLead("worker one")).toBe("agent_name_invalid") + }) + + test("validates task ids", () => { + //#then + expect(validateTaskId("T-123")).toBeNull() + expect(validateTaskId("")).toBe("task_id_required") + expect(validateTaskId("../../etc/passwd")).toBe("task_id_invalid") + expect(validateTaskId("a".repeat(129))).toBe("task_id_too_long") + }) }) diff --git a/src/tools/agent-teams/name-validation.ts b/src/tools/agent-teams/name-validation.ts index 10d868fa..dcf3fcf1 100644 --- a/src/tools/agent-teams/name-validation.ts +++ b/src/tools/agent-teams/name-validation.ts @@ -1,5 +1,7 @@ const VALID_NAME_RE = /^[A-Za-z0-9_-]+$/ const MAX_NAME_LENGTH = 64 +const VALID_TASK_ID_RE = /^[A-Za-z0-9_-]+$/ +const MAX_TASK_ID_LENGTH = 128 function validateName(value: string, label: "team" | "agent"): string | null { if (!value || !value.trim()) { @@ -27,3 +29,26 @@ export function validateAgentName(agentName: string): string | null { } return validateName(agentName, "agent") } + +export function validateAgentNameOrLead(agentName: string): string | null { + if (agentName === "team-lead") { + return null + } + return validateName(agentName, "agent") +} + +export function validateTaskId(taskId: string): string | null { + if (!taskId || !taskId.trim()) { + return "task_id_required" + } + + if (!VALID_TASK_ID_RE.test(taskId)) { + return "task_id_invalid" + } + + if (taskId.length > MAX_TASK_ID_LENGTH) { + return "task_id_too_long" + } + + return null +} diff --git a/src/tools/agent-teams/types.test.ts b/src/tools/agent-teams/types.test.ts new file mode 100644 index 00000000..85291866 --- /dev/null +++ b/src/tools/agent-teams/types.test.ts @@ -0,0 +1,29 @@ +/// +import { describe, expect, test } from "bun:test" +import { TeamTeammateMemberSchema } from "./types" + +describe("agent-teams types", () => { + test("rejects reserved agentType for teammate schema", () => { + //#given + const invalidTeammate = { + agentId: "worker@team", + name: "worker", + agentType: "team-lead", + model: "native", + prompt: "do work", + color: "blue", + planModeRequired: false, + joinedAt: Date.now(), + cwd: "/tmp", + subscriptions: [], + backendType: "native", + isActive: false, + } + + //#when + const result = TeamTeammateMemberSchema.safeParse(invalidTeammate) + + //#then + expect(result.success).toBe(false) + }) +}) diff --git a/src/tools/agent-teams/types.ts b/src/tools/agent-teams/types.ts index cfa76d76..3883238c 100644 --- a/src/tools/agent-teams/types.ts +++ b/src/tools/agent-teams/types.ts @@ -27,7 +27,9 @@ export const TeamLeadMemberSchema = z.object({ export const TeamTeammateMemberSchema = z.object({ agentId: z.string(), name: z.string(), - agentType: z.string(), + agentType: z.string().refine((value) => value !== "team-lead", { + message: "agent_type_reserved", + }), model: z.string(), prompt: z.string(), color: z.string(),