Back to skill

Security audit

skill_install

Security checks for vulnerabilities and agentic risk

Overview

This skill is a disclosed installer, but it installs untrusted ZIP contents into persistent OpenClaw skill directories with weak validation and unsafe filesystem handling.

Review ZIP sources carefully before using this skill. It should only be used with trusted skill archives, preferably after the installer is fixed to validate package metadata, constrain install paths to the skills directory, use a private temporary directory, reject symlinks and traversal, and make Gateway restart an explicit opt-in step. Do not run it with elevated privileges, and avoid the README's wildcarded rm -rf reinstall command.

Vulnerability Patterns
  • Tool Hijacking and SpoofingModifies or replaces tools so legitimate-looking calls execute attacker logic
  • 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 (4)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/skill_install.py:157
Finding
Archive-Controlled Skill Name Allows Filesystem Escape, Arbitrary Directory Creation, and Destructive Replacement## Vulnerability Details **File Location**: `scripts/skill_install.py`, lines 157-188 **Vulnerability Type**: Path traversal and unsafe filesystem operations **Risk Level**: High ### Vulnerable Code ```python skill_name = os.path.basename(skill_source) if skill_name == "openclaw_skill_temp": skill_md = os.path.join(skill_source, "SKILL.md") if os.path.exists(skill_md): with open(skill_md, 'r', encoding='utf-8') as f: for line in f: if line.startswith('name:'): skill_name = line.split(':', 1)[1].strip() break if skill_name == "openclaw_skill_temp": skill_name = os.path.splitext(os.path.basename(zip_path))[0] target_dir = os.path.join(self.skills_dir, skill_name) if os.path.exists(target_dir): response = input("是否覆盖? (y/N): ").strip().lower() if response != 'y': return False, "用户取消安装" shutil.rmtree(target_dir) shutil.copytree(skill_source, target_dir) ``` ### Technical Analysis The installer reads `skill_name` from an untrusted `SKILL.md` file inside the supplied archive and uses it directly to construct `target_dir`. It does not reject: - Absolute paths - `..` traversal components - Forward or backward path separators - Symlinked destination components - Paths that resolve outside the OpenClaw skills directory In Python, an absolute second argument to `os.path.join()` replaces the preceding directory. A value such as `/tmp/attacker-target` therefore causes `target_dir` to point outside `self.skills_dir`. A relative value such as `../../attacker-target` can similarly escape the intended directory after path resolution. The same untrusted destination is passed to both `shutil.rmtree()` and `shutil.copytree()`. Consequently, accepting the overwrite prompt can cause an existing directory outside the skills directory to be recursively deleted before being replaced with archive co ...[truncated 1559 chars]
Remediation
## Remediation Suggestions - Parse the YAML frontmatter with a real YAML parser instead of line-based string processing. - Require the skill name to match a restrictive slug pattern, such as: ```python r"^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$" ``` - Explicitly reject absolute paths, `..`, `/`, `\`, null bytes, and platform-specific path prefixes. - Resolve both the skills directory and candidate destination with `Path.resolve()`. - Verify that the resolved destination is a direct child of the resolved skills directory before performing any operation. - Reject symlinks in the destination and its relevant path components. - Do not recursively delete a path derived solely from package metadata. - Implement safer replacement by copying to a newly created staging directory, validating it, and then atomically renaming it into place. - Display the fully resolved destination to the user before any destructive operation.

T09 · Insecure Skill Coding Practices

