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

Content
View full analysis

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
View 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

Content
View full analysis

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
View 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

Content
View full analysis

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
View 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

Content
View full analysis

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
View 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
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.

Content

Scanner excerpt · README.md (reported line 84)May include surrounding context.

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

Tool Parameter Abuse

High
Category
Tool Misuse
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.

Content

Scanner excerpt · README.md (reported line 84)May include surrounding context.

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

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.

Content

No source excerpt is available for this finding.

Missing User Warnings

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

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 · README.md (reported line 65)May include surrounding context.

md
- Python 3.6 or higher
- OpenClaw installed via npm or nvm
- Write permissions to OpenClaw skills directory

## Troubleshooting

Undeclared Tool Scope

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.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

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

Content

No source excerpt is available for this finding.

Session Persistence

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

Content

Scanner excerpt · SKILL.md (reported line 175)May include surrounding context.

md
- Python 3.6 or higher
- OpenClaw installed (via npm or nvm)
- Sufficient permissions to write to OpenClaw skills directory

## Error Handling

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
80% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · SKILL.md (reported line 222)May include surrounding context.

bash
# Fix permissions
chmod 755 ~/.nvm/versions/node/*/lib/node_modules/openclaw/skills

Skill Not Appearing in OpenClaw

File System Enumeration

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Code scans file system directories looking for sensitive files. This could be reconnaissance for credential theft.

Content

Scanner excerpt · SKILL.md (reported line 241)May include surrounding context.

  1. Verify skill structure:
    bash
    ls -la ~/.nvm/versions/node/*/lib/node_modules/openclaw/skills/your-skill/
    

Changelog

Natural-Language Policy Violations

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

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

python
# 方法1: 通过 which 命令
        try:
            result = subprocess.run(
                ["which", "openclaw"],
                capture_output=True,
                text=True,

Context-Inappropriate Capability

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

Content

No source excerpt is available for this finding.

subprocess module call

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

Content

Scanner excerpt · scripts/skill_install.py (reported line 198)May include surrounding context.

python
# 11. 更新 Gateway
            print(f"\n🔄 正在更新 Gateway...")
            try:
                subprocess.run(
                    ["openclaw", "daemon", "restart"],
                    capture_output=True,
                    text=True,

Static analysis

No suspicious patterns detected.