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

Content
View full analysis
None: if not path.exists(): self.send_error(HTTPStatus.NOT_FOUND) return body = path.read_bytes() if path.name == "index.html": injection = f"" body = body.replace(b"", injection.encode("utf-8") + b"") 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
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
install-watchdog.sh:181
Finding

Persistent Shell Command Injection Through Generated Unix Configuration

Content
View full analysis
"$CONFIG_FILE" <
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
gateway-watchdog.sh:657
Finding

Complete Model Provider Configuration Is Persisted During Credential-Free Edge Probing

Content
View full analysis
"$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
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
install-watchdog.sh:164
Finding

Dashboard Token and Diagnostic State Files Lack Explicit Restrictive Unix Permissions

Content
View full analysis
/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" <
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
dashboard/static/app.js:429
Finding

Client-Side HTML Injection Through Unescaped Dashboard API Values

Content
View full analysis
`
${name}${meta?.path || "runtime snapshot"}
${meta?.exists ? fmtAge(meta.ageSeconds) : t("missing")}
`).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]) => `
Remediation
View remediation
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.

Content

No source excerpt is available for this finding.

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.

Content

No source excerpt is available for this finding.

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.

Content

No source excerpt is available for this finding.

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.

Content

No source excerpt is available for this finding.

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.

Content

No source excerpt is available for this finding.

Tool Parameter Abuse

High
Category
Tool Misuse
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.

Content

Scanner excerpt · dashboard/server.py (reported line 293)May include surrounding context.

python
if not command:
        return {"ok": False, "error": "empty command"}
    try:
        proc = subprocess.run(
            command,
            shell=True,
            cwd=str(STATE_DIR),

Scope Creep

High
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

High
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Scope Creep

High
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Scope Creep

High
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Scope Creep

High
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Scope Creep

High
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Scope Creep

High
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Tool Parameter Abuse

High
Category
Tool Misuse
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).

Content

Scanner excerpt · install-watchdog.sh (reported line 173)May include surrounding context.

sh
[ -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

Tool Parameter Abuse

High
Category
Tool Misuse
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).

Content

Scanner excerpt · uninstall-watchdog.sh (reported line 38)May include surrounding context.

sh
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

Tool Parameter Abuse

High
Category
Tool Misuse
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).

Content

Scanner excerpt · uninstall-watchdog.sh (reported line 46)May include surrounding context.

sh
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"

Missing User Warnings

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
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.

Content

Scanner excerpt · README.md (reported line 145)May include surrounding context.

md
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

Session Persistence

Medium
Category
Rogue Agent
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.

Content

Scanner excerpt · SKILL.md (reported line 89)May include surrounding context.

md
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

subprocess module call

Medium
Category
Dangerous Code Execution
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.

Content

Scanner excerpt · dashboard/server.py (reported line 293)May include surrounding context.

python
if not command:
        return {"ok": False, "error": "empty command"}
    try:
        proc = subprocess.run(
            command,
            shell=True,
            cwd=str(STATE_DIR),

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

Medium
Category
Data Flow
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.

Content

Scanner excerpt · dashboard/server.py (reported line 293)May include surrounding context.

python
if not command:
        return {"ok": False, "error": "empty command"}
    try:
        proc = subprocess.run(
            command,
            shell=True,
            cwd=str(STATE_DIR),

Missing User Warnings

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Scope Creep

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.