Error
Location
scripts/skill_install.py:133
Finding
Predictable Shared Temporary Directory Enables Symlink and Race-Condition Attacks## Vulnerability Details **File Location**: `scripts/skill_install.py`, lines 133-139 and 221-224 **Vulnerability Type**: Unsafe temporary directory and symlink race **Risk Level**: High ### Vulnerable Code ```python temp_dir = os.path.join("/tmp", "openclaw_skill_temp") os.makedirs(temp_dir, exist_ok=True) try: success, message = self.extract_zip(zip_path, temp_dir) if not success: return False, message ``` The extraction function writes archive members into this shared path: ```python with zipfile.ZipFile(zip_path, 'r') as zip_ref: zip_ref.extractall(extract_to) ``` Cleanup also operates on the predictable path: ```python finally: if os.path.exists(temp_dir): shutil.rmtree(temp_dir) ``` ### Technical Analysis Every invocation uses the fixed path `/tmp/openclaw_skill_temp`. The directory is created with `exist_ok=True`, so the installer neither requires a newly created directory nor verifies its ownership, permissions, contents, or file types. A local attacker can pre-create the directory before the victim runs the installer. It can contain stale attacker-controlled files or symlinks whose names match expected archive members. Archive extraction may follow pre-existing filesystem symlinks when opening destination files, allowing writes to be redirected outside the temporary directory under the victim process's privileges. Reusing the same directory also creates race conditions between concurrent installer processes. One process can modify or remove files while another process validates or copies them. Because existing contents are not cleared before extraction, stale files may become part of the installed skill even if they were not present in the selected archive. ### Attack Path 1. A local attacker creates `/tmp/openclaw_skill_temp` before the victim starts the installer. 2. The attacker makes the directory writable and adds a symlink whose name matches a ...[truncated 1273 chars]
Remediation
## Remediation Suggestions - Replace the fixed path with `tempfile.TemporaryDirectory()`: ```python import tempfile with tempfile.TemporaryDirectory(prefix="openclaw-skill-") as temp_dir: # Extract, validate, and install inside this unique directory. ``` - Ensure the temporary directory is newly created with restrictive permissions and is owned by the current user. - Never use `exist_ok=True` for a security-sensitive shared temporary workspace. - Before extraction, validate every ZIP member and its resolved destination. - Reject archive symlinks, device files, and other special file types. - Use `os.open()` protections such as `O_NOFOLLOW` where applicable when creating extracted files. - Ensure every resolved member path remains under the unique extraction directory. - Prevent concurrent processes from sharing extraction state. - Perform validation and copying from the same immutable staging tree.

T08 · Insecure Dependencies

Warning
Location
scripts/skill_install.py:91
Finding
Insufficient Package Validation Allows Untrusted or Tampered Skill Installation## Vulnerability Details **File Location**: `scripts/skill_install.py`, lines 91-108 and 146-188 **Vulnerability Type**: Insufficient package integrity and structure validation **Risk Level**: Medium ### Vulnerable Code ```python def validate_skill_structure(self, skill_path: str) -> Tuple[bool, str]: print(f"🔍 正在验证 skill 结构: {skill_path}") skill_md = os.path.join(skill_path, "SKILL.md") if not os.path.exists(skill_md): return False, "缺少 SKILL.md 文件" try: with open(skill_md, 'r', encoding='utf-8') as f: content = f.read() if not content.startswith('---'): return False, "SKILL.md 格式错误: 必须以 --- 开头" except Exception as e: return False, f"无法读取 SKILL.md: {e}" return True, "skill 结构有效" ``` After this minimal check, the complete extracted tree is installed: ```python success, message = self.validate_skill_structure(skill_source) if not success: return False, message shutil.copytree(skill_source, target_dir) ``` ### Technical Analysis The validator only verifies that `SKILL.md` exists and begins with three hyphens. It does not: - Parse or validate YAML frontmatter - Require or validate `_meta.json` - Verify consistency between metadata files - Authenticate that the archive originated from clawhub.ai - Verify a digital signature, checksum, or trusted publisher - Reject symlinks or special files - Inspect executable scripts or other package content - Enforce size, file-count, or decompression limits This behavior is weaker than the documented validation process, which states that both `SKILL.md` and `_meta.json` are validated. Any arbitrary ZIP containing a minimally formatted `SKILL.md` can pass validation. Restarting the Gateway after copying the package makes the untrusted skill available to OpenClaw. The installer itself does not directly execute scripts from the archive, but subsequent lo ...[truncated 1496 chars]
Remediation
## Remediation Suggestions - Parse `SKILL.md` frontmatter using a safe YAML parser and enforce a documented schema. - Require `_meta.json`, parse it with `json.load()`, and validate it against a strict JSON schema. - Verify that names, versions, identifiers, and publishers are consistent across metadata files. - Obtain trusted package checksums or signatures through an authenticated registry channel. - Verify the archive signature before extraction and installation. - Clearly distinguish trusted registry packages from arbitrary local archives. - Require explicit user approval and display a warning when package authenticity cannot be established. - Reject symlinks, hard links, device nodes, and unexpected special files. - Apply limits to archive size, expanded size, member count, path length, and compression ratio. - Inventory executable files and present them to the user before installation. - Validate the complete staging tree before copying it into the active skills directory.

