fix(task-tool): add task ID validation and improve lock acquisition safety

- Add task ID pattern validation (T-[A-Za-z0-9-]+) to prevent path traversal
- Refactor lock mechanism to use UUID-based IDs for reliable ownership tracking
- Implement atomic lock creation with stale lock detection and cleanup
- Add lock acquisition checks in create/update/delete handlers
- Expand task-reminder hook to track split tool names and clean up on session deletion
- Add comprehensive test coverage for validation and lock handling
This commit is contained in:
YeonGyu-Kim
2026-02-01 23:48:48 +09:00
parent 172446795c
commit f853d885fa
9 changed files with 206 additions and 54 deletions
+34 -3
View File
@@ -27,6 +27,13 @@ import {
listTaskFiles,
} from "../../features/claude-tasks/storage"
const TASK_ID_PATTERN = /^T-[A-Za-z0-9-]+$/
function parseTaskId(id: string): string | null {
if (!TASK_ID_PATTERN.test(id)) return null
return id
}
export function createTask(config: Partial<OhMyOpenCodeConfig>): ToolDefinition {
return tool({
description: `Unified task management tool with create, list, get, update, delete actions.
@@ -88,6 +95,10 @@ async function handleCreate(
const taskDir = getTaskDir(config)
const lock = acquireLock(taskDir)
if (!lock.acquired) {
return JSON.stringify({ error: "task_lock_unavailable" })
}
try {
const taskId = generateTaskId()
const task: TaskObject = {
@@ -176,8 +187,12 @@ async function handleGet(
config: Partial<OhMyOpenCodeConfig>
): Promise<string> {
const validatedArgs = TaskGetInputSchema.parse(args)
const taskId = parseTaskId(validatedArgs.id)
if (!taskId) {
return JSON.stringify({ error: "invalid_task_id" })
}
const taskDir = getTaskDir(config)
const taskPath = join(taskDir, `${validatedArgs.id}.json`)
const taskPath = join(taskDir, `${taskId}.json`)
const task = readJsonSafe(taskPath, TaskObjectSchema)
@@ -189,11 +204,19 @@ async function handleUpdate(
config: Partial<OhMyOpenCodeConfig>
): Promise<string> {
const validatedArgs = TaskUpdateInputSchema.parse(args)
const taskId = parseTaskId(validatedArgs.id)
if (!taskId) {
return JSON.stringify({ error: "invalid_task_id" })
}
const taskDir = getTaskDir(config)
const lock = acquireLock(taskDir)
if (!lock.acquired) {
return JSON.stringify({ error: "task_lock_unavailable" })
}
try {
const taskPath = join(taskDir, `${validatedArgs.id}.json`)
const taskPath = join(taskDir, `${taskId}.json`)
const task = readJsonSafe(taskPath, TaskObjectSchema)
if (!task) {
@@ -234,11 +257,19 @@ async function handleDelete(
config: Partial<OhMyOpenCodeConfig>
): Promise<string> {
const validatedArgs = TaskDeleteInputSchema.parse(args)
const taskId = parseTaskId(validatedArgs.id)
if (!taskId) {
return JSON.stringify({ error: "invalid_task_id" })
}
const taskDir = getTaskDir(config)
const lock = acquireLock(taskDir)
if (!lock.acquired) {
return JSON.stringify({ error: "task_lock_unavailable" })
}
try {
const taskPath = join(taskDir, `${validatedArgs.id}.json`)
const taskPath = join(taskDir, `${taskId}.json`)
if (!existsSync(taskPath)) {
return JSON.stringify({ error: "task_not_found" })