T09 · Insecure Skill Coding Practices
Error
- Location
- src/task-queue.js:150
- Finding
- Timed-Out Handlers Continue Running and May Be Executed Repeatedly<![CDATA[ ## Vulnerability Details **File Location**: `src/task-queue.js`, lines 150–180 and 293–304 **Vulnerability Type**: Uncancelled asynchronous execution and unsafe retry behavior **Risk Level**: High ### Vulnerable Code ```javascript while (task.retries <= task.maxRetries) { try { let resultPromise; if (task.handler) { resultPromise = task.handler(task); } else if (executor) { resultPromise = executor(task); } else { resultPromise = Promise.resolve({ message: `Task "${task.name}" queued — no handler or executor provided` }); } // Apply per-task timeout if configured if (task.timeout > 0) { task.result = await this._withTimeout(resultPromise, task.timeout, task.name); } else { task.result = await resultPromise; } task.status = 'success'; task.endTime = Date.now(); this._log(task, `Completed in ${task.endTime - task.startTime}ms`); this.emit('task:complete', task); break; } catch (err) { task.retries++; task.error = err.message; if (task.retries <= task.maxRetries) { this._log(task, `Retry ${task.retries}/${task.maxRetries}: ${err.message}`); this.emit('task:retry', { task, attempt: task.retries, error: err }); await this._delay(this.retryDelay * task.retries); // Exponential backoff } else { task.status = 'failed'; task.endTime = Date.now(); this._log(task, `Failed after ${task.maxRetries} retries: ${err.message}`); this.emit('task:failed', task); } } } ``` ```javascript _withTimeout(promise, ms, name) { return new Promise((resolve, reject) => { const timer = setTimeout( () => reject(new Error(`Task "${name}" timed out after ${ms}ms`)), ms ); promise .then(result => { clearTimeout(timer); resolve(result); }) .catch(err => { clearTimeout(timer); reject(err); }); }); } ``` ### Technical Analysis `_withTimeout()` races the supplied operation against a timer on ...[truncated 2199 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Provide an `AbortController` for every attempt and pass its `AbortSignal` to the handler or executor. 2. Abort the active attempt when its timeout expires. 3. Do not begin a retry until the previous attempt has confirmed termination. 4. If the underlying operation cannot be cancelled reliably, disable automatic retries after timeouts or require explicit opt-in. 5. Require idempotency keys for handlers that perform externally visible side effects. 6. Distinguish timeout failures from ordinary handler failures so callers can apply safer retry policies. 7. Attach rejection handling immediately and validate that handlers return promises or thenables before passing values to `_withTimeout()`. 8. Document that timeout enforcement cannot guarantee termination unless the handler cooperates with cancellation. 9. Add tests using a delayed side-effecting handler to confirm that no two attempts overlap and that timed-out operations cannot commit later. ]]>
