Back to skill

Security audit

Clh Stage

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly matches its stated inventory-manager purpose, but its generated HTML report can execute malicious skill metadata and its update-check networking is under-scoped in the security notes.

Review before installing in sensitive environments. Use Markdown or JSON output instead of HTML when inventorying untrusted third-party skills, avoid --check-updates unless you are comfortable contacting git remotes recorded in local skill metadata, and use --push only when you intend to send the inventory to Feishu or Notion with the relevant environment credentials.

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 (1)

T09 · Insecure Skill Coding Practices

Warning
Location
exporter/html_exporter.py:94
Finding

Stored HTML and JavaScript Injection in Generated Skill Reports

Content
View full analysis

Vulnerability Details

File Location: exporter/html_exporter.py, lines 94–103 and 218–233
Vulnerability Type: Stored HTML/JavaScript injection
Risk Level: Medium

Vulnerable Code

python
item = {
    'name': skill['name'],
    'status': skill.get('status', 'unknown'),
    'version': skill.get('version', ''),
    'source': _source_short(skill),
    'emoji': skill.get('emoji') or '',
    'homepage': skill.get('homepage') or '',
    'description': skill.get('description', ''),
    'big_category': cat[0],
    'sub_category': cat[1],
}
javascript
if (c.key === 'name') {
    const label = (skill.emoji ? skill.emoji + ' ' : '') + skill.name;
    const inner = skill.homepage
        ? '<a href="' + skill.homepage + '" target="_blank" style="color:#0066cc;text-decoration:none">' + label + '</a>'
        : label;
    return '<td><span class="skill-name">' + inner + '</span></td>';
}
if (c.key === 'version') {
    return '<td>' + (skill.version ? '<span class="skill-version">v' + skill.version + '</span>' : '-') + '</td>';
}
if (c.key === 'category') {
    return '<td><span class="category-tag">' + skill.big_category + '</span></td>';
}
const v = skill[c.key];
return '<td>' + ((v === null || v === undefined || v === '') ? '-' : v) + '</td>';
javascript
data.forEach(skill => {
    const row = document.createElement('tr');
    row.innerHTML = COLS.map(c => cellHtml(c, skill)).join('');
    tbody.appendChild(row);
});

Technical Analysis

The HTML exporter includes skill metadata obtained from openclaw skills list --json, including the skill name, description, emoji, homepage, version, and source. Installed third-party skills can therefore influence these fields.

Although the metadata is serialized safely enough for placement inside the JavaScript source, the report subsequently concatenates the values into HTML strings. The resulting string is assigned to row.innerHTML without contextual HTML escaping.

Consequently, attack ...[truncated 2185 chars]

Remediation
View remediation

Remediation Suggestions

  1. Do not construct table cells using attacker-derived HTML strings. Create elements with DOM APIs and place metadata into textContent:
javascript
const td = document.createElement('td');
const span = document.createElement('span');
span.className = 'skill-name';
span.textContent = label;
td.appendChild(span);
  1. Create links through DOM properties rather than HTML concatenation. Parse and validate homepage URLs before assigning them:
javascript
function safeHomepage(value) {
    try {
        const url = new URL(value);
        return url.protocol === 'https:' || url.protocol === 'http:' ? url.href : '';
    } catch {
        return '';
    }
}
  1. Reject active URL schemes, including javascript:, data:, and vbscript:. Prefer an https:-only policy unless another scheme is required by the product.

  2. Avoid innerHTML for generated rows. Build each cell as a DOM node and append it to the row. If HTML rendering is genuinely required, apply a well-reviewed sanitizer with a restrictive element and attribute allowlist.

  3. Apply defense-in-depth browser restrictions through a Content Security Policy, for example by prohibiting inline event handlers and limiting outbound connections. This should supplement, not replace, contextual output encoding.

  4. Add regression tests using metadata containing:

    • <img src=x onerror=...>
    • Attribute-breaking quotes
    • Closing tags and nested elements
    • javascript: and data: homepage URLs
    • Script-closing sequences

