Back to skill

Security audit

Agent Task Queue

Security checks for vulnerabilities and agentic risk

Overview

This is a coherent task-queue skill, but its distributed-worker implementation can duplicate or overwrite queued work in ways users should review before relying on it.

Review this skill carefully before using it for distributed or high-impact jobs. It is most appropriate for local testing or workflows where handlers are idempotent and duplicate execution is tolerable. If using Redis or multiple workers, add atomic task claiming, duplicate-ID rejection, Redis access controls, and clear handling for sensitive task payloads and logs.

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/TaskQueue.ts:41
Finding

Non-Atomic Task Claiming Allows Duplicate Distributed Execution

Content
View full analysis
{ const tasks = await this.storage.listTasks(); const now = Date.now(); const ready = tasks .filter((task) => ["queued", "retry_scheduled"].includes(task.status)) .filter((task) => new Date(task.runAt).getTime() <= now) .sort(compareReadyTasks); const next = ready[0]; if (!next) { return undefined; } next.status = "running"; next.attempts += 1; next.startedAt = new Date().toISOString(); next.updatedAt = next.startedAt; await this.storage.updateTask(next); await this.log(next.id, "info", "Task claimed for execution", { attempt: next.attempts }); return next; } ``` The Redis backend performs independent reads and writes: ```ts async updateTask(task: TaskRecord): Promise { await this.saveTask(task); } async listTasks(): Promise { await this.connect(); const entries = await this.client.hVals(this.key("tasks")); return entries.map((entry) => JSON.parse(entry) as TaskRecord); } ``` The SQLite backend likewise separates listing from updating: ```ts async updateTask(task: TaskRecord): Promise { await this.saveTask(task); } async listTasks(): Promise { const rows = this.db.prepare("SELECT id, data FROM tasks").all() as Record[]; return rows.map(parseTask); } ``` ### Technical Analysis Task claiming is implemented as a read-select-write sequence rather than an atomic storage operation. Each scheduler first obtains all task records, selects the highest-priority ready task locally, and only afterward writes the updated `running` state. When two schedulers poll the same Redis or ...[truncated 2089 chars]
Remediation
View remediation
; ``` 2. For Redis, implement the selection and state transition in one Lua script or equivalent atomic transaction. The script should: - Select only an eligible queued task. - Atomically change its state to `running`. - Assign an unpredictable claim or lease token. - Record the worker identity and lease expiration. - Return the task only when the claim succeeds. 3. For SQLite, use a transaction and conditional update. The update must include an eligibility predicate and verify that exactly one row changed, for example: ```sql UPDATE tasks SET data = ? WHERE id = ? AND current_status IN ('queued', 'retry_scheduled'); ``` Consider storing status and scheduling fields in dedicated columns rather than only inside JSON so they can be selected and updated safely in SQL. 4. Require the claim token or matching worker ownership for `complete()` and `fail()` operations. This prevents a stale worker from overwriting the result after its lease expires. 5. Add lease renewal and recovery for workers that crash while holding a task. 6. Add concurrency tests using multiple queue and scheduler instances against the same Redis and SQLite storage, asserting that a task handler executes exactly once. 7. Document whether handlers must be idempotent. Even with atomic claiming, idempotency keys should be used for sensitive external side effects. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
src/TaskQueue.ts:24
Finding

Caller-Controlled Duplicate Task IDs Silently Overwrite Existing Tasks

