Back to skill

Security audit

Task Watcher Skill

Security checks for vulnerabilities and agentic risk

Overview

The skill does what it says, but its background watcher accepts under-scoped local paths and task IDs that can cause unintended file reads, deletes, or writes.

Review before installing. Use this only in an environment where task records are written by trusted agents, run the watcher with a low-privilege account, and avoid enabling the cron job until task IDs and packet/status/notification paths are validated and confined to intended directories.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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
Findings (2)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/lib/notifiers.py:217
Finding

Path Traversal in Discord Notification Audit-File Creation

Content
View full analysis
None: """Write notification to file as audit trail.""" notification_file = os.path.join( self.output_dir, task.task_id + "_" + task.updated_at.replace(':', '-') + ".json" ) notification_data = { 'task_id': task.task_id, 'channel': self.channel, 'reply_to': task.reply_to, 'message': message, 'timestamp': task.updated_at, } with open(notification_file, 'w', encoding='utf-8') as f: json.dump(notification_data, f, ensure_ascii=False, indent=2) ``` ### Technical Analysis The filename is constructed using `task.task_id` without validating or sanitizing path separators, absolute paths, `..` components, or symlink traversal. The task ID can originate from the `--task-id` argument in `scripts/register_task.py` or from a record inserted directly into the shared JSONL task store. Because `os.path.join()` does not guarantee that the resulting path remains under `self.output_dir`, a crafted task ID can escape the notification directory. An absolute task ID causes the output directory to be discarded, while traversal components can target writable parent directories. The watcher subsequently opens the computed destination in write mode, creating the file or truncating an existing file whose complete generated name can be predicted. The timestamp suffix limits direct selection of an arbitrary existing filename, but it does not prevent unauthorized file creation outside the intended directory. Existing directories must be present, and the effective impact remains constrained by the operating-system permissions of the watcher process. ### Attack Path 1. An attacker obtains the ability to register a task or ...[truncated 1324 chars]
Remediation
View remediation

T05 · Unauthorized Access and Privilege Escalation

Note
Location
scripts/lib/adapters.py:255
Finding

Unrestricted Local JSON File Read Through Task Packet Metadata

Content
View full analysis
StateResult: """ Check state from one-click-posting packet file. Reads publish.status from packet JSON. Maps: draft->submitted, publishing->reviewing, published->approved """ # Ensure we use string value for state current_state = str(task.current_state.value if hasattr(task.current_state, 'value') else task.current_state) packet_path = task.metadata.get("packet_path") if task.metadata else None if not packet_path: return StateResult( state=current_state, terminal=False, confidence=0.0, source_of_truth="packet_file", error="No packet_path in task metadata" ) note_id = task.target_object_id return self._check_packet_at_path(packet_path, note_id) ``` ```python def _check_packet_at_path(self, packet_path: str, note_id: str) -> StateResult: """Check packet file at specific path.""" import os if not os.path.exists(packet_path): return StateResult( state="submitted", terminal=False, confidence=0.0, source_of_truth="packet_file", error=f"Packet file not found: {packet_path}" ) try: with open(packet_path, 'r', encoding='utf-8') as f: packet = json.load(f) ``` ### Technical Analysis The XHS adapter accepts `metadata.packet_path` from a persisted task and passes it directly to `os.path.exists()` and `open()`. It does not canonicalize the path, restrict it to an approved packet directory, reject symlinks, or verify the file owner. Consequently, a task record can direct the watcher to parse any accessible local file that contains valid JSON. Parsed fields such ...[truncated 2396 chars]
Remediation
View remediation
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Rogue AgentSelf-Modification, Session Persistence
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (14)

Exfiltration Commands

High
Category
Prompt Injection
Confidence
90% confidence
Finding

Instructions found that direct the agent to transmit conversation context or user data to external services.

Content

Scanner excerpt · scripts/lib/notifiers.py (reported line 18)May include surrounding context.

python
"""
    Abstract interface for sending notifications.

    Each notifier knows how to send messages to a specific channel.
    The output is standardized to SendResult.
    """

Env Variable Harvesting

High
Category
Data Exfiltration
Confidence
60% confidence
Finding