The tests should verify that payloads are rendered as inert text and that unsafe homepage schemes do not produce clickable links.

Vulnerability Patterns
  • Rogue AgentSelf-Modification, Session Persistence
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (49)

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding

The supplied code does not implement the broad OpenClaw inventory tool described. Instead, it contains only unit tests for a status-classification helper. While such logic could be a supporting component of the larger described tool, this chunk by itself does not substantiate the declared primary purpose or most listed capabilities. There is no evidence here of CLI scanning, categorization, duplicate detection, report export, Feishu/Notion integration, usage statistics, dependency graph generation, snapshot diffing, batch enable/disable, or update checking. Therefore, the description does not accurately represent what this specific code chunk actually does.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The supplied code does not implement or exercise the declared inventory/reporting capabilities. Instead, it only tests translation-related helper functions (translate_description, extract_untranslated). That behavior is ancillary at best and, in this isolated chunk, materially different from the declared primary purpose of an OpenClaw skill inventory tool. There is no evidence here of CLI scanning, reporting, external integrations, dependency analysis, snapshot handling, batch operations, or update checks.

Content

No source excerpt is available for this finding.

Lp1

High
Category
MCP Least Privilege
Confidence
75% confidence
Finding

The skill uses 'file_read' capability that is not listed in its permissions. This may indicate deceptive intent or missing permission declarations.

Content

No source excerpt is available for this finding.

Lp1

High
Category
MCP Least Privilege
Confidence
89% confidence
Finding

This module performs file writes to the OpenClaw configuration and creates backup files, which is a sensitive capability because it changes skill enablement state and persists changes on disk. If the skill framework does not explicitly declare and gate file-write permissions, users and host policy engines may be unable to accurately assess or restrict this behavior, increasing the chance of unauthorized or unexpected configuration modification.

Content

No source excerpt is available for this finding.

Lp1

High
Category
MCP Least Privilege
Confidence
90% confidence
Finding

The module makes outbound network requests via git ls-remote, but the finding indicates this capability is not declared in permissions. Undeclared network behavior is dangerous because it can surprise users, bypass expected trust boundaries, and magnify the impact of malicious metadata by allowing silent connections to external systems during a scan/report action.

Content

No source excerpt is available for this finding.

Lp1

High
Category
MCP Least Privilege
Confidence
75% confidence
Finding

The skill uses 'env' capability that is not listed in its permissions. This may indicate deceptive intent or missing permission declarations.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

Line L008 states metadata.language: zh-CN as the skill's output language. This is a natural-language locale restriction presented without any indication that users can choose another language or opt in, which matches the policy's language/locale violation criterion.

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 30)May include surrounding context.

md
- 🌍 **Description Sinicization**: built-in glossary replaces common English phrases, with an untranslated-words report
- 📊 **Usage stats**: dual data source — `skill_usage` table / curator (`--usage`)
- 🔗 **Dependency graph**: missing-dependency edges as mermaid (md) and SVG bipartite chart (html) (`--deps`)
- 🔁 **Batch enable/disable**: writes `skills.entries.<name>.enabled` with auto-backup and post-write verification
- ⬆️ **Update check**: git track vs remote HEAD, ClawHub track hint — report only, never auto-upgrades (`--check-updates`)
- 📤 **Remote push**: opt-in push of exported Markdown to Feishu Docs / Notion pages (`--push`; prints a privacy notice before sending)

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 170)May include surrounding context.

Custom category overlay

Create ~/.openclaw/skill_categories.json (takes precedence over the built-in map):