Content
View full analysis
(options: TaskOptions): Promise> { const task = createTaskRecord(options); await this.dependencies.validateTask(task); if (task.status === "waiting_dependencies" && (await this.dependencies.areDependenciesSatisfied(task))) { await this.dependencies.persistDependencyResults(task); task.status = new Date(task.runAt).getTime() <= Date.now() ? "queued" : "retry_scheduled"; } await this.storage.saveTask(task); await this.log(task.id, "info", "Task enqueued", { priority: task.priority, runAt: task.runAt, dependencies: task.dependencies }); return task; } ``` Every storage backend then replaces the existing value. In-memory storage: ```ts async saveTask(task: TaskRecord): Promise { this.tasks.set(task.id, structuredClone(task)); } ``` SQLite storage: ```ts async saveTask(task: TaskRecord): Promise { this.db.prepare("INSERT OR REPLACE INTO tasks (id, data) VALUES (?, ?)").run(task.id, JSON.stringify(task)); } ``` Redis storage: ```ts async saveTask(task: TaskRecord): Promise { await this.connect(); await this.client.hSet(this.key("tasks"), task.id, JSON.stringify(task)); } ``` ### Technical Analysis `TaskOptions.id` is caller-controlled. The queue creates a random UUID only when the caller does not supply an ID. When a supplied ID already exists, `enqueue()` performs no duplicate check and all three backends silently overwrite the existing record. This violates the expected distinction between creating a task and updatin ...[truncated 2120 chars]
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
Findings (8)

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
80% confidence
Finding

This code opens a Redis connection and sends serialized task records, logs, and dependency results over the network via Redis commands such as hSet, hGet, hVals, lRange, rPush, and hGetAll. There are no comments, docstrings, logging statements, or confirmation prompts in the file disclosing that user or system data is being transmitted to an external Redis service.

Content

No source excerpt is available for this finding.

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
88% confidence
Finding

The production dependency better-sqlite3 is specified with a caret range, which allows automatic installation of newer minor/patch releases. This weakens build reproducibility and can expose consumers to supply-chain risk or unintended dependency changes if a compromised or breaking release is published within the allowed range.

Content

Scanner excerpt · package.json (reported line 32)May include surrounding context.

json
"node": ">=20"
  },
  "dependencies": {
    "better-sqlite3": "^11.9.0",
    "redis": "^5.1.0"
  },
  "devDependencies": {

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
91% confidence
Finding

The redis runtime dependency is unpinned via a caret range, so different installations may resolve to different package versions over time. In a task-queue skill that likely relies on Redis for coordination, this increases supply-chain exposure and may introduce vulnerable or incompatible releases without an explicit code change.

Content

Scanner excerpt · package.json (reported line 33)May include surrounding context.

json
},
  "dependencies": {
    "better-sqlite3": "^11.9.0",
    "redis": "^5.1.0"
  },
  "devDependencies": {
    "@types/better-sqlite3": "^7.6.13",

Unverifiable Dependency: redis has 1 known advisory(ies) (CVE-2021-29469 (Node-Redis potential exponential regex in monitor mode)), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
40% confidence
Finding

Dependency has known vulnerabilities (CVEs). Using packages with unpatched security flaws exposes the environment to known exploits.

Content

No source excerpt is available for this finding.

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
40% confidence
Finding

Dependencies lack version pinning, allowing potential malicious package updates. Consider pinning versions.

Content

Scanner excerpt · package.json (reported line 36)May include surrounding context.

json
"redis": "^5.1.0"
  },
  "devDependencies": {
    "@types/better-sqlite3": "^7.6.13",
    "@types/node": "^22.13.10",
    "tsx": "^4.19.3",
    "typescript": "^5.8.2"

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
40% confidence
Finding

Dependencies lack version pinning, allowing potential malicious package updates. Consider pinning versions.

Content

Scanner excerpt · package.json (reported line 37)May include surrounding context.

json
},
  "devDependencies": {
    "@types/better-sqlite3": "^7.6.13",
    "@types/node": "^22.13.10",
    "tsx": "^4.19.3",
    "typescript": "^5.8.2"
  }

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
40% confidence
Finding

Dependencies lack version pinning, allowing potential malicious package updates. Consider pinning versions.

Content

Scanner excerpt · package.json (reported line 38)May include surrounding context.

json
"devDependencies": {
    "@types/better-sqlite3": "^7.6.13",
    "@types/node": "^22.13.10",
    "tsx": "^4.19.3",
    "typescript": "^5.8.2"
  }
}

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
40% confidence
Finding

Dependencies lack version pinning, allowing potential malicious package updates. Consider pinning versions.

Content

Scanner excerpt · package.json (reported line 39)May include surrounding context.

json
"@types/better-sqlite3": "^7.6.13",
    "@types/node": "^22.13.10",
    "tsx": "^4.19.3",
    "typescript": "^5.8.2"
  }
}