Back to skill

Security audit

todowrite

Security checks across malware telemetry and agentic risk

Overview

This task-management skill is purpose-aligned overall, but it needs Review because its always-on rules and helper scripts can modify persistent task state in unsafe or under-scoped ways.

Install only if you are comfortable with an always-on task-management policy that can reprioritize work and mutate persistent task records. Review the hook registration, avoid using untrusted task IDs or custom task directories with the CLI, and treat delete operations as potentially permanent until the implementation is fixed.

Vulnerability Patterns
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • 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
Findings (5)

T01 · Skill Instruction Hijacking

Error
Location
conversation-id.md:73
Finding
Always-on task policy can override the current user-directed workflow<![CDATA[ ## Vulnerability Details **File Location**: `conversation-id.md:73-80` **Vulnerability Type**: Agent instruction and session-goal hijacking **Risk Level**: High ### Vulnerable Code Snippet ```markdown ## TaskList check obligation Before starting new work, **always check TaskList first** — if pending tasks exist, handle them first or report to the user. - After subagent completion, re-check TaskList before processing results - Do not re-ask via `AskUserQuestion` for content already represented as a task — continue the existing task - Do not ignore existing items and create new ones - **Stale tasks from prior sessions**: if `TaskList` shows completed/in_progress tasks whose context is gone, run `TaskUpdate(status: "deleted")` to clear them — orphan tasks from prior sessions carry no usable context ``` The policy is exposed as an always-on topic in `SKILL.md:33-36`: ```markdown | conversation-id | — | Always-on | [conversation-id.md](./conversation-id.md) — subject-prefix references in user-visible output. Enforced by `resources/block-tasklist-id-in-conversation.sh` (PreToolUse:AskUserQuestion, registered in `settings.json`) | | completion-report | — | Always-on | [completion-report.md](./completion-report.md) — TaskUpdate completion format + file-change disclosure | | fix-plan-sync | — | Always-on | [fix-plan-sync.md](./fix-plan-sync.md) — two-way sync between task medium and checklist medium | | priority-prefix | — | Always-on | [priority-prefix.md](./priority-prefix.md) — priority/order via subject prefix (`P{n}`, PR-anchored, `fix-*` > P0) | ``` ### Technical Analysis The Skill does more than provide task-management functionality. It directs the agent to inspect TaskList before all new work and to handle pending entries before the current request. This can alter the active session goal even when the user did not ask to resume old tasks. The policy also directs the agent to delete completed or in-progress entries from prior sessions when their ...[truncated 1690 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Scope TaskList inspection to explicit task-management requests or to workflows in which the user has asked to resume tracked work. 2. State that the current user directive always takes precedence over unrelated TaskList entries. 3. Present unrelated pending entries as optional context rather than automatically processing them. 4. Never delete prior-session entries based only on an agent judgment that their context is gone. 5. Require explicit user confirmation before deleting or archiving any pre-existing task. 6. Prefer a reversible archival state over deletion and retain the original task metadata. 7. Remove the “Always-on” designation from policies that modify execution order or persistent state. ]]>

T08 · Insecure Dependencies

Error
Location
resources/block-tasklist-id-in-conversation.sh:76
Finding
Pre-tool hook executes a sibling data file as Bash code<![CDATA[ ## Vulnerability Details **File Location**: `resources/block-tasklist-id-in-conversation.sh:76-81` **Vulnerability Type**: Unsafe executable dependency loading **Risk Level**: High ### Vulnerable Code Snippet ```bash # Load locale-specific regex patterns from hook-kit data/ HG_DATA_FILE="$(dirname "$0")/../../hook-kit/data/hangul-patterns.regex" if [[ -f "$HG_DATA_FILE" ]]; then # shellcheck source=/dev/null . "$HG_DATA_FILE" fi HG_QUANTIFIER_SUFFIX="${HG_QUANTIFIER_SUFFIX:-}" ``` ### Technical Analysis The hook uses the Bash source operator (`.`) to load a file described as regex data. Sourcing does not parse the file as passive data; it executes every shell command in that file in the current hook process. The referenced file is outside this Skill’s directory under a sibling `hook-kit` path. The hook performs only an existence check and does not validate: - The canonical resolved path. - File ownership or permissions. - Whether the path or a parent is a symbolic link. - File integrity or an expected hash. - Whether the file contains only a permitted variable assignment. Because the script is intended to run as a `PreToolUse:AskUserQuestion` hook, compromise of the sibling file can turn a normal agent question into arbitrary local command execution. ### Attack Path 1. An attacker compromises the sibling `hook-kit` component, its installation process, or any writable parent/path element. 2. The attacker places shell commands in `data/hangul-patterns.regex` or redirects the path through a symbolic link. 3. The agent invokes `AskUserQuestion`. 4. The registered pre-tool hook starts. 5. The hook executes `. "$HG_DATA_FILE"`. 6. The attacker’s commands execute with the same account and environment as the agent process. For example, a malicious dependency file could contain command substitutions or ordinary commands in addition to assigning `HG_QUANTIFIER_SUFFIX`; all would execute during sourcing. ### Impact Assessment Successful exploitati ...[truncated 627 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not source regex or configuration data as shell code. 2. Store the required pattern in this Skill or read it through a strict data parser. 3. If a separate file is necessary, accept only a narrowly defined format, such as one raw line or JSON parsed with `jq`. 4. Resolve the canonical path and verify that it remains under an expected immutable installation directory. 5. Reject symbolic links and files writable by untrusted users or groups. 6. Verify file ownership, permissions, and optionally a pinned integrity hash before use. 7. Quote the imported value wherever it is interpolated into commands. 8. Add a security test proving that shell syntax placed in the data file is treated as text and is never executed. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
resources/claude-task.py:112
Finding
Unvalidated task IDs permit path traversal and arbitrary JSON-file access<![CDATA[ ## Vulnerability Details **File Location**: `resources/claude-task.py:112-127` **Additional Affected Location**: `resources/claude-task.py:207-238` **Vulnerability Type**: Path traversal and unsafe file access **Risk Level**: High ### Vulnerable Code Snippet ```python def load_task(task_dir: Path, task_id: str) -> Dict[str, Any]: task_file = task_dir / f"{task_id}.json" if not task_file.exists(): print(f"Error: Task #{task_id} not found in {task_dir}", file=sys.stderr) sys.exit(1) try: with open(task_file, "r", encoding="utf-8") as f: return json.load(f) except Exception as e: print(f"Error reading Task #{task_id}: {e}", file=sys.stderr) sys.exit(1) def save_task(task_dir: Path, task_data: Dict[str, Any]) -> None: task_id = task_data["id"] task_file = task_dir / f"{task_id}.json" with open(task_file, "w", encoding="utf-8") as f: json.dump(task_data, f, ensure_ascii=False, indent=2) ``` The same unvalidated ID is used by update and delete operations: ```python def cmd_update(args): task_dir = resolve_task_dir(args.dir, args.session, args.env) data = load_task(task_dir, args.id) if args.status: data["status"] = args.status if args.subject: data["subject"] = args.subject if args.description is not None: data["description"] = args.description if args.active_form is not None: data["activeForm"] = args.active_form if args.add_block: blocks = set(data.get("blocks", [])) blocks.update(args.add_block) data["blocks"] = sorted(list(blocks)) if args.add_blocked_by: blocked_by = set(data.get("blockedBy", [])) blocked_by.update(args.add_blocked_by) data["blockedBy"] = sorted(list(blocked_by)) save_task(task_dir, data) print(f"Updated Task #{args.id} (Status: {data['status']})") def cmd_delete(args): task_dir = resolve_task_dir(args.dir, args.sess ...[truncated 2509 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Require task IDs to match a strict numeric expression such as `^[1-9][0-9]*$`. 2. Reject path separators, absolute paths, dot segments, null bytes, and non-numeric input before constructing a filename. 3. Resolve both `task_dir` and the final task path, then verify that the final path is a direct child of the task directory. 4. Reject symbolic-link task files or use platform-appropriate no-follow file-opening controls. 5. Apply the same centralized validation to `show`, `update`, and `delete`. 6. Do not trust the `id` field loaded from a JSON document when deciding where to save it. Pass the already validated ID explicitly to `save_task`. 7. Use atomic writes to a temporary file in the validated directory followed by an atomic replacement. 8. Add tests for traversal IDs, absolute IDs, symlink targets, malformed IDs, and malicious internal `id` fields. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
resources/claude-task.py:233
Finding
Delete command permanently removes task files despite documented soft-delete semantics<![CDATA[ ## Vulnerability Details **File Location**: `resources/claude-task.py:233-240` **Related Documentation**: `claude-task.md:17-21` **Vulnerability Type**: Destructive operation inconsistent with documented behavior **Risk Level**: Medium ### Vulnerable Code Snippet The documentation describes a logical status transition: ```markdown - **Subcommands**: - `list` (or `ls`): List tasks in table format with ID, subject, activeForm, and status. - `show` (or `get`): View detailed JSON content for a specific task. - `add` (or `create`): Create a new task with auto-assigned numeric ID. - `update` (or `edit`): Update task status (`in_progress`, `completed`, `deleted`) or subject. - `delete` (or `rm`): Mark task status as `deleted`. - `dir`: Print resolved Task directory path. ``` The implementation instead permanently unlinks the file: ```python def cmd_delete(args): task_dir = resolve_task_dir(args.dir, args.session, args.env) task_file = task_dir / f"{args.id}.json" if task_file.exists(): task_file.unlink() print(f"Deleted Task #{args.id} from {task_dir}") else: print(f"Task #{args.id} not found in {task_dir}", file=sys.stderr) sys.exit(1) ``` ### Technical Analysis The documented behavior states that `delete` marks a task’s status as `deleted`, implying retention of the JSON record and reversible or auditable deletion. The implementation calls `Path.unlink()`, permanently removing the task file. This mismatch prevents users and calling agents from making an informed decision about the destructive effect. It also conflicts with workflows elsewhere in the Skill that treat `deleted` as a task state rather than physical erasure. The unit test reinforces physical deletion by directly calling `unlink()`, but it does not test the documented soft-delete contract. ### Attack Path 1. A user or agent consults the CLI documentation. 2. The caller expects `claude-task delete <id>` to preserve the task ...[truncated 815 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Implement deletion as a soft-delete operation: - Load the validated task. - Set `"status": "deleted"`. - Record an optional deletion timestamp and reason. - Save the task atomically. 2. If permanent removal is required, expose a separate command such as `purge`. 3. Require explicit confirmation or a `--force` option for permanent removal. 4. Consider moving purged records into a recoverable archive or trash directory. 5. Correct the accepted status choices so `update --status deleted` is available if soft deletion is part of the schema. 6. Add tests that assert the file remains present and its status becomes `deleted`. 7. Clearly document retention, recovery, and purge behavior. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
resources/claude-task.py:91
Finding
Task ID allocation and writes are vulnerable to concurrent overwrite<![CDATA[ ## Vulnerability Details **File Location**: `resources/claude-task.py:91-110` **Additional Affected Location**: `resources/claude-task.py:125-128` **Vulnerability Type**: Race condition and non-atomic file update **Risk Level**: Medium ### Vulnerable Code Snippet ```python def get_next_task_id(task_dir: Path) -> str: hw_file = task_dir / ".highwatermark" current_max = 0 if hw_file.exists(): try: content = hw_file.read_text().strip() if content.isdigit(): current_max = int(content) except Exception: pass for f in task_dir.glob("*.json"): if f.stem.isdigit(): val = int(f.stem) if val > current_max: current_max = val next_id = current_max + 1 hw_file.write_text(str(next_id) + "\n") return str(next_id) ``` Task data is also written directly to the destination file: ```python def save_task(task_dir: Path, task_data: Dict[str, Any]) -> None: task_id = task_data["id"] task_file = task_dir / f"{task_id}.json" with open(task_file, "w", encoding="utf-8") as f: json.dump(task_data, f, ensure_ascii=False, indent=2) ``` The guide advertises concurrency support in `claude-task.md`: ```markdown - **Concurrency & Highwatermark**: Auto-maintains `.highwatermark` for auto-incrementing numeric Task IDs (`#131`, `#132`, etc.). ``` ### Technical Analysis ID allocation is a read-modify-write sequence without any lock or exclusive operation. Two processes can read the same watermark and scan the same set of files before either writes its result. Both will then return the same `next_id`. `save_task` opens the destination with mode `"w"`, which truncates an existing file. Therefore, if two callers allocate the same ID, the later write can silently overwrite the earlier task. Direct writes also expose readers to partially written or truncated JSON if a process fails or another process reads during seri ...[truncated 1371 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Acquire an inter-process filesystem lock around the complete ID-allocation and task-creation transaction. 2. Create task files with exclusive creation semantics so an existing ID can never be silently overwritten. 3. If exclusive creation reports a collision, allocate a new ID while still holding the lock. 4. Write JSON to a temporary file in the same directory, flush and synchronize it as appropriate, then atomically replace the destination. 5. Update `.highwatermark` atomically using the same lock and temporary-file strategy. 6. Do not silently suppress watermark read errors; report corruption and fail safely. 7. Add multiprocessing tests that create many tasks concurrently and verify unique IDs and complete JSON documents. 8. Amend the documentation so concurrency is claimed only after locking and atomicity are implemented. ]]>
Vulnerability Patterns
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
Findings (3)

Lp3

Medium
Category
MCP Least Privilege
Confidence
88% confidence
Finding
The skill documents behaviors that imply file reads/writes, network access via GitHub CLI, and interaction with local task stores, yet it does not declare corresponding permissions. This creates hidden capability exposure: a user or reviewer may authorize the skill for simple TODO routing without realizing it can modify files and invoke networked issue creation workflows.

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding
The stated purpose is TODO/checklist routing, but the skill also references enforcement hooks that intercept AskUserQuestion usage and a standalone CLI that manages task JSON under user-home directories. That mismatch is dangerous because it hides control over user interaction and broader persistence mechanisms behind an innocuous productivity description, increasing the chance of unintended file modification, policy bypass, or confusing tool-governance side effects.

Vague Triggers

Medium
Confidence
93% confidence
Finding
The trigger list includes very generic terms such as "move," "transfer," "defer," and "hold," and labels them as HARD STOP conditions that force workflow actions. In a conversational skill, such broad keywords can be matched in benign discussion, causing unintended task deletion, checklist registration, or blocking-state transitions without the user clearly intending a medium transfer.

VirusTotal

VirusTotal findings are pending for this skill version.

View on VirusTotal

Static analysis

Detected: suspicious.dynamic_code_execution

Dynamic code execution detected.

Critical
Code
suspicious.dynamic_code_execution
Location
resources/tests/test_claude_task.py:21