json
{

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The documentation explicitly says the skill is Chinese-language by design via metadata.language: zh-CN. The policy allows locale constraints only when users are given a language choice or when the constraint is clearly documented and justified as region-specific; this statement provides no user opt-in and no region-specific justification.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The policy forbids forcing a specific language or locale unless the skill offers a choice or the constraint is clearly documented and justified as region-specific. Line L223 says the output and classification labels are Chinese and targets Chinese users with metadata.language: zh-CN, but this README does not present it as an opt-in choice or necessary compliance constraint.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The description states that CLI output and category labels are 'Chinese by design,' which imposes a specific language/locale behavior. The policy for this audit says to flag language constraints unless the skill offers user choice or clearly documents a justified region-specific constraint; this description presents a hard constraint rather than an opt-in.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The security/language note says the outputs and category labels are in Chinese and aimed at Chinese users. This is a natural-language locale restriction, and the file does not present it as an opt-in choice or a clearly necessary compliance constraint.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

This code contains natural-language descriptions entirely in Chinese, including the module docstring and duplicate-reason labels, with no indication that the skill is intentionally region-specific or that another language is supported. Under the policy, forcing a specific language without user opt-in or documented justification is a locale/language policy violation.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

This Python file contains natural-language documentation and operational messages exclusively in Chinese, including the module docstring and comments describing behavior. Under the stated policy, forcing a specific language without user opt-in can be a locale-policy violation, and this file does not document that the skill is intentionally Chinese-only or region-specific.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
86% confidence
Finding

The logger warning emitted on overlay-load failure uses only Chinese text, which is a user-visible operational message in many deployments. Because the file provides no locale selection or documented regional constraint, this can violate the language/locale policy for all file types.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

This code uses Chinese-only natural-language strings in the module docstring and operational log/error messages, which imposes a specific language on users and operators without any opt-in or fallback. The policy explicitly calls for flagging language or locale constraints when the skill does not offer a language choice.

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 · data/openclaw.py (reported line 48)May include surrounding context.

python
for attempt in range(max_retries):
        try:
            result = subprocess.run(
                ['openclaw', 'skills', 'list', '--json'],
                capture_output=True,
                text=True,

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The top-level docstring is entirely in Chinese and provides the skill's user-facing description and safety notes only in that language. This imposes a specific language/locale without any opt-in, alternative language, or documented regional justification, which matches the language-policy violation category.

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 · data/toggle.py (reported line 84)May include surrounding context.

python
def subprocess_run_list_json():
    import subprocess
    out = subprocess.run(['openclaw', 'skills', 'list', '--json'],
                         capture_output=True, text=True, encoding='utf-8', timeout=120)
    if out.returncode != 0:
        raise RuntimeError(out.stderr.strip()[:200])

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The top-level docstring is written as a hard language choice for the skill's behavior, indicating the skill reports status in Chinese. The policy requires avoiding forced language or locale constraints unless the user is offered a choice or the restriction is clearly justified as region-specific, which is not present here.

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 · data/updates.py (reported line 40)May include surrounding context.

python
if not os.path.isdir(os.path.join(skill_dir, '.git')):
        return ''
    try:
        out = subprocess.run(['git', '-C', skill_dir, 'rev-parse', 'HEAD'],
                             capture_output=True, text=True, timeout=30)
        if out.returncode == 0:
            return out.stdout.strip()

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
84% confidence
Finding

This code performs a network-capable git ls-remote against a URL taken from local installation metadata, which can be attacker-controlled if a malicious skill or tampered source-origin.json is present. Although _safe_git_url blocks plain HTTP and some private IPv4 ranges, it does not comprehensively prevent access to internal resources because it allows arbitrary public HTTPS endpoints and does not account for DNS rebinding, IPv6 loopback/private targets, or other hostname resolution edge cases, creating an SSRF-style outbound request primitive.

Content

Scanner excerpt · data/updates.py (reported line 52)May include surrounding context.

python
def _remote_head(url: str) -> str:
    """远端默认分支 HEAD,失败返回空串"""
    try:
        out = subprocess.run(['git', 'ls-remote', url, 'HEAD'],
                             capture_output=True, text=True, timeout=60)
        if out.returncode == 0 and out.stdout:
            return out.stdout.split()[0]

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The file's natural-language documentation is entirely in Chinese, and the operational log/user-facing strings are also Chinese-only. Under the stated policy, forcing a specific language without opt-in or a documented regional justification is a natural-language policy violation.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The warning message emitted on database read failure is Chinese-only, which enforces a specific language for operational feedback. The file does not indicate that users can choose language or that the skill is intentionally limited to a Chinese locale.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.