T07 · Tool Hijacking and Spoofing

Note
Location
scripts/skill_install.py:203
Finding
Gateway Restart Uses an Unverified PATH Executable and Reports Failed Commands as Successful## Vulnerability Details **File Location**: `scripts/skill_install.py`, lines 203-210 **Vulnerability Type**: Tool resolution through ambient PATH and unchecked subprocess result **Risk Level**: Low ### Vulnerable Code ```python try: subprocess.run( ["openclaw", "daemon", "restart"], capture_output=True, text=True, timeout=30 ) print("✅ Gateway 更新成功") except subprocess.TimeoutExpired: print("⚠️ Gateway 重启超时,请手动执行: openclaw daemon restart") except Exception as e: print(f"⚠️ Gateway 更新失败: {e}") print(f"💡 请手动执行: openclaw daemon restart") ``` ### Technical Analysis The installer invokes `openclaw` by name, causing executable resolution through the process's ambient `PATH`. It does not use a verified binary associated with the OpenClaw installation detected earlier. In an environment where an attacker can influence `PATH`, a spoofed executable named `openclaw` may be selected. In addition, `subprocess.run()` is called without `check=True`, and its `returncode` is never inspected. Any command that starts normally but exits with a nonzero status is therefore reported as a successful Gateway update. Although argument-array invocation avoids shell metacharacter injection, it does not protect against executable path spoofing. ### Attack Path 1. An attacker places a malicious executable named `openclaw` in a directory. 2. That directory is placed before the legitimate OpenClaw binary directory in the victim's `PATH`. 3. The victim runs the skill installer. 4. During the restart step, `subprocess.run()` resolves the attacker-controlled executable. 5. The malicious executable runs with the victim's privileges. 6. If the executable exits normally with a nonzero status, the installer still prints a success message because the return code is ignored. A non-malicious failure follows a simpler path: the legitimate restart command returns nonzero, but t ...[truncated 768 chars]
Remediation
## Remediation Suggestions - Resolve and retain the verified OpenClaw executable path during installation discovery. - Invoke the verified absolute executable path rather than relying on ambient `PATH`. - Confirm that the selected executable is a regular file in the expected installation and is not located in a user-writable untrusted directory. - Use `check=True`: ```python result = subprocess.run( [verified_openclaw_binary, "daemon", "restart"], capture_output=True, text=True, timeout=30, check=True ) ``` - Alternatively, explicitly inspect `result.returncode` before printing a success message. - Handle `subprocess.CalledProcessError` and report failure without exposing sensitive command output. - Treat a failed Gateway restart as an installation warning or failure rather than unconditional success. - Avoid recommending elevated execution as a workaround.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Rogue AgentSelf-Modification, Session Persistence
Findings (14)

Tool Parameter Abuse

High
Category
Tool Misuse
Content
The skill is already installed. You can manually remove it if you want to reinstall:
```bash
rm -rf ~/.nvm/versions/node/*/lib/node_modules/openclaw/skills/skill-name
```

## License
Confidence
98% confidence
Finding
This command directly recommends force-deleting a path inside `~/.nvm/versions/node/*/lib/node_modules/openclaw/skills/skill-name`, and the wildcard increases the risk of unintended matches across Node versions. In documentation for an installer skill, this context makes the issue more dangerous because it encourages operators to perform privileged cleanup actions manually, potentially causing data loss or breaking installations.

Tool Parameter Abuse

High
Category
Tool Misuse
Content
The skill is already installed. You can manually remove it if you want to reinstall:
```bash
rm -rf ~/.nvm/versions/node/*/lib/node_modules/openclaw/skills/skill-name
```

