Back to skill

Security audit

TaskQueue — Async Task Queue for AI Agents

Security checks for vulnerabilities and agentic risk

Overview

This skill is a small in-memory JavaScript task queue, but users should configure timeouts and avoid automatic retries for non-idempotent tasks.

Install only if you are comfortable treating this as a lightweight helper rather than a hardened production queue. Use finite timeouts, keep retry counts low, avoid retries for actions that publish, delete, charge, or mutate external systems unless they are idempotent, and do not accept untrusted task definitions without dependency validation.

Vulnerability Patterns
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (2)

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. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
src/task-queue.js:113
Finding
Dependency Ordering and Cycles Can Deadlock Queue Execution<![CDATA[ ## Vulnerability Details **File Location**: `src/task-queue.js`, lines 113–140 and 190–194 **Vulnerability Type**: Dependency-scheduling deadlock and denial of service **Risk Level**: Medium ### Vulnerable Code ```javascript // Sort by priority (lower number = higher priority) tasksToRun.sort((a, b) => a.priority - b.priority); // Build lookup map for dependency resolution — uses the full pre-drain list const taskMap = new Map(tasksToRun.map(t => [t.id, t])); const runOne = async (task) => { // Skip if cancelled if (this._cancelledIds.has(task.id) || task.status === 'cancelled') { task.status = 'cancelled'; this._log(task, 'Cancelled before start'); this.results.push(this._toResult(task)); this.emit('task:cancelled', task); return; } // Wait while paused while (this._paused) await this._delay(100); // Check dependency if (task.dependsOn) { const dep = taskMap.get(task.dependsOn); if (dep) { // Wait for dependency to finish (it may be running in a parallel batch) while (dep.status === 'running' || dep.status === 'queued') { await this._delay(50); } if (dep.status === 'failed' || dep.status === 'skipped' || dep.status === 'cancelled') { task.status = 'skipped'; this._log(task, `Skipped — dependency "${task.dependsOn}" did not succeed`); this.results.push(this._toResult(task)); this.emit('task:skipped', task); return; } } } ``` ```javascript // Run with concurrency — parallel batches of size this.concurrency for (let i = 0; i < tasksToRun.length; i += this.concurrency) { const batch = tasksToRun.slice(i, i + this.concurrency); await Promise.all(batch.map(t => runOne(t))); } ``` ### Technical Analysis Tasks are sorted by priority and divided into fixed batches. Every task in the current batch must settle through `Promise.all()` before the next batch starts. Dependency resolution, however, waits while the referenced task ...[truncated 2400 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Validate the complete dependency graph before starting execution. 2. Require task IDs to be unique and reject missing dependency references. 3. Reject direct self-dependencies. 4. Detect dependency cycles with depth-first traversal, Kahn's algorithm, or another standard graph-cycle algorithm. 5. Replace fixed priority batches with dependency-aware scheduling: - Identify tasks whose dependencies have succeeded. - Run only those ready tasks up to the concurrency limit. - Re-evaluate readiness as tasks complete. - Fail explicitly if unfinished tasks remain but none are runnable. 6. Treat priority as an ordering rule only among tasks that are already dependency-ready. 7. Add a queue-level timeout or deadlock detector as defense in depth. 8. Use `try/finally` in `run()` to ensure `_running` is reset even when scheduling fails. 9. Add tests covering forward dependencies, dependencies across concurrency boundaries, self-dependencies, multi-task cycles, missing IDs, duplicate IDs, and failed dependencies. ]]>
Vulnerability Patterns
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (2)

Unbounded Resource Access

Medium
Category
Excessive Agency
Content
* @param {number} [options.maxRetries=3]        - Default max retries per task
   * @param {number} [options.retryDelay=5000]     - Base delay between retries (ms); multiplied by retry count
   * @param {number} [options.concurrency=1]       - Max tasks running in parallel
   * @param {number} [options.timeout=0]           - Default task timeout in ms (0 = no timeout)
   */
  constructor(options = {}) {
    super();
Confidence
93% confidence
Finding
The queue permits a default timeout of 0, meaning tasks may run indefinitely. In an agent context, untrusted or buggy task handlers can hang forever, tying up concurrency slots, preventing queue drain, and causing denial of service or resource exhaustion over time.

Session Persistence

Medium
Category
Rogue Agent
Content
}

  /**
   * Add a task to the queue.
   * @param {object} task
   * @param {string}   [task.id]         - Unique ID (auto-generated if omitted)
   * @param {string}   [task.name]       - Human-readable name
Confidence
80% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Static analysis

No suspicious patterns detected.