Back to skill

Security audit

OpenClaw Usage Dashboard

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly matches its stated local dashboard purpose, but it needs review because private usage metadata can be exposed through unauthenticated dashboard APIs and there is a verified browser security flaw.

Review before installing, especially on shared or networked machines. Use only the default localhost binding, do not run it with --host 0.0.0.0 or another non-loopback address, and treat the dashboard as exposing private OpenClaw usage patterns, model choices, agent names, session timing, and system details.

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

T09 · Insecure Skill Coding Practices

Error
Location
dashboard.html:550
Finding
<![CDATA[Stored DOM XSS through dynamically generated inline event handlers]]><![CDATA[ ## Vulnerability Details **File Location**: `dashboard.html:340-342`, `dashboard.html:550-554` **Vulnerability Type**: Stored DOM-based cross-site scripting **Risk Level**: High ### Vulnerable Code ```javascript function esc(s) { return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;').replace(/'/g,'&#39;'); } ``` ```javascript legend.innerHTML = series.map(s => '<div class="legend-item ' + (hiddenSeries.has(s.id) ? 'hidden-series' : '') + '" onclick="toggleSeries(\'' + esc(s.id) + '\')" role="button" tabindex="0" onkeydown="if(event.key===\'Enter\'||event.key===\' \')toggleSeries(\'' + esc(s.id) + '\')">' + (s.dashed ? '<div class="legend-dashed"></div>' : '<div class="legend-color" style="background:' + esc(s.color) + '"></div>') + '<span>' + esc(s.label) + '</span></div>' ).join(''); ``` ### Technical Analysis The `esc()` function performs HTML entity encoding, but the encoded value is inserted into a JavaScript string inside the `onclick` and `onkeydown` HTML attributes. HTML escaping alone does not make a value safe for a nested JavaScript execution context. When the browser parses the generated HTML, it decodes `&#39;` back into a single quote before compiling the event-handler attribute as JavaScript. Consequently, an identifier containing JavaScript syntax can terminate the intended string and add arbitrary statements. The affected identifiers include model IDs obtained from parsed session logs and agent IDs derived from directory names under `~/.openclaw/agents`. The page's Content Security Policy permits `'unsafe-inline'`, so inline event handlers and injected inline JavaScript are allowed to execute. For example, an identifier shaped like the following can break out of the argument when its legend entry is activated: ```text ');alert(document.domain);// ``` After HTML entity decoding, the handler can become equivalent to: ```javascript toggleSeries('');alert(document.domain); ...[truncated 1639 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not generate inline event handlers with `innerHTML`. 2. Create legend elements using DOM APIs and attach handlers using `addEventListener()`: ```javascript legend.replaceChildren(); for (const s of series) { const item = document.createElement('div'); item.className = 'legend-item'; item.setAttribute('role', 'button'); item.tabIndex = 0; const activate = () => toggleSeries(s.id); item.addEventListener('click', activate); item.addEventListener('keydown', event => { if (event.key === 'Enter' || event.key === ' ') activate(); }); const label = document.createElement('span'); label.textContent = s.label; item.appendChild(label); legend.appendChild(item); } ``` 3. Use `textContent` for all identifiers and labels rather than interpolating them into markup. 4. Validate model and agent identifiers on the server against an explicit character and length policy where compatibility permits. 5. Remove the CSP allowance for `'unsafe-inline'`. Move scripts and styles into separate local files, or authorize fixed inline blocks with hashes or nonces. 6. Add regression tests using identifiers containing quotes, HTML entities, closing tags, and JavaScript fragments. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
server.js:35
Finding
<![CDATA[Unauthenticated sensitive telemetry exposure when bound to a non-loopback interface]]><![CDATA[ ## Vulnerability Details **File Location**: `server.js:35-39`, `server.js:555-591`, `server.js:612` **Vulnerability Type**: Missing authentication and unsafe network exposure **Risk Level**: Medium ### Vulnerable Code ```javascript const args = process.argv.slice(2); const portArg = args.indexOf('--port'); const hostArg = args.indexOf('--host'); const PORT = portArg !== -1 ? parseInt(args[portArg + 1]) : DEFAULT_PORT; const HOST = hostArg !== -1 ? args[hostArg + 1] : DEFAULT_HOST; ``` ```javascript // API: /api/stats?period=day if (pathname === '/api/stats') { const periodKey = url.searchParams.get('period') || 'day'; const periodMs = PERIOD_MAP[periodKey] || PERIOD_MAP.day; try { const data = computeDashboardData(periodMs); jsonResponse(res, data); } catch (e) { jsonResponse(res, { error: e.message }, 500); } return; } // API: /api/system if (pathname === '/api/system') { try { jsonResponse(res, getSystemInfo()); } catch (e) { jsonResponse(res, { error: e.message }, 500); } return; } // API: /api/config if (pathname === '/api/config') { try { jsonResponse(res, getConfig()); } catch (e) { jsonResponse(res, { error: e.message }, 500); } return; } ``` ```javascript server.listen(PORT, HOST, () => { ``` ### Technical Analysis The default host is safely restricted to `127.0.0.1`, but the command-line parser accepts an arbitrary `--host` value. A user or launching automation can therefore bind the service to `0.0.0.0`, a LAN address, or another non-loopback interface. The dashboard APIs do not implement authentication or authorization. If the binding is changed, any client capable of reaching the selected interface can directly request the API endpoints. The configured CORS response header does not mitigate this issue. CORS only controls whether browser scripts can read cross-origin responses; it does not prevent direct HTTP clients, command-line tools, malware, reverse proxies, or same-orig ...[truncated 1563 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Enforce loopback-only operation unless remote access is an explicitly supported requirement: ```javascript const ALLOWED_HOSTS = new Set(['127.0.0.1', '::1', 'localhost']); if (!ALLOWED_HOSTS.has(HOST)) { console.error('Refusing to bind the dashboard to a non-loopback interface.'); process.exit(1); } ``` 2. Prefer removing the arbitrary `--host` option entirely because remote operation is not required by the declared dashboard functionality. 3. If remote access is necessary, place it behind authenticated TLS or implement strong random bearer-token authentication for every dashboard and API request. 4. Require an explicit, prominently documented insecure or remote-access flag rather than silently accepting any host. 5. Validate `Host` and `Origin` headers as defense in depth, while recognizing that these checks are not substitutes for authentication. 6. Add security documentation explaining exactly what metadata the endpoints expose. 7. Add automated tests confirming that non-loopback binding is rejected in the default mode. ]]>
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (5)

Hidden Instructions

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

    <!-- Timeline Hero -->
    <div class="card" id="timelineCard">
      <div class="card-header">
        <span>📈</span>
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
91% confidence
Finding
The skill advertises execution of a local Node.js server and static analysis detected shell, environment, and network capabilities, but the manifest does not declare any tool scope or permissions boundary. That creates an authorization ambiguity where an agent may invoke code that reads local logs and exposes a localhost service without an explicit user-visible consent model, increasing the chance of unintended data exposure or over-broad execution.

Vague Triggers

Medium
Confidence
93% confidence
Finding
The trigger phrases are very broad and map to common requests like "system health," "how many requests," and "token usage," which can cause the skill to activate in contexts the user did not intend. Because this skill reads local session logs and launches a dashboard, over-triggering can lead to unexpected access to sensitive local usage metadata or unexpected service exposure on localhost.

Missing User Warnings

Low
Confidence
88% confidence
Finding
The description says data stays local, but it does not clearly warn users that the skill reads potentially sensitive session logs from disk and exposes the results through a browser-accessible localhost dashboard. Even when bound only to localhost, this can surprise users and increase privacy risk, especially on shared machines or in environments where local web access is monitored or proxied.

Description-Behavior Mismatch

Low
Confidence
91% confidence
Finding
The dashboard calls /api/config and displays primary/fallback model information, which goes beyond strictly necessary usage-stat rendering and exposes additional local configuration metadata. While this is not remote code execution or data exfiltration by itself, it broadens the data surface and can reveal sensitive environment details to anyone with access to the dashboard.

Static analysis

Detected: suspicious.dangerous_exec

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
server.js:398