Back to skill

Security audit

orchestration, telegram, cron

Security checks for vulnerabilities and agentic risk

Overview

This skill has a coherent automation purpose, but its included executor can perform high-impact skill and cron changes without enforcing the Telegram authorization checks it documents.

Review before installing. This is suitable only for a tightly controlled Dev server where Telegram identity checks are enforced before any text reaches the executor, the executor account has limited privileges, and skill installs/updates are restricted to approved, pinned packages. Do not deploy the included script as a production Telegram bridge without adding structured chat/from verification and safer temporary-file handling.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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
Findings (3)

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/dev_executor.py:190
Finding
Declared Telegram sender and group authorization is not enforced<![CDATA[ ## Vulnerability Details **File Location**: `scripts/dev_executor.py:190-221` **Vulnerability Type**: Missing authorization enforcement **Risk Level**: High ### Vulnerable Code ```python def handle_line(line: str) -> tuple[bool, str]: line = line.strip() if not line.startswith("DEV "): return False, "SKIP" parts = shlex.split(line) # parts[0] = DEV if len(parts) < 2: return False, "ERR: missing command" cmd = parts[1] rest = parts[2:] if cmd == "skill": return handle_skill(rest) if cmd == "cron": return handle_cron(rest) return False, f"ERR: unknown command: {cmd}" def main(): ap = argparse.ArgumentParser() ap.add_argument("--group", required=True, help="Allowed Telegram group chat id") ap.add_argument("--pm-from", required=True, help="Allowed PM bot numeric from.id") ap.add_argument("--stdin", action="store_true", help="Read commands from stdin (for testing)") args = ap.parse_args() if args.stdin: for line in sys.stdin: ok, msg = handle_line(line) if msg != "SKIP": print(msg) return ``` ### Technical Analysis The executor accepts trusted Telegram group and sender identifiers through `--group` and `--pm-from`, but these values are never used to authorize commands. The command handler receives only an unstructured text line, so it has no access to the originating `chat.id` or `from.id`. Consequently, every input source that can write to the process's standard input is treated as authorized as long as the line begins with `DEV ` and contains an allowlisted command type. This contradicts the security contract in `SKILL.md`, which requires commands to be accepted only when both the Telegram group and sender match configured identifiers. Although the script is described as a scaffold, any wrapper that forwards Telegram text without independently and correctly enforcing identity checks would expose ...[truncated 1394 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace raw text input with a structured event containing at least: - `chat.id` - `from.id` - message text - a trustworthy indication that the event came from the configured Telegram integration 2. Before calling `handle_line()`, reject every event for which: - `chat.id` does not equal the configured group identifier. - `from.id` does not equal the configured PM bot identifier. - The message is not a single-line command beginning with the exact required prefix. 3. Parse numeric identifiers once and compare them using consistent types rather than loosely comparing strings. 4. Treat stdin mode as testing-only. Require an explicit development flag and refuse to enable it in production configurations. 5. Ensure the Telegram wrapper does not trust sender or group identifiers supplied inside message text. 6. Add tests proving that wrong-group, wrong-sender, missing-metadata, malformed-event, and forwarded-message cases are rejected before any subprocess is started. 7. Apply least privilege to the executor account and restrict filesystem permissions on the OpenClaw workspace. ]]>

T08 · Insecure Dependencies

Error
Location
scripts/dev_executor.py:66
Finding
Remote skills can be installed or updated without source, version, or integrity controls<![CDATA[ ## Vulnerability Details **File Location**: `scripts/dev_executor.py:66-78` **Vulnerability Type**: Unrestricted and unpinned third-party skill installation **Risk Level**: High ### Vulnerable Code ```python if len(args) < 2: return False, f"ERR: missing slug for skill {sub}" slug = args[1] # Use clawhub CLI if sub == "install": cmd = ["clawhub", "install", slug, "--dir", os.path.join(ws, "skills")] elif sub == "update": cmd = ["clawhub", "update", slug] else: # search query = " ".join(args[1:]) cmd = ["clawhub", "search", query] rc, out = run(cmd, cwd=ws) ``` ### Technical Analysis The executor passes a chat-supplied skill slug directly to the `clawhub install` or `clawhub update` operation. No approved-skill allowlist, immutable version, digest, signature, publisher verification, or review gate is applied. The code also does not enforce the documented slug format. Passing subprocess arguments as a list prevents conventional shell metacharacter injection, but it does not address the supply-chain risk: the selected remote package can contain mutable or malicious skill content. An update can also replace previously reviewed content with a later publisher-controlled version. This issue becomes especially significant when combined with the missing authorization enforcement, but it remains a supply-chain weakness even for an authenticated PM account because compromise or operator error can introduce unreviewed components into the active workspace. ### Attack Path 1. An attacker publishes or compromises a remotely available ClawHub skill. 2. The attacker causes an accepted command to reference that skill, for example: ```text DEV skill install attacker-namespace/malicious-skill ``` 3. The executor invokes `clawhub install` without checking an approved publisher, version, signature, or expected digest. 4. The remote skill is written into the OpenClaw skills directory. 5. ...[truncated 962 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Maintain an explicit allowlist of approved skill namespaces, slugs, publishers, and versions. 2. Pin installations to immutable versions and verified cryptographic digests. 3. Verify package signatures or registry provenance before installation. 4. Require human confirmation for first-time installation and for updates that change reviewed content. 5. Download and inspect packages in a quarantined directory before promoting them into the active skills directory. 6. Enforce the documented slug syntax with a strict full-match regular expression and reject leading hyphens, unexpected path components, control characters, and excessive lengths. 7. Disable unrestricted update operations; resolve the intended update to a known digest before modifying the workspace. 8. Run installation and review steps under a restricted account without access to production credentials. 9. Log the requesting identity, package source, publisher, resolved version, and verified digest for every installation or update. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/dev_executor.py:173
Finding
Predictable cron-job temporary file allows symlink overwrite attacks<![CDATA[ ## Vulnerability Details **File Location**: `scripts/dev_executor.py:173-183` **Vulnerability Type**: Unsafe predictable temporary file **Risk Level**: Medium ### Vulnerable Code ```python # Use openclaw cron add via stdin json (if supported) else via temp file. # We'll write a temp file. tmp = os.path.join(ws, "tmp-cron-job.json") with open(tmp, "w", encoding="utf-8") as f: json.dump(job, f) rc, out = run(["openclaw", "cron", "add", "--file", tmp], cwd=ws) try: os.remove(tmp) except OSError: pass return (rc == 0), ("OK " + out[-1200:]) if rc == 0 else ("ERR " + out[-1200:]) ``` ### Technical Analysis Every cron creation uses the same predictable path, `tmp-cron-job.json`, inside the workspace. Python's normal `open(..., "w")` follows symbolic links and truncates an existing target. If another user or process can create or replace entries in the workspace, it can pre-create this path as a symbolic link to another file writable by the executor. Triggering a cron-add operation then truncates and overwrites the symlink target with attacker-influenced JSON. The fixed filename also creates concurrency hazards: two simultaneous cron-add requests can overwrite or delete each other's temporary files, potentially causing the wrong job definition to be submitted. ### Attack Path 1. An attacker obtains write access to the OpenClaw workspace directory but does not have the executor account's full file permissions. 2. The attacker creates a symbolic link: ```text tmp-cron-job.json -> target-file-writable-by-executor ``` 3. The attacker or another authorized user triggers: ```text DEV cron add every=10m name="job" message="controlled content" ``` 4. The executor opens the predictable path with write-and-truncate semantics. 5. The operating system follows the symbolic link, and the target file is overwritten with the generated cron JSON. 6. Th ...[truncated 802 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Use `tempfile.NamedTemporaryFile` or `tempfile.mkstemp` to create a uniquely named file atomically. 2. Place temporary files in a private directory owned by the executor with permissions such as `0700`. 3. Ensure files are created with restrictive permissions such as `0600`. 4. Where supported, use no-follow and exclusive-creation semantics to prevent symbolic-link traversal and pre-existing-file replacement. 5. Perform cleanup in a `finally` block so exceptions from file writing or subprocess execution do not leave sensitive artifacts behind. 6. Prefer passing the JSON through standard input to `openclaw` if the CLI supports it, avoiding a filesystem temporary file entirely. 7. Prevent concurrent invocations from sharing state, and add tests covering symlink pre-creation, existing files, exceptions, and parallel cron-add operations. ]]>
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (8)

Ae1

High
Category
analysis-evasion
Content
`scripts/dev_executor.py` is included as a parser/executor scaffold for testing, but the primary path is the Dev bot behavior above.
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Intent-Code Divergence

High
Confidence
99% confidence
Finding
The module claims it only accepts commands from a specific Telegram group and PM sender, but the implementation never verifies either property when processing commands. In context, this script is explicitly intended to bridge chat messages into local clawhub/openclaw administrative actions, so missing authentication/authorization enables unauthorized skill installation and cron management if any untrusted input reaches handle_line.

Lp3

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding
The skill defines behavior that can trigger shell-like command execution (`clawhub`, `openclaw cron ...`) and access local configuration/workspace state, but it does not declare an explicit tool scope or permission boundary. In a skill that bridges Telegram messages to server-side actions, missing tool restrictions increases the risk of overbroad execution, accidental capability exposure, and unsafe deployment assumptions.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def run(cmd: list[str], cwd: str | None = None) -> tuple[int, str]:
    p = subprocess.run(cmd, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
    out = (p.stdout or "").strip()
    return p.returncode, out
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Tainted flow: 'tmp' from os.environ.get (line 179, credential/environment) → open (file write)

Medium
Category
Data Flow
Content
# Use openclaw cron add via stdin json (if supported) else via temp file.
        # We'll write a temp file.
        tmp = os.path.join(ws, "tmp-cron-job.json")
        with open(tmp, "w", encoding="utf-8") as f:
            json.dump(job, f)
        rc, out = run(["openclaw", "cron", "add", "--file", tmp], cwd=ws)
        try:
Confidence
81% confidence
Finding
The temp file path is derived from the workspace path, which can come from the OPENCLAW_WORKSPACE environment variable, and the code writes to a predictable filename. If an attacker can influence that environment or workspace contents, they may cause file overwrite or symlink-based writes to unintended locations with the script's privileges.

Natural-Language Policy Violations

Low
Confidence
93% confidence
Finding
The instruction 'PM speaks natural Russian to the human' imposes a specific language requirement in the skill description. Under the stated policy, forcing a language or locale without user opt-in is a natural-language policy violation unless the constraint is explicitly optional or justified as region-specific.

Missing User Warnings

Low
Confidence
79% confidence
Finding
The code invokes local subprocess commands to remove or run cron jobs, which can alter scheduled task state or trigger actions, but there is no confirmation prompt or direct user-facing warning at those call sites. The top-level docstring states the script executes allowlisted operations, but it does not clearly warn that these specific operations may modify or trigger system behavior.

Missing User Warnings

Low
Confidence
84% confidence
Finding
The code writes a cron job definition to a local file and then deletes it, but there is no confirmation prompt or user-facing notice at the point of execution. Although the module docstring describes local CLI execution generally, it does not specifically disclose this file write behavior near the operation itself.

Static analysis

No suspicious patterns detected.