Back to skill

Security audit

OpenClaw A2UI

Security checks for vulnerabilities and agentic risk

Overview

This skill is meant to render rich chat cards, but it modifies OpenClaw UI files and exposes the browser to risky HTML rendering behavior.

Install only if you trust this publisher and are comfortable with an OpenClaw plugin that edits the control UI, registers persistent plugin state, and renders raw HTML from chat replies. Before production use, the sanitizer, manifest authorization/CORS behavior, and cross-skill allowlist design should be tightened or independently reviewed.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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
Findings (3)

T09 · Insecure Skill Coding Practices

Error
Location
assets/skill-ui-bridge.js:13
Finding
Unsafe HTML Sanitization Permits Dangerous URLs and Unrestricted Inline CSS<![CDATA[ ## Vulnerability Details **File Location**: `assets/skill-ui-bridge.js:13-33, 149-156`; `ui-config.json:5-28` **Vulnerability Type**: Improper HTML and URL sanitization **Risk Level**: High ### Vulnerable Code ```javascript function sanitize(html) { var doc = new DOMParser().parseFromString('<body>' + html + '</body>', 'text/html'); cleanNode(doc.body); return doc.body.innerHTML; } function cleanNode(node) { var i = node.childNodes.length; while (i--) { var child = node.childNodes[i]; if (child.nodeType === 3) continue; if (child.nodeType !== 1) { node.removeChild(child); continue; } var tag = child.tagName.toLowerCase(); if (!allowedTags.has(tag)) { var frag = document.createDocumentFragment(); while (child.firstChild) frag.appendChild(child.firstChild); node.replaceChild(frag, child); continue; } var attrs = Array.from(child.attributes); for (var a = 0; a < attrs.length; a++) { if (!allowedAttrs.has(attrs[a].name)) child.removeAttribute(attrs[a].name); } cleanNode(child); } } ``` ```javascript var wrapper = document.createElement('div'); wrapper.setAttribute(DONE_ATTR, '1'); wrapper.style.cssText = 'display:block;margin:4px 0;opacity:0;transition:opacity 0.18s ease'; wrapper.innerHTML = sanitize(text.slice(aIdx)); el.style.display = ''; el.innerHTML = ''; el.appendChild(wrapper); ``` The configured attributes include URL-bearing and unrestricted styling attributes: ```json "allowedAttrs": [ "class", "style", "id", "href", "target", "rel", "src", "alt", "width", "height", "data-card", "data-type", "data-action", "data-value", "open", "title", "viewBox", "fill", "stroke", "stroke-width", "stroke-linecap", "stroke-linejoin", "d", "cx", "cy", "r", "x", "y", "x1", "y1", "x2", "y2", "points", "xmlns" ] ``` ### Technical Analysis The custom sanitizer checks only whether an element name and attribute name appear in global allowlists. It does not inspe ...[truncated 2371 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace the custom sanitizer with a maintained sanitizer such as DOMPurify, configured with a fixed, reviewed policy. 2. Enforce an immutable protocol policy for URL-bearing attributes: - Allow only explicitly required schemes, normally `https:` and carefully scoped relative URLs. - Reject `javascript:`, `data:`, `vbscript:`, `file:`, and unknown schemes. - Normalize URLs before validation to prevent encoding and whitespace bypasses. 3. Restrict remote resource origins or proxy resources through a trusted backend. 4. Remove arbitrary `style` support where possible. Use predefined CSS classes instead. 5. If inline styles are essential, parse CSS and allow only a narrow set of non-network, non-positioning properties. 6. Add regression tests for encoded `javascript:` URLs, mixed-case schemes, control characters, remote image tracking, CSS `url()` values, and deceptive fixed-position overlays. 7. Preserve `rel="noopener noreferrer"` for links that open new windows and consider blocking untrusted `target` values. ]]>

T05 · Unauthorized Access and Privilege Escalation

Error
Location
assets/skill-ui-bridge-plugin.js:168
Finding
Global Cross-Skill Allowlist Union Lets One Skill Weaken HTML Security for All Messages<![CDATA[ ## Vulnerability Details **File Location**: `assets/skill-ui-bridge-plugin.js:168-187`; `assets/skill-ui-bridge.js:241-258` **Vulnerability Type**: Cross-Skill trust-boundary violation and sanitizer policy escalation **Risk Level**: High ### Vulnerable Code The gateway reads every Skill-provided UI configuration without enforcing a maximum security policy: ```javascript function buildManifest(skillsDir, logger) { const manifest = { skills: [], timestamp: Date.now() }; if (!skillsDir || !fs.existsSync(skillsDir)) return manifest; try { const entries = fs.readdirSync(skillsDir, { withFileTypes: true }); for (const entry of entries) { if (!entry.isDirectory()) continue; if (entry.name.startsWith(".") || entry.name.includes("..")) continue; const configPath = path.join(skillsDir, entry.name, "ui-config.json"); if (!fs.existsSync(configPath)) continue; try { const config = JSON.parse(fs.readFileSync(configPath, "utf8")); manifest.skills.push({ name: entry.name, config }); } catch (err) { logger?.warn(`${PLUGIN_ID}: bad ui-config.json in '${entry.name}': ${err?.message}`); } } } catch (err) { logger?.warn(`${PLUGIN_ID}: cannot read skillsDir: ${err?.message}`); } return manifest; } ``` The browser then merges every Skill's requested tags and attributes into one global policy: ```javascript async function boot() { try { var m; if (window.__skillUiManifest) { m = window.__skillUiManifest; } else { var authHeader = getAuthHeader(); var fetchOpts = authHeader ? { headers: { 'Authorization': authHeader } } : {}; var r = await fetch('/plugins/skill-ui/manifest', fetchOpts); if (!r.ok) { console.warn('[skill-ui-bridge] manifest', r.status); return; } m = await r.json(); } (m.skills || []).forEach(function (s) { var c = s.config || {}; ((c.dompurify && c.dompurify.allowedTags) || []).forEach(func ...[truncated 2803 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Define a hard-coded maximum set of safe tags and attributes in the bridge. 2. Treat Skill configuration as a request to reduce that policy, never as authority to expand it. 3. Permanently reject: - Attributes beginning with `on`. - Active tags such as `script`, `iframe`, `object`, `embed`, `link`, and `meta`. - Dangerous URL schemes and executable SVG features. 4. Apply a separate sanitizer policy per Skill rather than creating a global union. 5. Cryptographically identify or explicitly trust Skills permitted to request advanced rendering capabilities. 6. Validate each `ui-config.json` against a strict schema on the gateway before including it in the manifest. 7. Log and reject unsafe policy entries instead of silently accepting them. 8. Add tests in which one Skill attempts to enable `onerror`, `onclick`, `script`, SVG animation, unsafe URLs, and other active content. 9. Consider sandboxing rendered cards in a restricted iframe without same-origin access if rich third-party content must be supported. ]]>

T09 · Insecure Skill Coding Practices

Note
Location
assets/skill-ui-bridge-plugin.js:193
Finding
Debug Manifest Endpoint Is Unauthenticated and Exposed Through Wildcard CORS<![CDATA[ ## Vulnerability Details **File Location**: `assets/skill-ui-bridge-plugin.js:193-218` **Vulnerability Type**: Unauthenticated information disclosure **Risk Level**: Low ### Vulnerable Code ```javascript function createManifestHandler({ skillsDir, logger }) { return async (req, res) => { const pathname = new URL(req.url ?? "/", "http://localhost").pathname; if (pathname !== MANIFEST_PATH) return false; if (req.method === "OPTIONS") { res.writeHead(204, CORS_HEADERS); res.end(); return true; } const manifest = buildManifest(skillsDir, logger); const body = JSON.stringify(manifest); res.writeHead(200, { "Content-Type": "application/json; charset=utf-8", "Content-Length": Buffer.byteLength(body), ...CORS_HEADERS, }); res.end(body); return true; }; } const CORS_HEADERS = { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "GET, OPTIONS", "Access-Control-Allow-Headers": "Content-Type", }; ``` ### Technical Analysis The source comment describes this as an authenticated debugging route, but the handler performs no authentication or authorization check. It also responds with `Access-Control-Allow-Origin: *`, allowing scripts from any web origin to read the response when the gateway is reachable from the victim's browser. The endpoint returns a manifest generated from every discovered `workspace/skills/*/ui-config.json`. This exposes installed Skill names and their UI configuration. It also handles methods other than `GET` unless they are rejected elsewhere by the hosting framework. ### Attack Path 1. The OpenClaw gateway is reachable from a victim's browser, such as through localhost, a private network, or a publicly accessible deployment. 2. The victim visits an attacker-controlled website. 3. Attacker JavaScript sends a cross-origin request to `/plugins/skill-ui/manifest` on the gateway. 4. The handler returns the manifest without checking authen ...[truncated 790 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove the debugging endpoint from production builds if it is not required. 2. Require the gateway's standard authenticated session or bearer-token validation before returning the manifest. 3. Return `401 Unauthorized` or `403 Forbidden` when authorization fails. 4. Replace wildcard CORS with an explicit allowlist containing only the trusted control UI origin. 5. Restrict the route to `GET` and `OPTIONS`; return `405 Method Not Allowed` for other methods. 6. Return only the minimum fields required by the browser instead of serializing complete Skill configurations. 7. Add `Vary: Origin` when dynamically selecting allowed origins. 8. Add tests confirming that unauthenticated and untrusted cross-origin requests cannot read the endpoint. ]]>
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (34)

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding
The same skill documentation also includes uninstall and cleanup operations that modify configuration and optionally delete plugin files, which are administrative actions unrelated to ordinary reply formatting. Bundling these behaviors under a UI skill increases the chance of accidental execution of sensitive maintenance tasks under misleading expectations.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
The same skill documentation also includes uninstall and cleanup operations that modify configuration and optionally delete plugin files, which are administrative actions unrelated to ordinary reply formatting. Bundling these behaviors under a UI skill increases the chance of accidental execution of sensitive maintenance tasks under misleading expectations.

Hidden Instructions

High
Category
Prompt Injection
Content
>
> ✅ 正确写法(单卡片模式):
> <div class="a2ui">
>   <!-- 标题:AI Agent 最新趋势 -->
>   <!-- 数据内容 -->
>   <!-- 数据来源说明 -->
>   <!-- 截止时间戳 -->
Confidence
70% confidence
Finding
Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.

Hidden Instructions

High
Category
Prompt Injection
Content
>
> ✅ 正确写法(单卡片模式):
> <div class="a2ui">
>   <!-- 标题:AI Agent 最新趋势 -->
>   <!-- 数据内容 -->
>   <!-- 数据来源说明 -->
>   <!-- 截止时间戳 -->
Confidence
70% confidence
Finding
Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.

Hidden Instructions

High
Category
Prompt Injection
Content
<span style="font-size:16px;flex-shrink:0">ℹ️</span>
  <div><div style="font-size:13px;font-weight:600;color:#1e40af;margin-bottom:2px">【标题】</div><div style="font-size:13px;color:#1e3a8a">【内容】</div></div>
</div>
<!-- success: bg=#f0fdf4 border=#22c55e title-color=#15803d text-color=#14532d emoji=✅ -->
<!-- warning: bg=#fffbeb border=#f59e0b title-color=#b45309 text-color=#92400e emoji=⚠️ -->
<!-- error:   bg=#fef2f2 border=#ef4444 title-color=#b91c1c text-color=#991b1b emoji=❌ -->
```
Confidence
70% confidence
Finding
Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.

Hidden Instructions

High
Category
Prompt Injection
Content
<div style="font-size:13px;color:#6b7280">【步骤说明】</div>
      </div>
    </div>
    <!-- 最后一步(绿色圆圈,无连接线) -->
    <div style="display:flex;gap:14px">
      <div style="display:flex;flex-direction:column;align-items:center">
        <div style="width:28px;height:28px;border-radius:50%;background:#22c55e;color:#fff;font-size:13px;font-weight:700;display:flex;align-items:center;justify-content:center;flex-shrink:0">N</div>
Confidence
70% confidence
Finding
Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.

Hidden Instructions

High
Category
Prompt Injection
Content
<div style="font-size:13px;color:#6b7280">【步骤说明】</div>
      </div>
    </div>
    <!-- 最后一步(绿色圆圈,无连接线) -->
    <div style="display:flex;gap:14px">
      <div style="display:flex;flex-direction:column;align-items:center">
        <div style="width:28px;height:28px;border-radius:50%;background:#22c55e;color:#fff;font-size:13px;font-weight:700;display:flex;align-items:center;justify-content:center;flex-shrink:0">N</div>
Confidence
70% confidence
Finding
Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.

Hidden Instructions

High
Category
Prompt Injection
Content
<div style="padding:16px 20px">
    <!-- 使用 flex/grid 布局嵌入所有子内容 -->
    <div style="display:flex;flex-direction:column;gap:14px">
      <!-- 国际焦点模块 -->
      <div style="background:#f8fafc;border-radius:8px;padding:12px">
        <div style="font-size:13px;font-weight:600;color:#5865f2;margin-bottom:6px">📰 国际焦点</div>
        <div style="font-weight:600;color:#1a1a2e;margin-bottom:4px;font-size:14px">中东冲突升级:以军击毙伊朗高级官员,霍尔木兹海峡受阻影响全球能源</div>
Confidence
70% confidence
Finding
Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.

Hidden Instructions

High
Category
Prompt Injection
Content
## alert-card(提示/警告卡片)

```html
<!-- 成功 -->
<div style="background:#f0fdf4;border:1px solid #86efac;border-radius:12px;padding:16px;max-width:600px;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;margin:8px 0;display:flex;gap:12px;align-items:flex-start">
  <svg style="flex-shrink:0;margin-top:1px" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#22c55e" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><polyline points="9 12 11 14 15 10"/></svg>
  <div>
Confidence
70% confidence
Finding
Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.

Hidden Instructions

High
Category
Prompt Injection
Content
</div>
</div>

<!-- 错误 -->
<div style="background:#fef2f2;border:1px solid #fca5a5;border-radius:12px;padding:16px;max-width:600px;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;margin:8px 0;display:flex;gap:12px;align-items:flex-start">
  <svg style="flex-shrink:0;margin-top:1px" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#ef4444" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/></svg>
  <div>
Confidence
70% confidence
Finding
Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.

Lp3

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding
The skill instructs filesystem discovery, config inspection, and installation of a browser/plugin bridge, which implies environment and local file access capabilities, yet it declares no tool or permission scope. This weakens reviewability and least-privilege controls, making it easier for a seemingly presentational skill to perform host-level setup actions without explicit authorization boundaries.

Natural-Language Policy Violations

Medium
Confidence
90% confidence
Finding
The skill description and operating instructions are written entirely in Chinese and impose behavior defaults without offering a language or locale choice. This can violate language/locale policy expectations when users have not opted into Chinese or the skill is not explicitly documented as China-specific.

Vague Triggers

Medium
Confidence
95% confidence
Finding
The description says the skill is 'default enabled' and should apply whenever content is structured, covering very common outputs such as lists, steps, summaries, status, and code. This broad natural-language trigger lacks clear boundaries for when the skill should not activate beyond plain chat, creating a high chance of unintended invocation during ordinary responses.

Context-Inappropriate Capability

Medium
Confidence
94% confidence
Finding
The documentation contains host-level installation, config editing, extension deployment, index.html injection, and restart procedures, all of which exceed the expected scope of a message-formatting skill. Even if intended for setup, these instructions normalize privileged system modification from within a skill context and can lead to unauthorized or unsafe changes on the host.

Context-Inappropriate Capability

Medium
Confidence
91% confidence
Finding
The skill directs broad filesystem searches across /etc, $HOME, /usr/local, and /opt to locate configs and skill paths. For a presentation skill, this is unjustified reconnaissance behavior that can expose sensitive environment layout and encourages overbroad file access when narrower, user-supplied paths would suffice.

Description-Behavior Mismatch

Medium
Confidence
93% confidence
Finding
The skill manifest describes adding personalized rich HTML rendering for OpenClaw webchat responses, but this plugin achieves that by directly modifying OpenClaw's installed control-ui index.html on startup and restoring it on shutdown. Persistent filesystem modification of platform assets is a materially broader operation than the manifest's user-facing description suggests.

Intent-Code Divergence

Medium
Confidence
97% confidence
Finding
The comment says the manifest route 'requires authentication', but the handler serves the manifest to any requester matching the path and method, with no authentication or authorization checks. This can expose installed skill names and UI configuration metadata to unauthorized parties, aiding reconnaissance and potentially leaking sensitive configuration details.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The endpoint returns a manifest of installed skills and their UI config, and the response includes permissive CORS headers with no access control. Even if the data seems non-secret, it reveals environment structure and installed capabilities, which can help an attacker profile the system and identify follow-on targets.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The plugin writes a generated JavaScript file and modifies control-ui's index.html on gateway startup, which is a safety-relevant file modification. Although the code logs these actions, there is no clear prior disclosure or warning to the user that the plugin will alter installed UI files and create backup artifacts.

Context-Inappropriate Capability

Medium
Confidence
95% confidence
Finding
The manifest describes this skill as adding personalized rich-HTML UI rendering for chat replies. In this file, the bridge reads bearer tokens from the URL fragment and multiple localStorage entries, then uses them in an Authorization header for a network request to /plugins/skill-ui/manifest. Accessing and reusing authentication material is not an obvious requirement for merely rendering HTML chat output in-page.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
This code extracts a bearer token from the URL fragment or localStorage and attaches it to a fetch request for the manifest. That is a sensitive credential access plus network transmission path, but the file provides no confirmation prompt, user-facing warning, or explanatory comment/docstring disclosing that behavior.

Natural-Language Policy Violations

Medium
Confidence
90% confidence
Finding
The file’s headings, descriptions, and example content are entirely in Chinese, presenting the skill reference as language-specific with no indication that users may choose another language. Under the policy for natural-language violations, a forced locale without opt-in can be a language policy issue unless clearly justified as region-specific.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The title and all instructional content are written in Chinese, and the template text throughout the file is presented only in Chinese with no indication that other languages are supported. Under the language/locale policy rule, forcing a specific language without user opt-in is a policy concern when no alternative or opt-in mechanism is documented.

Context-Inappropriate Capability

Medium
Confidence
86% confidence
Finding
The documented form template introduces interactive data collection and event submission capabilities that are not clearly necessary for a rich-reply rendering skill. In a UI-rendering context, such components can solicit sensitive user input or trigger downstream actions without clear scope boundaries or consent expectations.

Description-Behavior Mismatch

Medium
Confidence
92% confidence
Finding
The file extends a reply-formatting skill into operational behaviors outside simple rendering by instructing the agent to write HTML into the workspace and then present it via a local canvas endpoint. That broadens the skill’s capability surface from formatting to filesystem modification and local content serving, which can become dangerous if user-controlled content is embedded into the generated HTML.

Static analysis

No suspicious patterns detected.