## License
Confidence
98% confidence
Finding
This command directly recommends force-deleting a path inside `~/.nvm/versions/node/*/lib/node_modules/openclaw/skills/skill-name`, and the wildcard increases the risk of unintended matches across Node versions. In documentation for an installer skill, this context makes the issue more dangerous because it encourages operators to perform privileged cleanup actions manually, potentially causing data loss or breaking installations.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The documentation claims behaviors such as ZIP discovery, duplicate handling, and interactive selection that static analysis says are not actually implemented, while also omitting an implemented --list capability. This mismatch can mislead users and reviewers about what the skill really does, reducing informed consent and masking operational risk in an installer that modifies persistent skill directories and restarts Gateway.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
This markdown file explains that the skill extracts files into the OpenClaw installation and restarts the Gateway, both of which affect the user's local system state. The description presents these actions as routine steps but does not include any explicit warning or caution about modifying installed files or restarting a running service/process.

Session Persistence

Medium
Category
Rogue Agent
Content
- Python 3.6 or higher
- OpenClaw installed via npm or nvm
- Write permissions to OpenClaw skills directory

## Troubleshooting
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.

Lp3

Medium
Category
MCP Least Privilege
Confidence
88% confidence
Finding
The skill advertises executable behavior involving file reads/writes and shell commands, but it declares no explicit tool scope or permissions boundary. This makes the skill harder to review and increases the chance of over-privileged execution, especially because it installs files and restarts a service.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The example output switches to Chinese for status and result messages, which implies the skill may force a specific language in its user-facing interaction. The file does not state that the skill is Chinese-only, region-specific, or configurable for user language preference, so this appears to violate the language/locale policy.

Session Persistence

Medium
Category
Rogue Agent
Content
- Python 3.6 or higher
- OpenClaw installed (via npm or nvm)
- Sufficient permissions to write to OpenClaw skills directory

## Error Handling
Confidence
92% confidence
Finding
The skill's core function is to write into the OpenClaw skills directory, creating persistent changes that survive the session and become available to the platform after a Gateway restart. Because this is an installer for ZIP-delivered skills, inadequate trust validation of installed content can turn persistence into a durable compromise vector.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
```bash
# Fix permissions
chmod 755 ~/.nvm/versions/node/*/lib/node_modules/openclaw/skills
```

### Skill Not Appearing in OpenClaw
Confidence
80% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

File System Enumeration

Medium
Category
Data Exfiltration
Content
3. Verify skill structure:
   ```bash
   ls -la ~/.nvm/versions/node/*/lib/node_modules/openclaw/skills/your-skill/
   ```

## Changelog
Confidence
60% confidence
Finding
Code scans file system directories looking for sensitive files. This could be reconnaissance for credential theft.

Natural-Language Policy Violations

Medium
Confidence
97% confidence
Finding
This code file contains natural-language strings and docstrings entirely in Chinese for usage, status, prompts, and errors. The stated policy flags locale/language constraints when a skill forces a specific language without user opt-in, and no alternative language selection is provided anywhere in the file.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
# 方法1: 通过 which 命令
        try:
            result = subprocess.run(
                ["which", "openclaw"],
                capture_output=True,
                text=True,
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Context-Inappropriate Capability

Medium
Confidence
91% confidence
Finding
This installer is supposed to unpack and validate ZIP files, but it also restarts the OpenClaw daemon, giving it process-control behavior that can activate newly installed content immediately. In a skill-installation context, that makes malicious or unsafe skills more dangerous because installation and activation are coupled, reducing the opportunity for inspection or rollback before execution.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
# 11. 更新 Gateway
            print(f"\n🔄 正在更新 Gateway...")
            try:
                subprocess.run(
                    ["openclaw", "daemon", "restart"],
                    capture_output=True,
                    text=True,
Confidence
86% confidence
Finding
The installer executes `openclaw daemon restart` as an external process after copying untrusted skill contents into the skills directory. In the context of a ZIP installer for third-party skills, this expands capability from file installation to process control and can immediately activate a newly installed malicious skill without a separate review or explicit admin step; it also trusts PATH resolution for `openclaw`, which could run an attacker-controlled binary in a compromised environment.

Static analysis

No suspicious patterns detected.