T03 · Remote Payload Retrieval and Execution
- Location
install.py:13- Finding
Unverified Remote Python Payload Download and Execution
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
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.
install.py:13Unverified Remote Python Payload Download and Execution
gateway_watchdog.py:90Implicit Execution of an Unpinned Package Through npx
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.
# 尝试 npx
try:
result = subprocess.run(
["npx", "openclaw", "--version"],
capture_output=True,
timeout=10,
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.
try:
# 检查进程是否存在
if platform.system() == "Windows":
result = subprocess.run(
f"tasklist /FI \"PID eq {old_pid}\"",
capture_output=True,
text=True,
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.
try:
if platform.system() == "Windows":
subprocess.run(f"taskkill /PID {pid} /F", shell=True)
else:
os.kill(int(pid), signal.SIGTERM)
PID_FILE.unlink()
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.
pid = f.read().strip()
try:
if platform.system() == "Windows":
result = subprocess.run(
f"tasklist /FI \"PID eq {pid}\"",
capture_output=True,
text=True,
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.
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.
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.
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 calls execute external commands. Without careful input validation, this enables command injection.
# 首先尝试直接调用 (可能已经在 PATH 中)
try:
result = subprocess.run(
["openclaw", "--version"],
capture_output=True,
timeout=10,
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
for p in paths_to_try:
try:
result = subprocess.run(
[str(p), "--version"],
capture_output=True,
timeout=10,
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.
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.
# 尝试 npx
try:
result = subprocess.run(
["npx", "openclaw", "--version"],
capture_output=True,
timeout=10,
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 calls execute external commands. Without careful input validation, this enables command injection.
for p in paths_to_try:
try:
result = subprocess.run(
[str(p), "--version"],
capture_output=True,
timeout=10
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.
"""检查 Gateway 是否正常运行"""
try:
cmd = get_openclaw_cmd() + ["gateway", "status"]
result = subprocess.run(
cmd,
capture_output=True,
text=True,
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.
try:
log("Gateway 连续失败,准备重启...")
cmd = get_openclaw_cmd() + ["gateway", "restart"]
subprocess.run(
cmd,
capture_output=True,
text=True,
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.
try:
# 检查进程是否存在
if platform.system() == "Windows":
result = subprocess.run(
f"tasklist /FI \"PID eq {old_pid}\"",
capture_output=True,
text=True,
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
if platform.system() == "Windows":
# Windows: 用 start /b 后台运行
proc = subprocess.Popen(
[sys.executable, __file__, "run"],
stdout=open(LOG_FILE, "a"),
stderr=subprocess.STDOUT,
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.
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP
)
else:
# Linux: nohup
proc = subprocess.Popen(
["nohup", sys.executable, __file__, "run"],
stdout=open(LOG_FILE, "a"),
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
)
else:
# Linux: nohup
proc = subprocess.Popen(
["nohup", sys.executable, __file__, "run"],
stdout=open(LOG_FILE, "a"),
stderr=subprocess.STDOUT,
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.
try:
if platform.system() == "Windows":
subprocess.run(f"taskkill /PID {pid} /F", shell=True)
else:
os.kill(int(pid), signal.SIGTERM)
PID_FILE.unlink()
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
pid = f.read().strip()
try:
if platform.system() == "Windows":
result = subprocess.run(
f"tasklist /FI \"PID eq {pid}\"",
capture_output=True,
text=True,
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.
print("=" * 50)
try:
cmd = get_openclaw_cmd() + ["gateway", "status"]
result = subprocess.run(
cmd,
capture_output=True,
text=True,
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.
log("手动重启 Gateway...")
try:
cmd = get_openclaw_cmd() + ["gateway", "restart"]
result = subprocess.run(
cmd,
capture_output=True,
text=True,
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.