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

Content
View full analysis
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
View remediation
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

Content
View full analysis
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
View remediation
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
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
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.

Content

Scanner excerpt · scripts/progress_selfcheck_and_send.py (reported line 212)May include surrounding context.

python
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()}")

Missing User Warnings

Medium
Category
Not specified by scanner
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.

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/progress_selfcheck_and_send.py (reported line 304)May include surrounding context.

python
# 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:

subprocess module call

Medium
Category
Dangerous Code Execution
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.

Content

Scanner excerpt · scripts/task_reactivate.py (reported line 60)May include surrounding context.

python
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

Missing User Warnings

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Vague Triggers

Low
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.