Back to skill

Security audit

Openclaw Command Center

Security checks for vulnerabilities and agentic risk

Overview

This dashboard is purpose-aligned, but its default server and authentication behavior can expose sensitive OpenClaw session data and controls beyond the local user.

Install only if you are prepared to run it behind strict local or trusted-network controls. Before use, bind it explicitly to loopback, enable token or properly enforced proxy authentication, avoid exposing port 3333 to a LAN/VPN/public interface, and review whether persistent operator discovery from transcripts is acceptable for your workspace.

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

T05 · Unauthorized Access and Privilege Escalation

Error
Location
src/index.js:248
Finding
Dashboard Binds to All Network Interfaces Without Authentication by Default<![CDATA[ ## Vulnerability Details **File Location**: `src/config.js:207-211`, `src/config.js:235-238`, `src/index.js:248-269`, `src/index.js:291-621`, `src/index.js:630-632` **Vulnerability Type**: Missing authentication and incorrect network binding **Risk Level**: High ### Vulnerable Code ```js // src/config.js server: { port: parseInt(process.env.PORT || fileConfig.server?.port || "3333", 10), host: process.env.HOST || fileConfig.server?.host || "localhost", }, ``` ```js // src/config.js auth: { mode: process.env.DASHBOARD_AUTH_MODE || fileConfig.auth?.mode || "none", token: process.env.DASHBOARD_TOKEN || fileConfig.auth?.token, // ... }, ``` ```js // src/index.js const server = http.createServer((req, res) => { // CORS headers res.setHeader("Access-Control-Allow-Origin", "*"); const urlParts = req.url.split("?"); const pathname = urlParts[0]; const query = new URLSearchParams(urlParts[1] || ""); // Fast path for health check if (pathname === "/api/health") { res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify({ status: "ok", port: PORT, timestamp: new Date().toISOString() })); return; } // Auth check (unless public path) const isPublicPath = AUTH_CONFIG.publicPaths.some( (p) => pathname === p || pathname.startsWith(p + "/"), ); if (!isPublicPath && AUTH_CONFIG.mode !== "none") { const authResult = checkAuth(req, AUTH_CONFIG); ``` ```js // src/index.js server.listen(PORT, () => { const profile = process.env.OPENCLAW_PROFILE; console.log(`🦞 OpenClaw Command Center running at http://localhost:${PORT}`); ``` ### Technical Analysis The configuration declares a default host of `localhost`, but that value is never passed to `server.listen()`. Calling `server.listen(PORT)` without a host causes Node.js to listen on the unspecified address, generally `::` or `0.0.0.0`, rather than restricting the service to the loopback interface. At the same time, authentication d ...[truncated 2597 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Enforce the configured host when starting the server: ```js server.listen(PORT, CONFIG.server.host, () => { // ... }); ``` 2. Default to an explicit loopback address such as `127.0.0.1`, rather than relying on hostname resolution. 3. Refuse to start when authentication is disabled and the configured address is not loopback. 4. Require authentication for all sensitive APIs and require separate authorization for mutation and job-control routes. 5. Change `/api/action` to accept only authenticated `POST` requests. 6. Consider disabling job-control endpoints unless explicitly enabled. 7. Add automated tests that verify the default process is inaccessible through non-loopback interfaces. 8. Update startup logging so that it reports the address actually bound rather than always printing `localhost`. ]]>

T05 · Unauthorized Access and Privilege Escalation

Error
Location
src/auth.js:33
Finding
Proxy Authentication Modes Trust Spoofable Identity Headers<![CDATA[ ## Vulnerability Details **File Location**: `src/auth.js:33-78` **Vulnerability Type**: Authentication bypass through unverified proxy headers **Risk Level**: High when the service is directly reachable ### Vulnerable Code ```js if (mode === "tailscale") { const login = (req.headers[AUTH_HEADERS.tailscale.login] || "").toLowerCase(); const name = req.headers[AUTH_HEADERS.tailscale.name] || ""; const pic = req.headers[AUTH_HEADERS.tailscale.pic] || ""; if (!login) { return { authorized: false, reason: "Not accessed via Tailscale Serve" }; } const isAllowed = authConfig.allowedUsers.some((allowed) => { if (allowed === "*") return true; if (allowed === login) return true; if (allowed.startsWith("*@")) { const domain = allowed.slice(2); return login.endsWith("@" + domain); } return false; }); if (isAllowed) { return { authorized: true, user: { type: "tailscale", login, name, pic } }; } return { authorized: false, reason: `User ${login} not in allowlist`, user: { login } }; } if (mode === "cloudflare") { const email = (req.headers[AUTH_HEADERS.cloudflare.email] || "").toLowerCase(); if (!email) { return { authorized: false, reason: "Not accessed via Cloudflare Access" }; } const isAllowed = authConfig.allowedUsers.some((allowed) => { if (allowed === "*") return true; if (allowed === email) return true; if (allowed.startsWith("*@")) { const domain = allowed.slice(2); return email.endsWith("@" + domain); } return false; }); if (isAllowed) { return { authorized: true, user: { type: "cloudflare", email } }; } return { authorized: false, reason: `User ${email} not in allowlist`, user: { email } }; } ``` ### Technical Analysis The Tailscale and Cloudflare modes treat request headers as authenticated identity assertions. The code does not verify that the immediate peer is a trusted proxy and does not cryptographically validate a Cloudflare Acce ...[truncated 1798 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Bind proxy-authenticated deployments to `127.0.0.1`, a Unix-domain socket, or an isolated private interface reachable only by the trusted proxy. 2. Validate the immediate peer address against a strict trusted-proxy list before accepting identity headers. 3. Strip incoming identity headers at the outer proxy and set authoritative replacements. 4. For Cloudflare Access, validate the signed Access JWT, including issuer, audience, expiration, and signature. 5. Do not use wildcard user allowlists for administrative or transcript-bearing interfaces. 6. Fail closed if proxy verification cannot be completed. 7. Document firewall requirements and add integration tests proving that direct requests with forged headers are rejected. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
src/index.js:248
Finding
Wildcard CORS Permits Cross-Origin Reads of Sensitive Dashboard APIs<![CDATA[ ## Vulnerability Details **File Location**: `src/index.js:248-251` **Vulnerability Type**: Overly permissive cross-origin resource sharing **Risk Level**: Medium ### Vulnerable Code ```js const server = http.createServer((req, res) => { // CORS headers res.setHeader("Access-Control-Allow-Origin", "*"); const urlParts = req.url.split("?"); const pathname = urlParts[0]; const query = new URLSearchParams(urlParts[1] || ""); ``` ### Technical Analysis The server adds `Access-Control-Allow-Origin: *` to every response, including responses that contain session information and transcript excerpts. This allows JavaScript from arbitrary origins to read responses whenever the browser permits the request. In the default `none` authentication mode, no credential protection prevents such reads. A malicious website visited by a user may therefore query a locally running dashboard and transfer returned data to an attacker-controlled server. Browser Private Network Access enforcement may limit exploitation in some browser and deployment combinations, but it must not be relied on as the dashboard's access-control mechanism. The wildcard policy is unnecessary for the declared same-origin dashboard. The frontend uses relative URLs such as `/api/state`, `/api/session`, and `/api/events`. ### Attack Path 1. A victim runs the dashboard locally using its default unauthenticated configuration. 2. The victim visits an attacker-controlled website. 3. The attacker's JavaScript requests `http://localhost:3333/api/sessions` or `http://localhost:3333/api/state`. 4. Where browser private-network policy permits the request, the wildcard CORS response authorizes the malicious origin to read it. 5. The script extracts session keys and requests `/api/session?key=...`. 6. The script sends the collected data to the attacker's server. ### Impact Assessment The flaw can expose session metadata, conversation excerpts, user and channel identities, topics, cron informatio ...[truncated 295 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove the CORS header entirely for the default same-origin dashboard. 2. If cross-origin clients are required, use an explicit allowlist of complete trusted origins. 3. Validate the `Origin` header before returning sensitive data. 4. Reject `null` and unexpected origins. 5. Apply stricter controls to transcript-bearing and state-changing endpoints. 6. Add `Vary: Origin` when dynamically selecting an allowed origin. 7. Combine CORS hardening with mandatory authentication; CORS is not an authentication mechanism. ]]>

T09 · Insecure Skill Coding Practices

Note
Location
src/auth.js:82
Finding
Unescaped Proxy Identity Values Are Reflected into the Access-Denied HTML Page<![CDATA[ ## Vulnerability Details **File Location**: `src/auth.js:82-113` **Vulnerability Type**: Reflected HTML injection **Risk Level**: Low ### Vulnerable Code ```js function getUnauthorizedPage(reason, user, authConfig) { const userInfo = user ? `<p class="user-info">Detected: ${user.login || user.email || user.ip || "unknown"}</p>` : ""; return `<!DOCTYPE html> <html> <head> <title>Access Denied - Command Center</title> <style> * { box-sizing: border-box; margin: 0; padding: 0; } body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%); min-height: 100vh; display: flex; align-items: center; justify-content: center; color: #e8e8e8; } </style> </head> <body> <div class="container"> <div class="icon">🔐</div> <h1>Access Denied</h1> <div class="reason">${reason}</div> ${userInfo} ``` ### Technical Analysis The denial page interpolates `reason` and identity fields directly into an HTML document without output encoding. In the Tailscale and Cloudflare modes, those identity values originate from HTTP request headers. A direct client can therefore include HTML markup in a forged identity header. When the forged identity does not pass the allowlist, the value is included both in the rejection reason and in `userInfo`. Because the response content type is `text/html`, the browser parses the injected markup. The exploitability is constrained because the attacker generally controls the request producing the response. It becomes more consequential if a victim can be induced to navigate to a crafted request through a proxy or other request-smuggling mechanism. ### Attack Path 1. The dashboard runs in Tailscale or Cloudflare authentication mode. 2. An attacker sends a request containing a malicious id ...[truncated 734 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. HTML-encode all dynamic values before inserting them into an HTML response. 2. Prefer a static access-denied page that does not reflect identity-header contents. 3. Return structured JSON for API authentication failures rather than HTML. 4. Add a restrictive Content Security Policy, such as: ```http Content-Security-Policy: default-src 'none'; style-src 'unsafe-inline' ``` 5. Validate identity-header syntax and reject values containing unexpected characters. 6. Add tests with HTML metacharacters in login, email, IP, and reason values. ]]>
Vulnerability Patterns
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (138)

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
Parsing transcripts to identify human operators and storing persistent operator records is collection of identity-related metadata beyond what many users would expect from a dashboard. In context, this is sensitive because transcripts may contain personal data from Slack, Telegram, or Discord and could enable profiling or privacy violations if mishandled.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
Parsing transcripts to identify human operators and storing persistent operator records is collection of identity-related metadata beyond what many users would expect from a dashboard. In context, this is sensitive because transcripts may contain personal data from Slack, Telegram, or Discord and could enable profiling or privacy violations if mishandled.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
Parsing transcripts to identify human operators and storing persistent operator records is collection of identity-related metadata beyond what many users would expect from a dashboard. In context, this is sensitive because transcripts may contain personal data from Slack, Telegram, or Discord and could enable profiling or privacy violations if mishandled.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
Parsing transcripts to identify human operators and storing persistent operator records is collection of identity-related metadata beyond what many users would expect from a dashboard. In context, this is sensitive because transcripts may contain personal data from Slack, Telegram, or Discord and could enable profiling or privacy violations if mishandled.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
Parsing transcripts to identify human operators and storing persistent operator records is collection of identity-related metadata beyond what many users would expect from a dashboard. In context, this is sensitive because transcripts may contain personal data from Slack, Telegram, or Discord and could enable profiling or privacy violations if mishandled.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
Parsing transcripts to identify human operators and storing persistent operator records is collection of identity-related metadata beyond what many users would expect from a dashboard. In context, this is sensitive because transcripts may contain personal data from Slack, Telegram, or Discord and could enable profiling or privacy violations if mishandled.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
Parsing transcripts to identify human operators and storing persistent operator records is collection of identity-related metadata beyond what many users would expect from a dashboard. In context, this is sensitive because transcripts may contain personal data from Slack, Telegram, or Discord and could enable profiling or privacy violations if mishandled.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
Parsing transcripts to identify human operators and storing persistent operator records is collection of identity-related metadata beyond what many users would expect from a dashboard. In context, this is sensitive because transcripts may contain personal data from Slack, Telegram, or Discord and could enable profiling or privacy violations if mishandled.

Tp4

High
Category
MCP Tool Poisoning
Confidence
89% confidence
Finding
Parsing transcripts to identify human operators and storing persistent operator records is collection of identity-related metadata beyond what many users would expect from a dashboard. In context, this is sensitive because transcripts may contain personal data from Slack, Telegram, or Discord and could enable profiling or privacy violations if mishandled.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
Parsing transcripts to identify human operators and storing persistent operator records is collection of identity-related metadata beyond what many users would expect from a dashboard. In context, this is sensitive because transcripts may contain personal data from Slack, Telegram, or Discord and could enable profiling or privacy violations if mishandled.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
Parsing transcripts to identify human operators and storing persistent operator records is collection of identity-related metadata beyond what many users would expect from a dashboard. In context, this is sensitive because transcripts may contain personal data from Slack, Telegram, or Discord and could enable profiling or privacy violations if mishandled.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
Parsing transcripts to identify human operators and storing persistent operator records is collection of identity-related metadata beyond what many users would expect from a dashboard. In context, this is sensitive because transcripts may contain personal data from Slack, Telegram, or Discord and could enable profiling or privacy violations if mishandled.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
Parsing transcripts to identify human operators and storing persistent operator records is collection of identity-related metadata beyond what many users would expect from a dashboard. In context, this is sensitive because transcripts may contain personal data from Slack, Telegram, or Discord and could enable profiling or privacy violations if mishandled.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
Parsing transcripts to identify human operators and storing persistent operator records is collection of identity-related metadata beyond what many users would expect from a dashboard. In context, this is sensitive because transcripts may contain personal data from Slack, Telegram, or Discord and could enable profiling or privacy violations if mishandled.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
Parsing transcripts to identify human operators and storing persistent operator records is collection of identity-related metadata beyond what many users would expect from a dashboard. In context, this is sensitive because transcripts may contain personal data from Slack, Telegram, or Discord and could enable profiling or privacy violations if mishandled.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
Parsing transcripts to identify human operators and storing persistent operator records is collection of identity-related metadata beyond what many users would expect from a dashboard. In context, this is sensitive because transcripts may contain personal data from Slack, Telegram, or Discord and could enable profiling or privacy violations if mishandled.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
Parsing transcripts to identify human operators and storing persistent operator records is collection of identity-related metadata beyond what many users would expect from a dashboard. In context, this is sensitive because transcripts may contain personal data from Slack, Telegram, or Discord and could enable profiling or privacy violations if mishandled.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
Parsing transcripts to identify human operators and storing persistent operator records is collection of identity-related metadata beyond what many users would expect from a dashboard. In context, this is sensitive because transcripts may contain personal data from Slack, Telegram, or Discord and could enable profiling or privacy violations if mishandled.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
Parsing transcripts to identify human operators and storing persistent operator records is collection of identity-related metadata beyond what many users would expect from a dashboard. In context, this is sensitive because transcripts may contain personal data from Slack, Telegram, or Discord and could enable profiling or privacy violations if mishandled.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
Parsing transcripts to identify human operators and storing persistent operator records is collection of identity-related metadata beyond what many users would expect from a dashboard. In context, this is sensitive because transcripts may contain personal data from Slack, Telegram, or Discord and could enable profiling or privacy violations if mishandled.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
Parsing transcripts to identify human operators and storing persistent operator records is collection of identity-related metadata beyond what many users would expect from a dashboard. In context, this is sensitive because transcripts may contain personal data from Slack, Telegram, or Discord and could enable profiling or privacy violations if mishandled.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
Parsing transcripts to identify human operators and storing persistent operator records is collection of identity-related metadata beyond what many users would expect from a dashboard. In context, this is sensitive because transcripts may contain personal data from Slack, Telegram, or Discord and could enable profiling or privacy violations if mishandled.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
Parsing transcripts to identify human operators and storing persistent operator records is collection of identity-related metadata beyond what many users would expect from a dashboard. In context, this is sensitive because transcripts may contain personal data from Slack, Telegram, or Discord and could enable profiling or privacy violations if mishandled.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
Parsing transcripts to identify human operators and storing persistent operator records is collection of identity-related metadata beyond what many users would expect from a dashboard. In context, this is sensitive because transcripts may contain personal data from Slack, Telegram, or Discord and could enable profiling or privacy violations if mishandled.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
Parsing transcripts to identify human operators and storing persistent operator records is collection of identity-related metadata beyond what many users would expect from a dashboard. In context, this is sensitive because transcripts may contain personal data from Slack, Telegram, or Discord and could enable profiling or privacy violations if mishandled.

Static analysis

Detected: suspicious.dangerous_exec

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
lib/server.js:461

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
scripts/install-system-deps.sh:50

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
scripts/linear-sync.js:495

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
src/llm-usage.js:19

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
src/openclaw.js:46

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
tests/iostat-leak.test.js:16

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
tests/server.test.js:14