Back to skill

Security audit

progress-selfcheck

Security checks for vulnerabilities and agentic risk

Overview

The skill is mostly aligned with progress reporting, but it automatically runs task-supplied commands and builds PowerShell commands unsafely, so it needs careful review before installation.

Do not install this into a production or sensitive OpenClaw workspace without changes. Disable the cron job and Feishu sending by default, replace task `next` command execution with a strict allowlist or explicit approval, and change Feishu sending to call `openclaw` with safe argument handling instead of constructing PowerShell source. Treat write access to the task ledger as execution authority.

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
scripts/progress_selfcheck_and_send.py:198
Finding
PowerShell Command Injection Through Report Content and Feishu Configuration<![CDATA[ ## Vulnerability Details **File Location**: `scripts/progress_selfcheck_and_send.py`, lines 198-212 **Vulnerability Type**: PowerShell command injection **Risk Level**: High ### Vulnerable Code ```python def _send_feishu(cfg: dict, message: str) -> None: if not cfg.get("feishu", {}).get("enabled", True): return account = cfg["feishu"]["account"] target = cfg["feishu"]["target"] safe = message.replace("`", "``") ps = ( "$m = @'\n" + safe + "\n'@; " + f"openclaw message send --channel feishu --account {account} --target {target} --message \"$m\"" ) cmd = ["powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", ps] workdir = Path(cfg["workdir"]) res = subprocess.run(cmd, cwd=str(workdir), capture_output=True, text=True, encoding="utf-8", errors="replace") ``` ### Technical Analysis The function constructs executable PowerShell source by concatenating report content and configuration values into a command string. Replacing backticks is not sufficient to secure a PowerShell single-quoted here-string. A line containing the here-string terminator `'@` can close `$m` before the intended boundary. Any text following that terminator can then be interpreted as PowerShell code. The `feishu.account` and `feishu.target` values are also inserted directly into the PowerShell command without quoting or strict validation. PowerShell metacharacters in either value can consequently alter the intended command. The generated report contains data read from several mutable sources: - Task titles and `next` commands from the task ledger - Progress-event messages - Cron job names and state - Feishu configuration values The relevant content is formatted into `message` in `scripts/progress_selfcheck_and_send.py`, lines 217-282, before being embedded in PowerShell source. Using `subprocess.run()` with an argument array does not prevent this vulnerability because PowerShell itself is explicitly i ...[truncated 1720 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Do not construct PowerShell source from report or configuration data. Invoke the `openclaw` executable directly and pass every value as a distinct process argument: ```python def _send_feishu(cfg: dict, message: str) -> None: feishu = cfg.get("feishu", {}) if not feishu.get("enabled", True): return account = feishu["account"] target = feishu["target"] cmd = [ "openclaw", "message", "send", "--channel", "feishu", "--account", account, "--target", target, "--message", message, ] workdir = Path(cfg["workdir"]) res = subprocess.run( cmd, cwd=str(workdir), capture_output=True, text=True, encoding="utf-8", errors="replace", shell=False, timeout=60, ) if res.returncode != 0: raise SystemExit( f"openclaw message send failed rc={res.returncode}: " f"{res.stderr.strip() or res.stdout.strip()}" ) ``` Additional hardening should include: 1. Validate `account` against a narrow identifier pattern suitable for configured OpenClaw accounts. 2. Validate `target` against the expected Feishu target syntax, such as a strict `user:ou_...` format. 3. Reject control characters in account and target fields. 4. Apply length limits to report fields to prevent resource exhaustion. 5. Avoid logging full command errors if they may expose sensitive message content. 6. Run the scheduled job under a dedicated, least-privileged account. 7. Add regression tests containing PowerShell metacharacters, newlines, quotes, and the `'@` here-string terminator. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/task_reactivate.py:29
Finding
Arbitrary Process Execution Through Auto-Reactivated Task Commands<![CDATA[ ## Vulnerability Details **File Location**: `scripts/task_reactivate.py`, lines 29-102 **Vulnerability Type**: Unsafe execution of task-controlled commands with an ineffective denylist **Risk Level**: High ### Vulnerable Code ```python BLOCK_EXTERNAL_TOKENS = [ "openclaw message send", "--channel feishu", "--channel webchat", "http://", "https://", ] def is_external_action(cmd: str) -> bool: c = (cmd or "").lower() return any(tok in c for tok in [t.lower() for t in BLOCK_EXTERNAL_TOKENS]) def run_local(next_cmd: str, timeout_s: int = 180) -> Tuple[int, str]: parts = shlex.split(next_cmd) if not parts: return 2, "empty next" try: res = subprocess.run(parts, cwd=str(Path.cwd()), capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=timeout_s) out = (res.stdout or "").strip() err = (res.stderr or "").strip() msg = out if out else err return res.returncode, msg[:4000] except Exception as e: return 1, str(e) ``` The task command is subsequently executed as follows: ```python for item in stale[: args.max]: t = item["task"] next_cmd = t.get("next") or "" if not next_cmd: continue if is_external_action(next_cmd): append_event({"ts": now_ts(), "event": "touch", "id": t["id"], "notes": "reactivate_skipped: external action"}) continue append_event({"ts": now_ts(), "event": "touch", "id": t["id"], "notes": f"reactivate: running `{next_cmd}`"}) rc, msg = run_local(next_cmd) ``` ### Technical Analysis The Skill documents auto-reactivation as “local-only,” but the implementation does not enforce a safe local-operation boundary. It only rejects commands containing five literal substrings. Every other executable and argument sequence is accepted and passed to `subprocess.run()`. The absence of `shell=True` prevents direct interpretation of ordinary shell operators, but it does not make arb ...[truncated 2661 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Replace the substring denylist with a strict allowlist and structured task actions. Recommended controls: 1. Do not store arbitrary command lines in the task ledger. Store a predefined action identifier and validated parameters instead. 2. Maintain an explicit allowlist of permitted executables using resolved absolute paths. 3. Define an argument schema for each permitted executable and reject unknown flags or positional arguments. 4. Prohibit general-purpose interpreters and command runners, including Python, PowerShell, shell programs, `cmd.exe`, and similar tools. 5. Prohibit network clients and tools that can launch subprocesses. 6. Canonicalize and validate every filesystem path against a dedicated workspace subdirectory. 7. Reject symlinks and path traversal when an action reads or writes files. 8. Require explicit operator approval before a newly created task can become automatically executable. 9. Run reactivation in a sandbox with networking disabled, a minimal environment, resource limits, and access only to a dedicated working directory. 10. Execute the reactivation service under a dedicated least-privileged account. 11. Record the action identifier, validated arguments, executable path, and result in an append-only audit log. 12. Treat ledger write access as execution authority and restrict its filesystem permissions accordingly. A safer design would dispatch fixed operations rather than commands: ```python ALLOWED_ACTIONS = { "refresh_local_index": refresh_local_index, "rebuild_report": rebuild_report, } def run_local_action(action_name: str, params: dict) -> tuple[int, str]: handler = ALLOWED_ACTIONS.get(action_name) if handler is None: return 2, "action is not allowed" validated = validate_action_parameters(action_name, params) return handler(validated) ``` If arbitrary commands are operationally unavoidable, they should not be executed automatically. Require interactive appr ...[truncated 74 chars]
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (12)

Intent-Code Divergence

High
Confidence
98% confidence
Finding
The docstring states that external actions are blocked, but the implementation only checks for a small set of substrings such as specific channels and `http://`/`https://`. An attacker can easily bypass this by using other programs, different flags, alternate protocols, or wrapper scripts, while the misleading documentation may cause reviewers or operators to trust the safety boundary incorrectly.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The skill description states that it sends periodic self-checks to Feishu and writes artifacts for Webchat pull, but it does not present these behaviors as a clear privacy/security warning or explain what data may leave the local environment. Users may enable the skill without understanding that task status, workflow details, or other potentially sensitive operational metadata could be transmitted or exposed through external messaging and artifact retrieval paths.

Intent-Code Divergence

Medium
Confidence
95% confidence
Finding
The module presents itself as a self-check/reporting utility, but it also mutates task state by auto-reactivating tasks. This mismatch is dangerous because operators may run or schedule it expecting read-only behavior, causing unintended workflow changes and making the side effect easy to overlook during review.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The script sends a formatted snapshot of tasks, alerts, and recent events to Feishu without any user-facing disclosure or consent mechanism at send time. Because the payload may include internal task titles, statuses, and event messages, this creates a data exfiltration risk if the target is misconfigured, unauthorized, or broader than intended.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
cmd = ["powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", ps]

    workdir = Path(cfg["workdir"])
    res = subprocess.run(cmd, cwd=str(workdir), capture_output=True, text=True, encoding="utf-8", errors="replace")
    if res.returncode != 0:
        raise SystemExit(f"openclaw message send failed rc={res.returncode}: {res.stderr.strip() or res.stdout.strip()}")
Confidence
95% confidence
Finding
The script builds a PowerShell command string using configuration-derived values (`account`, `target`) and attacker-influenced message content, then executes it with `powershell -Command`. While backticks in the message are doubled, other PowerShell metacharacters and the unquoted/interpolated config fields can still enable command injection if configuration or upstream data is untrusted. In this skill context, the script also forwards task/event data externally, so compromise of inputs can become both code execution and data exfiltration.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
Automatic task reactivation occurs whenever eligible tasks are found, without explicit user confirmation or a prominent warning. In a scheduled automation context, this can silently alter task workflow, re-open blocked work, and create integrity issues if stale or incorrect task metadata triggers reactivation.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
# Auto-reactivate safe tasks
    if task_snap.get("eligible"):
        try:
            subprocess.run(["python", str(HERE / "task_reactivate.py"), "--stale-min", str(cfg.get("stale_minutes", 5)), "--max", str(cfg.get("max_reactivate_per_run", 2))],
                           cwd=str(Path(cfg["workdir"])), capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=180)
            task_snap = _task_snapshot(cfg, stale_min=int(cfg.get("stale_minutes", 5)), limits=limits)
        except Exception:
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
if not parts:
        return 2, "empty next"
    try:
        res = subprocess.run(parts, cwd=str(Path.cwd()), capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=timeout_s)
        out = (res.stdout or "").strip()
        err = (res.stderr or "").strip()
        msg = out if out else err
Confidence
95% confidence
Finding
The script automatically executes the task's `next` field via `subprocess.run` after only `shlex.split`, with no allowlist, origin validation, or trust boundary enforcement on task data. Because task ledger entries may be influenced by other components or users, this creates an arbitrary local command execution path that can run destructive commands or invoke tools that reach the network indirectly.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
Stale tasks marked `auto=true` are reactivated and executed automatically with no confirmation, user-facing warning, or secondary authorization at the point of execution. In this context, that makes any compromised or malformed task entry immediately actionable, increasing the likelihood that dangerous commands are executed silently as part of routine maintenance.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
Both cron jobs are explicitly configured to run in the Asia/Shanghai timezone, which imposes a locale-specific behavior. There is no accompanying justification, configurability, or indication that the timezone is user-selected, which creates a natural-language locale policy concern.

Natural-Language Policy Violations

Low
Confidence
96% confidence
Finding
The formatted report strings are all hard-coded in Chinese, which imposes a specific language on users without any visible opt-in or locale selection mechanism. This matches the language/locale policy violation category because the file does not document a justified region-specific constraint or offer a configurable language choice.

Vague Triggers

Low
Confidence
82% confidence
Finding
This manifest defines scheduled jobs and payload messages, but it does not document any constraints, exclusions, or limiting context for when these jobs should or should not be enabled beyond a bare cron schedule. For manifest files, the absence of specificity around trigger scope can lead to unintended invocation if this template is reused without additional guardrails.

Static analysis

No suspicious patterns detected.