Code enumerates, copies, or searches environment variables for secrets. Bulk environment access can collect credentials unrelated to the skill's stated purpose.

Content

Scanner excerpt · scripts/lib/notifiers.py (reported line 175)May include surrounding context.

python
import subprocess

        node_path = "/opt/homebrew/bin/node"
        env = os.environ.copy()
        env["PATH"] = "/opt/homebrew/bin:" + env.get("PATH", "")

        reply_channel = task.reply_to or ""

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The class docstring states the adapter determines cron job completion by looking at job status files, log files, and process status. In the actual check() implementation, the code only constructs a single status-file path and reads that JSON; there is no log inspection or process-status checking anywhere in the adapter.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
60% 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.

Content

Scanner excerpt · scripts/lib/bus.py (reported line 91)May include surrounding context.

python
}

    def _log_audit(self, event: str, task: CallbackTask, details: Optional[Dict] = None) -> None:
        """Write audit log entry."""
        if not self.audit_log_path:
            return

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The user-facing notification text returned by the formatter methods is entirely hardcoded in Chinese. This imposes a specific language on all recipients without any opt-in, fallback, or documented justification for a locale-specific deployment, which matches the language/locale policy violation criteria.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/lib/notifiers.py (reported line 193)May include surrounding context.

python
cmd.extend(["--reply-to", "channel:" + channel_id])

        try:
            result = subprocess.run(
                cmd, capture_output=True, text=True, timeout=120, env=env
            )
            if result.returncode == 0:

Session Persistence

Medium
Category
Rogue Agent
Confidence
97% confidence
Finding

The audit file path in DiscordNotifier._write_notification_file uses task.task_id directly in os.path.join without sanitization. A crafted task_id containing path separators or traversal components could cause writes outside the intended notification directory, potentially overwriting arbitrary files writable by the process and persisting sensitive notification content in attacker-chosen locations.

Content

Scanner excerpt · scripts/lib/notifiers.py (reported line 216)May include surrounding context.

python
return SendResult(ok=False, delivered=False, error="openclaw binary not found: " + self.openclaw_bin)

    def _write_notification_file(self, task: CallbackTask, message: str) -> None:
        """Write notification to file as audit trail."""
        notification_file = os.path.join(
            self.output_dir,
            task.task_id + "_" + task.updated_at.replace(':', '-') + ".json"

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

DirectNotifier.read_notification and DirectNotifier.ack accept arbitrary file paths and perform open/remove operations without constraining them to the notifier's output directory. If an attacker can influence the filepath argument, they can read or delete unintended files accessible to the process, turning a notification helper into a local file access primitive.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

This code constructs user-facing notification messages entirely in Chinese string literals, including terminal-state summaries and recommended next actions. Because the file provides no user opt-in, language selection, or justification for a Chinese-only locale, it violates the language/locale policy for natural-language content.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
60% 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.

Content

Scanner excerpt · tests/test_adapters.py (reported line 24)May include surrounding context.

python
@pytest.fixture
    def temp_dir(self):
        """Create temporary directory for test data."""
        temp_dir = tempfile.mkdtemp()
        yield temp_dir
        shutil.rmtree(temp_dir)

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
89% confidence
Finding

This code file contains natural-language expectations that enforce Chinese output (for example, asserting the message contains Chinese text). Under the policy criteria, forcing a specific language without user opt-in can be a locale policy violation, and this test suggests the notifier behavior is expected to be Chinese-only.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
87% confidence
Finding

The assertion requires terminal notifications to include Chinese text, which indicates the skill behavior is tied to a single language. Because no opt-in, fallback, or documented region-specific justification appears in this file, this matches the natural-language locale policy concern.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
86% confidence
Finding

This assertion expects a Chinese phrase in the generated notification, indicating that at least one user-facing path is locked to a specific language. In the absence of documented opt-in or region-specific scope in this file, that is a policy-relevant locale constraint.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
86% confidence
Finding

The stored session message is expected to contain Chinese text, again implying user-facing notifications are forced into a single language. This is a natural-language policy issue unless the skill offers language choice or clearly documents a justified locale restriction.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.