Back to skill

Security audit

OpenClaw Gateway Resilience Guard

Security checks for vulnerabilities and agentic risk

Overview

This appears to be a real OpenClaw watchdog, but it installs persistent local services and a dashboard whose token and command handling create review-worthy local command-execution and data-exposure risk.

Install only if you are comfortable with a persistent user-level watchdog that reads OpenClaw diagnostics/logs and can restart Gateway. Avoid custom command settings, disable dashboard actions where possible, keep the dashboard bound to 127.0.0.1, protect the config/state directories, and review exported diagnostics before sharing because they may contain local paths, operational details, or provider metadata.

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

T09 · Insecure Skill Coding Practices

Error
Location
dashboard/server.py:420
Finding
Dashboard Action Token Disclosed to Unauthenticated Page Requesters<![CDATA[ ## Vulnerability Details **File Location**: `dashboard/server.py:420-423` **Vulnerability Type**: Authentication secret disclosure and action authorization bypass **Risk Level**: High ### Vulnerable Code ```python def send_static(self, path: Path, content_type: str) -> None: if not path.exists(): self.send_error(HTTPStatus.NOT_FOUND) return body = path.read_bytes() if path.name == "index.html": injection = f"<script>window.WATCHDOG_ACTION_TOKEN = {json.dumps(ACTION_TOKEN if ACTIONS_ENABLED else '')};</script>" body = body.replace(b"</head>", injection.encode("utf-8") + b"</head>") self.send_response(200) self.send_header("Content-Type", content_type) self.send_header("Cache-Control", "no-store") self.send_header("Content-Length", str(len(body))) self.end_headers() self.wfile.write(body) ``` The token protects the following sensitive actions: ```python def verify_action(headers) -> tuple[bool, str]: if not ACTIONS_ENABLED: return False, "Dashboard actions are disabled." token = headers.get("X-Watchdog-Token", "") if not ACTION_TOKEN or not secrets.compare_digest(token, ACTION_TOKEN): return False, "Invalid or missing dashboard action token." return True, "ok" ``` ### Technical Analysis The dashboard's supposedly secret action token is embedded directly into every unauthenticated response for `/` or `/index.html`. Consequently, any client capable of requesting the dashboard page can recover the credential from `window.WATCHDOG_ACTION_TOKEN`. The server does not validate the HTTP `Host` or `Origin` header. Binding to `127.0.0.1` reduces direct remote exposure but does not establish an authentication boundary. Local malware, another process running under the user, a browser extension, or a browser-based DNS-rebinding attack could potentially request the page and obtain the token. Once recovered, the token authorizes Gateway restart, diagnostics exec ...[truncated 1283 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Never embed `ACTION_TOKEN` in HTML or JavaScript. 2. Keep dashboard actions disabled by default unless the user explicitly enables them. 3. Require the user to enter the token manually or establish an authenticated session. 4. If session authentication is used, store the session identifier in a cookie with: - `HttpOnly` - `SameSite=Strict` - An appropriate `Secure` policy where HTTPS is available 5. Validate `Host` against an allowlist such as `127.0.0.1:18790` and `localhost:18790`. 6. Reject state-changing requests whose `Origin` does not match the dashboard origin. 7. Consider requiring a fresh confirmation or short-lived nonce for Gateway restarts. 8. Rate-limit failed authentication and sensitive action requests. 9. Do not store the action token in browser `localStorage`, because any script executing in the dashboard origin can retrieve it. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
install-watchdog.sh:181
Finding
Persistent Shell Command Injection Through Generated Unix Configuration<![CDATA[ ## Vulnerability Details **File Location**: `install-watchdog.sh:52-58`, `install-watchdog.sh:181-188`, and `gateway-watchdog.sh:71-74` **Vulnerability Type**: Shell command injection through executable configuration **Risk Level**: High ### Vulnerable Code Installer arguments are accepted without validation or shell-safe serialization: ```bash while [ "$#" -gt 0 ]; do case "$1" in --yes|-y) YES=1 ;; --no-start) NO_START=1 ;; --channel-url) CHANNEL_URL="${2:?missing URL}"; shift ;; --gateway-service|--service) GATEWAY_SERVICE="${2:?missing service name}"; shift ;; --health-url) GATEWAY_HEALTH_URL="${2:?missing URL}"; shift ;; --restart-command) RESTART_COMMAND="${2:?missing command}"; shift ;; --install-dir) INSTALL_DIR="${2:?missing dir}"; shift ;; ``` These values are interpolated into a shell file: ```bash cat >"$CONFIG_FILE" <<EOF # OpenClaw Gateway Watchdog config. # Re-run install-watchdog.sh with flags or edit this file to override defaults. GATEWAY_SERVICE="${GATEWAY_SERVICE}" GATEWAY_HEALTH_URL="${GATEWAY_HEALTH_URL}" GATEWAY_HOST="127.0.0.1" GATEWAY_PORT="18789" CHANNEL_URL="${CHANNEL_URL}" NETWORK_URLS="https://www.baidu.com https://www.qq.com https://api.weixin.qq.com" RESTART_COMMAND="${RESTART_COMMAND}" ``` The resulting file is later executed by the shell: ```bash if [ -f "$CONFIG_FILE" ]; then # shellcheck disable=SC1090 . "$CONFIG_FILE" fi ``` ### Technical Analysis The installer treats user-supplied values as data when accepting arguments, but later writes those values into a file that is sourced as shell code. Double quotes do not safely serialize arbitrary input into a shell program. Values containing a double quote, command substitution, backticks, shell metacharacters, or a newline can terminate the intended assignment and inject additional commands. Because the watchdog is installed as a systemd user service or macOS LaunchAgent, injected commands can execute whenever the persistent wa ...[truncated 1618 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not source configuration files as shell programs. 2. Store configuration in a non-executable format such as JSON and parse it with a dedicated parser. 3. If shell-compatible configuration must be retained temporarily: - Serialize every value with `printf '%q'`. - Reject newlines, carriage returns, null bytes, and unexpected control characters. - Validate URLs with an allowlisted scheme and a proper URL parser. - Validate service names against a narrow expression such as `^[A-Za-z0-9_.@-]+$`. 4. Separate restart command configuration from ordinary data fields. 5. Prefer a structured command and argument array instead of a single shell command string. 6. Ensure the generated configuration is owned by the current user and writable only by that user. 7. Add tests using values containing quotes, command substitution, backticks, semicolons, and newlines. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
gateway-watchdog.sh:657
Finding
Complete Model Provider Configuration Is Persisted During Credential-Free Edge Probing<![CDATA[ ## Vulnerability Details **File Location**: `gateway-watchdog.sh:657-665` and `gateway-watchdog.ps1:448-462` **Vulnerability Type**: Excessive collection and plaintext persistence of potentially sensitive provider configuration **Risk Level**: Medium ### Vulnerable Code Unix implementation: ```bash if [ -z "$model" ]; then openclaw models status --json >"$status_file" 2>&1 || return 2 model=$(json_field "$status_file" "defaultModel") fi [ -n "$model" ] || return 2 case "$model" in */*) provider="${model%%/*}" ;; *) return 2 ;; esac openclaw config get "models.providers.${provider}" >"$provider_file" 2>&1 || return 2 base_url=$(json_field "$provider_file" "baseUrl") [ -n "$base_url" ] || return 2 ``` Windows implementation: ```powershell $providerOutput = & openclaw config get "models.providers.$provider" 2>&1 if ($LASTEXITCODE -ne 0) { return 2 } Save-ProbeOutput -Name "last-openclaw-model-provider.json" -Output $providerOutput | Out-Null try { $providerJson = ($providerOutput -join "`n") | ConvertFrom-Json -ErrorAction Stop $baseUrl = [string]$providerJson.baseUrl } catch { return 2 } if (-not $baseUrl) { return 2 } ``` ### Technical Analysis The edge probe requires only the provider's `baseUrl`, but both implementations request and persist the complete provider object. Depending on OpenClaw's configuration output, this object may include API keys, bearer tokens, custom authorization headers, proxy credentials, organization identifiers, or other sensitive provider settings. The network request itself does not explicitly attach credentials, and no confirmed exfiltration to a third-party endpoint was found. The vulnerability is the unnecessary copying of potentially credential-bearing configuration into a long-lived state file. This behavior exceeds the minimum data access necessary for a no-credential reachability check. ### Attack Path 1. Model edge probing runs and identifies the configured provider. 2. The watchdog execute ...[truncated 952 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Query only the `baseUrl` field if the OpenClaw CLI supports field-level retrieval. 2. Parse provider output in memory and never write the complete provider object to disk. 3. If temporary storage is unavoidable: - Create the file with mode `0600`. - Delete it immediately after extracting the required field. - Redact fields matching `token`, `secret`, `password`, `apiKey`, `authorization`, and related names. 4. Do not include provider configuration files in dashboard exports or support bundles. 5. Document precisely what provider metadata is read and retained. 6. Add automated tests confirming that known credential fields never appear in state files or logs. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
install-watchdog.sh:164
Finding
Dashboard Token and Diagnostic State Files Lack Explicit Restrictive Unix Permissions<![CDATA[ ## Vulnerability Details **File Location**: `install-watchdog.sh:164-216` **Vulnerability Type**: Insecure local secret and diagnostic file permissions **Risk Level**: Medium ### Vulnerable Code ```bash mkdir -p "$INSTALL_DIR" "$CONFIG_DIR" "$STATE_DIR" install -m 0755 "${SCRIPT_DIR}/gateway-watchdog.sh" "${INSTALL_DIR}/gateway-watchdog.sh" install -m 0755 "${SCRIPT_DIR}/uninstall-watchdog.sh" "${INSTALL_DIR}/uninstall-watchdog.sh" install -m 0644 "${SCRIPT_DIR}/gateway-watchdog.ps1" "${INSTALL_DIR}/gateway-watchdog.ps1" 2>/dev/null || true install -m 0644 "${SCRIPT_DIR}/install-watchdog.ps1" "${INSTALL_DIR}/install-watchdog.ps1" 2>/dev/null || true install -m 0644 "${SCRIPT_DIR}/uninstall-watchdog.ps1" "${INSTALL_DIR}/uninstall-watchdog.ps1" 2>/dev/null || true [ -f "${SCRIPT_DIR}/README.md" ] && install -m 0644 "${SCRIPT_DIR}/README.md" "${INSTALL_DIR}/README.md" [ -f "${SCRIPT_DIR}/README.zh-CN.md" ] && install -m 0644 "${SCRIPT_DIR}/README.zh-CN.md" "${INSTALL_DIR}/README.zh-CN.md" if [ -d "${SCRIPT_DIR}/dashboard" ]; then rm -rf "${INSTALL_DIR}/dashboard" cp -R "${SCRIPT_DIR}/dashboard" "${INSTALL_DIR}/dashboard" fi if [ ! -f "$CONFIG_FILE" ]; then DASHBOARD_TOKEN_VALUE="$(generate_token)" cat >"$CONFIG_FILE" <<EOF # OpenClaw Gateway Watchdog config. ... DASHBOARD_ACTIONS_ENABLED="1" DASHBOARD_TOKEN="${DASHBOARD_TOKEN_VALUE}" ... EOF ``` The runtime also creates state and log files without first enforcing an owner-only `umask`: ```bash mkdir -p "$STATE_DIR" "$RUNTIME_DIR" "$(dirname "$LOG_FILE")" ``` ### Technical Analysis The Unix installer generates an action token and writes it to `watchdog.env`, but it does not set `umask 077`, use `install -m 0600`, or explicitly call `chmod 600` afterward. The configuration and state directories are also not explicitly created with mode `0700`. As a result, confidentiality depends on the user's ambient `umask` and pre-existing directory permissions. On a permissively configured multi-user sy ...[truncated 1201 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Set `umask 077` at the beginning of both the installer and watchdog runtime. 2. Create sensitive directories explicitly with mode `0700`. 3. Create the configuration through a temporary owner-only file, then atomically rename it. 4. Enforce mode `0600` on: - `watchdog.env` - Watchdog logs - OpenClaw snapshots - Provider and model probe files - Dashboard PID and access logs 5. Verify that files and directories are owned by the current user before reading or writing them. 6. During upgrades, repair unsafe permissions on existing files. 7. Avoid placing sensitive runtime locks under a predictable shared `/tmp` directory; prefer a user-owned runtime directory and verify ownership. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
dashboard/static/app.js:429
Finding
Client-Side HTML Injection Through Unescaped Dashboard API Values<![CDATA[ ## Vulnerability Details **File Location**: `dashboard/static/app.js:429-446`, `dashboard/static/app.js:517-564` **Vulnerability Type**: DOM-based HTML injection and potential cross-site scripting **Risk Level**: Medium ### Vulnerable Code API-controlled file paths are inserted into HTML and attribute contexts: ```javascript function renderLayers(data) { const files = data.files || {}; const cfg = data.config || {}; const layers = [ ["Gateway", files.gateway, data.summary.status === "ok" ? "ok" : data.summary.status === "unknown" ? "warn" : "fail"], ["OpenClaw Health", files.health, data.summary.status === "degraded" ? "fail" : files.health?.exists ? "ok" : "warn"], ["Logs Signal", files.signals, data.summary.categoryCount ? "warn" : "ok"], ["Model Probe", files.modelHistory, data.summary.modelFailures ? "warn" : files.modelHistory?.exists ? "ok" : "warn"], ["Config", { exists: true, ageSeconds: null, path: data.configFile }, cfg.MODEL_PROBE_ENABLED === "1" || cfg.ModelProbeEnabled === true ? "warn" : "ok"], ]; $("layers").innerHTML = layers.map(([name, meta, state]) => ` <div class="layer"> <span class="spark ${state}"></span> <div><strong>${name}</strong><span class="subtle" title="${meta?.path || ""}">${meta?.path || "runtime snapshot"}</span></div> <span class="subtle">${meta?.exists ? fmtAge(meta.ageSeconds) : t("missing")}</span> </div> `).join(""); } ``` Additional API-derived values are rendered through `innerHTML`: ```javascript function renderRuntimeMap(data) { const node = $("runtimeMap"); if (!node) return; const rows = [ [t("stateDirectory"), data.stateDir || t("notAvailable")], [t("configFile"), data.configFile || t("notAvailable")], [t("dashboardPort"), data.dashboard?.port || t("notAvailable")], [t("dashboardAction"), data.dashboard?.actionsEnabled ? t("enabled") : t("disabled")], ]; node.innerHTML = rows.map(([title, detail]) => ` <div class=" ...[truncated 2709 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace `innerHTML` rendering with DOM construction using `document.createElement`. 2. Assign all untrusted values through `textContent`. 3. Assign attributes through safe DOM properties after validating their values. 4. If HTML generation is unavoidable, apply context-aware escaping separately for text and attribute contexts. 5. Add a restrictive Content Security Policy, for example: - `default-src 'self'` - `script-src 'self'` - `object-src 'none'` - `base-uri 'none'` - `frame-ancestors 'none'` 6. Remove the action token from `window` and browser `localStorage`. 7. Validate dashboard configuration values server-side before returning them. 8. Add frontend tests with payloads containing HTML tags, quotes, event handlers, and encoded markup. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Rogue AgentSelf-Modification, Session Persistence
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
Findings (67)

Lp1

High
Category
MCP Least Privilege
Confidence
91% confidence
Finding
Shell capability is present through run_command using shell=True, but the declared permissions only describe restarting the gateway and diagnostics behavior, not arbitrary shell execution from configuration. This mismatch matters because the code can execute a configured command string, exceeding the least-privilege expectations set by the manifest.

Lp1

High
Category
MCP Least Privilege
Confidence
91% confidence
Finding
Shell capability is present through run_command using shell=True, but the declared permissions only describe restarting the gateway and diagnostics behavior, not arbitrary shell execution from configuration. This mismatch matters because the code can execute a configured command string, exceeding the least-privilege expectations set by the manifest.

Lp1

High
Category
MCP Least Privilege
Confidence
91% confidence
Finding
Shell capability is present through run_command using shell=True, but the declared permissions only describe restarting the gateway and diagnostics behavior, not arbitrary shell execution from configuration. This mismatch matters because the code can execute a configured command string, exceeding the least-privilege expectations set by the manifest.

Lp1

High
Category
MCP Least Privilege
Confidence
91% confidence
Finding
Shell capability is present through run_command using shell=True, but the declared permissions only describe restarting the gateway and diagnostics behavior, not arbitrary shell execution from configuration. This mismatch matters because the code can execute a configured command string, exceeding the least-privilege expectations set by the manifest.

Lp1

High
Category
MCP Least Privilege
Confidence
91% confidence
Finding
Shell capability is present through run_command using shell=True, but the declared permissions only describe restarting the gateway and diagnostics behavior, not arbitrary shell execution from configuration. This mismatch matters because the code can execute a configured command string, exceeding the least-privilege expectations set by the manifest.

Tool Parameter Abuse

High
Category
Tool Misuse
Content
if not command:
        return {"ok": False, "error": "empty command"}
    try:
        proc = subprocess.run(
            command,
            shell=True,
            cwd=str(STATE_DIR),
Confidence
98% confidence
Finding
This is a real tool-parameter abuse issue because the command parameter to subprocess.run is a shell string and can be influenced by configuration, turning an intended maintenance action into a generic execution primitive. In the context of a local dashboard with restart and diagnostics actions, that greatly increases the blast radius of any auth bypass, token exposure, or config tampering.

Scope Creep

High
Confidence
99% confidence
Finding
The server injects the action token directly into index.html as JavaScript, making the credential available to any script running in the page context and to anyone who can load the dashboard UI. Since that same token authorizes restart, diagnostics, and config changes, exposing it client-side collapses the trust boundary and undermines the action protection mechanism.

Context-Inappropriate Capability

High
Confidence
99% confidence
Finding
The restart action reads RESTART_COMMAND from configuration and executes it via shell=True, so the dashboard's 'restart gateway' function is actually arbitrary command execution. Because the server also supports config mutation and exposes the action token in the UI, this becomes especially dangerous: an attacker who can influence config or obtain the token can run any shell command as the user.

Scope Creep

High
Confidence
98% confidence
Finding
The diagnostic action path allows execution of arbitrary shell commands from configuration using cmd.exe /c $Config.OpenClawDiagCommand. This exceeds the declared permissions, turns configuration into code execution, and creates a high-risk primitive whereby any actor able to modify the config can run arbitrary commands as the user.

Scope Creep

High
Confidence
99% confidence
Finding
The restart path executes an arbitrary RestartCommand from configuration via cmd.exe /c, not just the declared systemctl --user or openclaw gateway restart operations. This creates a direct arbitrary command execution vector under the current user account if the config is modified, and the watchdog may trigger it repeatedly based on health-check conditions.

Scope Creep

High
Confidence
98% confidence
Finding
The script executes OPENCLAW_DIAG_COMMAND via `sh -c`, where the value is loaded from a user-controlled config file or environment. That gives the skill a general-purpose command execution path far beyond the declared restart/monitoring behavior, so anyone able to modify config can cause arbitrary code execution in the user's context.

Scope Creep

High
Confidence
98% confidence
Finding
On model probe failure, the script runs MODEL_PROBE_COMMAND through `sh -c`, again turning configuration into arbitrary shell execution. Because this path can be triggered by network/provider failures, it expands a monitoring tool into a command runner that is not covered by the stated permissions and could be abused for persistence or arbitrary local actions.

Scope Creep

High
Confidence
99% confidence
Finding
The restart path honors RESTART_COMMAND from config/environment and executes it with `sh -c`. Since restart is a frequently reachable code path, this effectively grants arbitrary command execution whenever the watchdog decides to restart, which materially exceeds the manifest's described restart mechanisms.

Tool Parameter Abuse

High
Category
Tool Misuse
Content
[ -f "${SCRIPT_DIR}/README.md" ] && install -m 0644 "${SCRIPT_DIR}/README.md" "${INSTALL_DIR}/README.md"
[ -f "${SCRIPT_DIR}/README.zh-CN.md" ] && install -m 0644 "${SCRIPT_DIR}/README.zh-CN.md" "${INSTALL_DIR}/README.zh-CN.md"
if [ -d "${SCRIPT_DIR}/dashboard" ]; then
  rm -rf "${INSTALL_DIR}/dashboard"
  cp -R "${SCRIPT_DIR}/dashboard" "${INSTALL_DIR}/dashboard"
fi
Confidence
95% confidence
Finding
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Tool Parameter Abuse

High
Category
Tool Misuse
Content
if [ -n "${pid:-}" ] && kill -0 "$pid" 2>/dev/null; then
    kill "$pid" 2>/dev/null || true
  fi
  rm -f "${STATE_DIR}/watchdog.pid"
fi

if [ -f "${STATE_DIR}/dashboard.pid" ]; then
Confidence
95% confidence
Finding
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Tool Parameter Abuse

High
Category
Tool Misuse
Content
if [ -n "${pid:-}" ] && kill -0 "$pid" 2>/dev/null; then
    kill "$pid" 2>/dev/null || true
  fi
  rm -f "${STATE_DIR}/dashboard.pid"
fi

rm -f "$SERVICE_FILE" "$PLIST_FILE"
Confidence
95% confidence
Finding
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Missing User Warnings

Medium
Confidence
88% confidence
Finding
The README states that the watchdog collects diagnostics snapshots, log signals, and can optionally send model probe requests, but it does not clearly warn users that these artifacts may contain sensitive operational data, prompts, endpoint details, or other private information. Because this skill also writes persistent logs and exposes a localhost dashboard, insufficient disclosure increases the risk of accidental retention or exposure of sensitive data during troubleshooting or support sharing.

Session Persistence

Medium
Category
Rogue Agent
Content
On macOS, inspect `launchctl print gui/$(id -u)/ai.clawhub.gateway-resilience-guard`.
On Windows, inspect `Get-ScheduledTask -TaskName "OpenClaw Gateway Resilience Guard"`.

If user systemd is unavailable, the installer starts a direct background fallback and stores its pid under `~/.local/state/openclaw-gateway-watchdog/watchdog.pid`.

## Safety Model
Confidence
80% 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.

Session Persistence

Medium
Category
Rogue Agent
Content
On macOS, inspect `launchctl print gui/$(id -u)/ai.clawhub.gateway-resilience-guard`.
On Windows, inspect `Get-ScheduledTask -TaskName "OpenClaw Gateway Resilience Guard"`.

If user systemd is unavailable, the installer starts a direct background fallback and stores its pid under `~/.local/state/openclaw-gateway-watchdog/watchdog.pid`.

## Safety Model
Confidence
80% 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.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
if not command:
        return {"ok": False, "error": "empty command"}
    try:
        proc = subprocess.run(
            command,
            shell=True,
            cwd=str(STATE_DIR),
Confidence
97% confidence
Finding
The dashboard executes shell commands via subprocess.run(..., shell=True), which is dangerous because the restart command is sourced from configuration and not restricted to a fixed safe command. In this skill, a localhost web server with action endpoints can trigger that execution path, so any compromise of the config, token handling, or local browser context can become arbitrary command execution under the user's account.

Tainted flow: 'STATE_DIR' from os.environ.get (line 26, credential/environment) → subprocess.run (code execution)

Medium
Category
Data Flow
Content
if not command:
        return {"ok": False, "error": "empty command"}
    try:
        proc = subprocess.run(
            command,
            shell=True,
            cwd=str(STATE_DIR),
Confidence
65% confidence
Finding
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Missing User Warnings

Medium
Confidence
84% confidence
Finding
The dashboard updates the watchdog configuration file in place via write_text when presets are applied, which can change runtime behavior persistently. This file contains no confirmation prompt, no user-facing log message, and no inline warning near the write path explaining that a POST request can modify local configuration.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
Placing the action token in client-side HTML is sensitive credential exposure, and the file provides no warning or compensating control to users about this handling. Although the core problem is the exposure itself, the lack of disclosure worsens the risk because users may assume the token remains server-side.

Scope Creep

Medium
Confidence
96% confidence
Finding
The GET endpoints /api/status and /api/export are unauthenticated and return masked config, file paths, logs, diagnostics, model history, and signal data to any local process or webpage able to reach localhost. In a localhost dashboard context this is still sensitive, because malicious local software or a visited website can often probe local services and exfiltrate operational details.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The dashboard action token is loaded from and written to browser localStorage, which is long-lived and readable by any JavaScript running in the same origin. If the dashboard ever has an XSS bug, a malicious browser extension, or shared workstation access, the token can be stolen and reused to invoke privileged actions such as diagnostics, config changes, or gateway restarts.

Static analysis

Detected: suspicious.install_untrusted_source

Install source points to URL shortener or raw IP.

Warn
Code
suspicious.install_untrusted_source
Location
openclaw-plugin/openclaw.plugin.json:12