Back to skill

Security audit

Gateway Watchdog

Security checks for vulnerabilities and agentic risk

Overview

This skill is a real Gateway watchdog, but it can start a persistent local process and has unsafe unverified code/dependency execution paths.

Review this carefully before installing. Do not run it as administrator/root, avoid the one-click installer unless the bundled gateway_watchdog.py is present and reviewed, and make sure OpenClaw is installed from a trusted source so the npx fallback is not used. Install only if you explicitly want a background process that can keep restarting Gateway under your account.

Vulnerability Patterns
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (2)

T03 · Remote Payload Retrieval and Execution

Error
Location
install.py:13
Finding
Unverified Remote Python Payload Download and Execution<![CDATA[ ## Vulnerability Details **File Location**: `install.py:13-49` **Vulnerability Type**: Unverified remote code retrieval and execution **Risk Level**: Critical ### Vulnerable Code ```python REPO_URL = "https://raw.githubusercontent.com/adminlove520/openclaw-gateway-watchdog-v2/main/gateway_watchdog.py" def download_watchdog(): """下载 gateway_watchdog.py""" script_path = Path(__file__).parent.resolve() / "gateway_watchdog.py" if script_path.exists(): print(f"✅ gateway_watchdog.py 已存在") return script_path print("📥 正在下载 gateway_watchdog.py...") try: urllib.request.urlretrieve(REPO_URL, script_path) print(f"✅ 下载完成: {script_path}") return script_path except Exception as e: print(f"❌ 下载失败: {e}") return None def main(): print("=" * 50) print("🚀 OpenClaw Gateway 7/24 运行") print("=" * 50) # 1. 下载脚本 script_path = download_watchdog() if not script_path: sys.exit(1) # 2. 启动 watchdog print("\n🚀 启动 Gateway Watchdog...") try: result = subprocess.run( [sys.executable, str(script_path), "start"], capture_output=True, text=True ) ``` ### Technical Analysis When the local `gateway_watchdog.py` file does not exist, the installer downloads a replacement from the mutable `main` branch of a personal GitHub repository. The retrieved content is accepted without a cryptographic hash check, digital-signature verification, immutable commit pin, or source-content validation. The downloaded file is then passed directly to the current Python interpreter. Consequently, the effective code executed by the Skill can change after the reviewed package has been published. HTTPS protects transport in normal circumstances but does not protect against repository compromise, malicious upstream changes, account takeover, or an unauthorized maintainer update. The URL in `install.py` also ref ...[truncated 1798 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove the network fallback and execute only the `gateway_watchdog.py` file included in the reviewed Skill package. 2. Treat a missing bundled script as an installation error rather than silently replacing it with remote code. 3. If remote retrieval is operationally necessary: - Pin the URL to an immutable commit rather than the mutable `main` branch. - Publish and hard-code a trusted SHA-256 digest for the expected file. - Verify the digest before writing or executing the file. - Prefer a signed release artifact and validate its signature against a trusted, pinned public key. - Download into a securely created temporary file, validate it, and only then atomically move it into place. 4. Require explicit user confirmation before executing newly downloaded code. 5. Make the repository URL consistent with the documented official source. 6. Fail closed and delete the downloaded file if any integrity, signature, size, or content check fails. 7. Run the watchdog under a dedicated, non-privileged account and explicitly warn users not to execute the installer as root or administrator. ]]>

T08 · Insecure Dependencies

Error
Location
gateway_watchdog.py:90
Finding
Implicit Execution of an Unpinned Package Through npx<![CDATA[ ## Vulnerability Details **File Location**: `gateway_watchdog.py:90-99` **Vulnerability Type**: Unsafe automatic dependency retrieval and execution **Risk Level**: High ### Vulnerable Code ```python # 尝试 npx try: result = subprocess.run( ["npx", "openclaw", "--version"], capture_output=True, timeout=10, shell=True ) if result.returncode == 0: return "npx openclaw" ``` ### Technical Analysis During OpenClaw command detection on Windows, the watchdog invokes `npx openclaw --version` when earlier executable candidates cannot be used. Depending on the installed npm/npx behavior, `npx` may retrieve and execute the named registry package when it is not already installed locally. The package is identified only by the mutable name `openclaw`. No version, package digest, lockfile, signature, trusted registry configuration, or no-install restriction is supplied. Package installation and lifecycle behavior can therefore occur merely as a side effect of command detection. Using `shell=True` is unnecessary for this argument-list invocation and increases dependence on shell command-resolution behavior. No direct command injection source was confirmed in this specific call because the arguments are static, but avoiding the shell would reduce the attack surface. ### Attack Path 1. A Windows user starts the watchdog without a discoverable local OpenClaw executable. 2. `detect_openclaw()` reaches the npx fallback. 3. The script runs `npx openclaw --version`. 4. If the package is not locally available, npx may resolve it through the configured npm registry and download it. 5. A compromised, replaced, or otherwise unsafe registry package executes package installation or runtime code. 6. That code runs with the privileges of the user who started the watchdog. ### Impact Assessment A malicious package could execute arbitrary code u ...[truncated 602 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove the automatic npx fallback from executable detection. 2. Resolve only preinstalled executables using `shutil.which("openclaw")` and explicitly approved absolute paths. 3. If OpenClaw is unavailable, stop with a clear error and require the user to install it separately from a trusted source. 4. If npx must be supported, use a mode that prohibits downloads, such as an appropriate `--no-install` or offline-only option supported by the deployed npx version. 5. Pin any permitted package to an explicitly reviewed version and verify it through a lockfile, integrity metadata, and a trusted registry. 6. Invoke subprocesses with `shell=False` and an argument list. 7. Document the exact dependency source, publisher, version, and verification procedure. 8. Run dependency detection and the watchdog from a least-privileged account rather than an administrator session. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
Findings (30)

Tool Parameter Abuse

High
Category
Tool Misuse
Content
# 尝试 npx
        try:
            result = subprocess.run(
                ["npx", "openclaw", "--version"],
                capture_output=True,
                timeout=10,
Confidence
96% confidence
Finding
This tool invocation combines `npx` with `shell=True`, which increases the chance of unsafe parameter interpretation and supply-chain abuse. In an agent skill context, any external tool execution that can dynamically resolve packages is especially dangerous because it extends trust beyond the local codebase.

Tool Parameter Abuse

High
Category
Tool Misuse
Content
try:
            # 检查进程是否存在
            if platform.system() == "Windows":
                result = subprocess.run(
                    f"tasklist /FI \"PID eq {old_pid}\"",
                    capture_output=True,
                    text=True,
Confidence
99% confidence
Finding
The PID filter for `tasklist` is constructed from file-sourced data and executed via the shell, enabling parameter and command injection. Because PID files are often writable or replaceable in weakly secured deployments, this is a practical abuse path.

Tool Parameter Abuse

High
Category
Tool Misuse
Content
try:
        if platform.system() == "Windows":
            subprocess.run(f"taskkill /PID {pid} /F", shell=True)
        else:
            os.kill(int(pid), signal.SIGTERM)
        PID_FILE.unlink()
Confidence
99% confidence
Finding
The `taskkill` command uses shell interpolation of a PID file value, making arbitrary shell command injection possible. This can let an attacker escalate from file tampering to code execution in the watchdog's security context.

Tool Parameter Abuse

High
Category
Tool Misuse
Content
pid = f.read().strip()
        try:
            if platform.system() == "Windows":
                result = subprocess.run(
                    f"tasklist /FI \"PID eq {pid}\"",
                    capture_output=True,
                    text=True,
Confidence
99% confidence
Finding
This status-check path repeats the same shell-based `tasklist` pattern using PID file data. An attacker who controls the PID file can trigger command execution when an operator checks service status.

Intent-Code Divergence

High
Confidence
99% confidence
Finding
This installer fetches a Python script from a remote GitHub URL and immediately executes it later without any checksum, signature, pinning, or trust verification. That creates a remote code execution supply-chain risk: if the repository, branch content, network path, or hosting account is compromised, users will run attacker-controlled code under their own privileges.

Natural-Language Policy Violations

Medium
Confidence
93% confidence
Finding
Most of the README's operational instructions and descriptions are written in Chinese, and there is no indication that users may choose another language or that the locale restriction is intentional for a region-specific audience. Under the stated policy, forcing a specific language without opt-in is a natural-language policy violation.

Vague Triggers

Medium
Confidence
94% confidence
Finding
The trigger phrases are broad, generic operational requests such as 'keep Gateway running' and 'set watchdog', which could be matched in ordinary conversation without clearly signaling consent to download software or start a persistent process. In this skill’s context, accidental invocation is more dangerous because the described behavior includes fetching code from GitHub and launching a long-running watchdog process.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
The skill instructs the agent to download `gateway_watchdog.py` from GitHub if missing and execute it, but it does not warn the user about network access, remote code retrieval, or the creation of a persistent watchdog process. This is dangerous because it enables silent system modification and execution of unverified external code, increasing the risk of supply-chain compromise or unauthorized persistence.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
# 首先尝试直接调用 (可能已经在 PATH 中)
    try:
        result = subprocess.run(
            ["openclaw", "--version"],
            capture_output=True,
            timeout=10,
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
for p in paths_to_try:
            try:
                result = subprocess.run(
                    [str(p), "--version"],
                    capture_output=True,
                    timeout=10,
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Rp1

Medium
Category
MCP Rug Pull
Confidence
92% confidence
Finding
The code references `npx` as part of runtime tool discovery without pinning a package version. `npx` can resolve packages dynamically, which creates supply-chain risk and non-deterministic execution behavior if the package source or local environment is compromised.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
# 尝试 npx
        try:
            result = subprocess.run(
                ["npx", "openclaw", "--version"],
                capture_output=True,
                timeout=10,
Confidence
95% confidence
Finding
Running `npx openclaw --version` with `shell=True` introduces unnecessary shell exposure and also trusts `npx` to resolve and potentially fetch a package dynamically. In a compromised environment, PATH hijacking or malicious package resolution can result in execution of attacker-controlled code.

Rp1

Medium
Category
MCP Rug Pull
Confidence
93% confidence
Finding
Persisting `npx openclaw` as the command means later watchdog operations may invoke an unpinned package executor repeatedly. This expands supply-chain exposure from one-time discovery to routine operational execution.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
for p in paths_to_try:
            try:
                result = subprocess.run(
                    [str(p), "--version"],
                    capture_output=True,
                    timeout=10
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
"""检查 Gateway 是否正常运行"""
    try:
        cmd = get_openclaw_cmd() + ["gateway", "status"]
        result = subprocess.run(
            cmd,
            capture_output=True,
            text=True,
Confidence
91% confidence
Finding
`cmd` is derived from the persisted configuration value `openclaw_cmd`, and this command is executed with `shell=True` on Windows. If an attacker can modify `gateway_watchdog.json` or influence command discovery earlier, they can cause arbitrary command execution whenever the watchdog performs a status check.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
try:
        log("Gateway 连续失败,准备重启...")
        cmd = get_openclaw_cmd() + ["gateway", "restart"]
        subprocess.run(
            cmd,
            capture_output=True,
            text=True,
Confidence
91% confidence
Finding
This restart path executes `openclaw_cmd` from configuration with `shell=True` on Windows. Because the watchdog automatically triggers this code after repeated failures, a maliciously altered config can yield repeated arbitrary command execution with the privileges of the watchdog process.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
try:
            # 检查进程是否存在
            if platform.system() == "Windows":
                result = subprocess.run(
                    f"tasklist /FI \"PID eq {old_pid}\"",
                    capture_output=True,
                    text=True,
Confidence
98% confidence
Finding
`old_pid` is read from a PID file and inserted into a shell command string for `tasklist`. A maliciously modified PID file could inject additional shell commands on Windows, making this a straightforward command-injection issue.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
if platform.system() == "Windows":
        # Windows: 用 start /b 后台运行
        proc = subprocess.Popen(
            [sys.executable, __file__, "run"],
            stdout=open(LOG_FILE, "a"),
            stderr=subprocess.STDOUT,
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Session Persistence

Medium
Category
Rogue Agent
Content
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP
        )
    else:
        # Linux: nohup
        proc = subprocess.Popen(
            ["nohup", sys.executable, __file__, "run"],
            stdout=open(LOG_FILE, "a"),
Confidence
65% 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
)
    else:
        # Linux: nohup
        proc = subprocess.Popen(
            ["nohup", sys.executable, __file__, "run"],
            stdout=open(LOG_FILE, "a"),
            stderr=subprocess.STDOUT,
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
try:
        if platform.system() == "Windows":
            subprocess.run(f"taskkill /PID {pid} /F", shell=True)
        else:
            os.kill(int(pid), signal.SIGTERM)
        PID_FILE.unlink()
Confidence
98% confidence
Finding
The `pid` value is read from a writable PID file and interpolated directly into a shell command: `taskkill /PID {pid} /F`. If an attacker can alter the PID file, they can inject additional shell syntax and execute arbitrary commands on Windows.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
pid = f.read().strip()
        try:
            if platform.system() == "Windows":
                result = subprocess.run(
                    f"tasklist /FI \"PID eq {pid}\"",
                    capture_output=True,
                    text=True,
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
print("=" * 50)
    try:
        cmd = get_openclaw_cmd() + ["gateway", "status"]
        result = subprocess.run(
            cmd,
            capture_output=True,
            text=True,
Confidence
90% confidence
Finding
This status command again executes `openclaw_cmd` from configuration with `shell=True` on Windows. Any compromise of the config file or path discovery mechanism can be turned into arbitrary command execution when a user checks watchdog status.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
log("手动重启 Gateway...")
    try:
        cmd = get_openclaw_cmd() + ["gateway", "restart"]
        result = subprocess.run(
            cmd,
            capture_output=True,
            text=True,
Confidence
90% confidence
Finding
Manual restart uses the same untrusted `openclaw_cmd` value with `shell=True` on Windows. This allows malicious config tampering to execute arbitrary OS commands when an operator invokes the restart function.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The file’s user-facing description is entirely in Chinese and presents the skill as a one-click installer without any indication that another language is available. Under the policy, forcing a specific language without user opt-in is a natural-language policy violation unless the locale constraint is documented and justified.

Static analysis

No suspicious patterns detected.