Back to skill

Security audit

OpenClaw Model Card

Security checks for vulnerabilities and agentic risk

Overview

The skill is mostly aligned with its model-card purpose, but its screenshot renderer can treat configuration text as active HTML, creating unexpected rendering and network-resource risk for untrusted configs.

Review before installing. Use text mode freely for trusted OpenClaw configs, but avoid --image on configs supplied by other people unless rendering is sandboxed with no network access. Treat generated images as potentially attacker-influenced if the source openclaw.json is not trusted, and do not rely on the current sanitization claim.

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
scripts/md2img.js:7
Finding
Active HTML Injection Through Untrusted Model Configuration During Image Rendering<![CDATA[ ## Vulnerability Details **File Location**: `scripts/show-model-config.py:121-138`, `scripts/md2img.js:7-11`, `scripts/md2img.js:30`, and `scripts/md2img.js:68-72` **Vulnerability Type**: Untrusted HTML injection and unsafe document rendering **Risk Level**: Medium ### Vulnerable Code Configuration-derived provider names, model IDs, and aliases are inserted into Markdown without escaping: ```python for pname, pinfo in config.get('models', {}).get('providers', {}).items(): models = pinfo.get('models', []) lines.append(f'### {pname} ({len(models)})') lines.append('| Model ID | Alias | Context | Type |') lines.append('| :--- | :--- | ---: | :---: |') for m in models: mid = m.get('id', '-') ctx = fmt_ctx(m.get('contextWindow', 0)) tag = '**Multimodal**' if 'image' in m.get('input', []) else 'Text' alias = '-' for k, v in defaults_models.items(): if k == f'{pname}/{mid}': alias = v.get('alias', '') or '-' break lines.append(f'| {mid} | `{alias}` | {ctx} | {tag} |') ``` The Markdown renderer explicitly permits raw HTML: ```javascript const md = require('markdown-it')({ html: true, linkify: true, typographer: true }).use(require('markdown-it-emoji').full); ``` The rendered HTML is embedded directly into the document: ```javascript const htmlBody = md.render(content); ``` The resulting document is passed to `wkhtmltoimage` without disabling JavaScript or external resource loading: ```javascript const run = spawnSync( 'wkhtmltoimage', ['--width', '660', '--disable-smart-width', tempHtml, outputPath], { encoding: 'utf-8' } ); ``` ### Technical Analysis The image workflow treats values from `openclaw.json` as trusted presentation data. Provider names, model IDs, and aliases are interpolated directly into Markdown, but Markdown and HTML metacharacters are not escaped. Because `markdown-it` is configured with `html: true`, raw HTM ...[truncated 2610 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Disable raw HTML in `markdown-it`: ```javascript const md = require('markdown-it')({ html: false, linkify: true, typographer: true }).use(require('markdown-it-emoji').full); ``` 2. Escape all configuration-derived values before inserting them into Markdown. This must cover provider names, model IDs, aliases, primary model references, and fallback values. Use a dedicated Markdown-escaping function rather than ad hoc replacements. 3. Apply an allowlist-based HTML sanitizer if HTML support is genuinely required. Remove scripts, event-handler attributes, frames, embedded objects, remote-resource elements, and unsafe URL schemes. 4. Harden the renderer with supported `wkhtmltoimage` options, including disabling JavaScript and local-file access. External network resource loading should also be blocked through renderer configuration, sandboxing, or network isolation. 5. Run image rendering in a restricted subprocess or container with: - No unnecessary network access. - A minimal filesystem view. - A dedicated unprivileged user. - Resource and execution time limits. 6. Validate configuration field types and impose reasonable length limits before generating output. 7. Add regression tests containing payloads in every displayed configuration field, including: - Raw `<script>` elements. - Remote `<img>` elements. - HTML event-handler attributes. - `iframe` and stylesheet references. - Markdown table delimiters and backticks. 8. Update `SKILL.md` so that its sanitization claim accurately reflects the implemented controls. ]]>
Vulnerability Patterns
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • 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 (5)

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The documented purpose is narrowly framed around rendering OpenClaw model inventory from openclaw.json, but the behavior described by analysis indicates the implementation can operate on arbitrary markdown and relies on an external renderer. This mismatch is dangerous because users and orchestration systems may grant trust and inputs based on the declared purpose, while the actual behavior expands into generic content rendering and shell-driven processing that could expose local data or be abused for unintended file handling.

Lp3

Medium
Category
MCP Least Privilege
Confidence
93% confidence
Finding
The skill instructs the agent to run local scripts, read configuration files, write output images, and invoke shell commands, but it declares no tool restrictions such as allowed-tools or permissions. That creates unnecessary authority for an agent invoking the skill and weakens policy enforcement, increasing the chance that a compromised or modified skill path could access files or execute commands beyond the intended scope.

Context-Inappropriate Capability

Medium
Confidence
91% confidence
Finding
The skill's stated purpose is to generate model inventory/model-card images from local data, but this helper achieves that by spawning the external `wkhtmltoimage` program. Subprocess execution is a broader capability than the manifest suggests and is not explicitly justified in the stated scope, even though it is used for rendering.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
with open(md_file, 'w', encoding='utf-8') as f:
            f.write(markdown)

        proc = subprocess.run(
            ['node', md2img, md_file, output_path],
            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
86% confidence
Finding
The manifest describes listing configured models, verifying default/fallback chains, and rendering a model-card screenshot from openclaw.json. While image rendering is in scope, invoking an external runtime via subprocess adds a general command-execution capability that is not justified by the stated purpose of a configuration inspection/rendering skill.

Static analysis

Detected: suspicious.dangerous_exec

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
scripts/md2img.js:22