T06 · System Persistence
- Location
scripts/self-healing/workspace_watchdog.py:13- Finding
Root-Level Scheduled Persistence with Automatic Cron Repair
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
This is a high-privilege self-healing skill that discloses some risk, but it runs persistent root-level automation and can change system, Git, gateway, memory, and installed-skill state with weak user control.
Install only in a fully trusted, isolated OpenClaw environment after reviewing every scheduled job and script. Do not use it on shared systems or production hosts unless you are comfortable with root-level recurring execution, automatic configuration rollback, gateway restarts, Git commits, outbound Telegram notifications, and automatic installation of other skills. Before use, remove auto-skill installation and cron self-repair, replace shell-based commands, add an uninstall path, and keep backups outside any Git repository.
scripts/self-healing/workspace_watchdog.py:13Root-Level Scheduled Persistence with Automatic Cron Repair
scripts/self-healing/self_improver.py:43Autonomous Installation of Mutable Remote Skills Without Payload Verification
scripts/self-healing/self_improver.py:112Root Command Injection Through Unvalidated Skill Slug
scripts/self-healing/health_check.py:3Telegram Configuration Enables Shell Injection and Exposes Bot Credentials in Process Arguments
scripts/self-healing/self_improver.py:99Candidate-Controlled Text Is Written into Persistent Agent Memory
scripts/self-healing/backup.py:2Sensitive OpenClaw Backups Can Be Automatically Added to Git History
scripts/self-healing/health_check.py:15Any Gateway Status Error Triggers Unverified Configuration Rollback and Restart
scripts/self-healing/system_watchdog.py:3Root Watchdog Deletes Logs and Modifies Kernel Cache State
This constructs a shell command with secrets and untrusted configuration values interpolated directly into it, then executes it with os.system(). Even though the message is URL-encoded, BOT_TOKEN and CHAT_ID come from a local file and are not shell-escaped, so a malicious or tampered config could trigger command injection; the command line may also expose the bot token to local process inspection.
cfg[k.strip()] = v.strip()
token, chat_id = cfg.get('BOT_TOKEN'), cfg.get('CHAT_ID')
if token and chat_id:
os.system(f"curl -s 'https://api.telegram.org/bot{token}/sendMessage?chat_id={chat_id}&text={urllib.parse.quote_plus(msg)}' > /dev/null")
def check():
try:
os.system() and os exec-family calls run shell commands with the process's full privileges, enabling arbitrary command execution.
latest = backups[0]
shutil.copy2(latest / 'openclaw.json', CONFIG)
print(f"Restored from {latest}")
os.system('openclaw gateway restart')
if __name__ == "__main__": run_restore()
os.system() and os exec-family calls run shell commands with the process's full privileges, enabling arbitrary command execution.
latest = backups[0]
shutil.copy2(latest / 'openclaw.json', CONFIG)
print(f"Restored from {latest}")
os.system('openclaw gateway restart')
if __name__ == "__main__": run_restore()
This script autonomously fetches metadata from external services, evaluates skills with a trivial keyword filter, and installs them automatically. That creates a remote supply-chain pathway where a malicious or compromised skill source can cause unreviewed code or instructions to be added to the system, especially since the safety screening is easily bypassed.
The code builds a shell command with untrusted values from the local Telegram config file and executes it via os.system. Even though the message text is URL-encoded, BOT_TOKEN and CHAT_ID are not shell-escaped, so a maliciously modified config file could inject arbitrary shell syntax and achieve command execution; additionally, it silently exfiltrates status data to an external service.
token = config.get('BOT_TOKEN')
chat_id = config.get('CHAT_ID')
if token and chat_id:
os.system(f"curl -s 'https://api.telegram.org/bot{token}/sendMessage?chat_id={chat_id}&text={urllib.parse.quote_plus(msg)}' > /dev/null")
def append_memory(message):
The code automatically installs a skill by running an external command without any user warning, confirmation, or review step. In this context that is especially dangerous because installation is triggered by network-fetched candidate data and only guarded by simplistic checks, making unauthorized capability expansion likely.
The installer shells out with os.popen using a slug value that ultimately comes from a JSON candidates file and network-driven workflow. If an attacker can influence the slug or candidate source, they can inject shell metacharacters and execute arbitrary commands; beyond injection, this code autonomously installs remote skills without approval, expanding compromise risk.
def install_skill(slug):
log(f"Installing {slug}")
result = os.popen(f"openclaw skills install {slug} 2>&1").read()
if 'error' in result.lower() or 'failed' in result.lower():
log(f"Install failed {slug}: {result[:200]}", level='error')
return False, result
This code invokes a shell via os.system() to delete log files automatically when disk space is low. Although the command string is static and not directly user-controlled, using a shell for destructive filesystem operations is risky because it bypasses safer APIs, depends on shell semantics, and can delete data without validation or auditing; if run with elevated privileges, mistakes or path manipulation around the target directory can cause broader data loss.
# Disk space check (thấp hơn 500MB thì dọn log)
total, used, free = shutil.disk_usage("/")
if free < 500 * 1024 * 1024:
os.system('find /root/.openclaw/logs -name "*.log" -mtime +7 -delete')
# Memory check (nếu swap đầy thì dọn rác)
if psutil.swap_memory().percent > 90:
This code executes a privileged shell command that writes to /proc/sys/vm/drop_caches, forcibly dropping kernel caches whenever swap usage exceeds a threshold. That is a high-risk operational action because it can degrade performance, interfere with normal system behavior, and requires elevated privileges; using os.system() also adds unnecessary shell exposure and makes the action harder to control safely.
# Memory check (nếu swap đầy thì dọn rác)
if psutil.swap_memory().percent > 90:
os.system('sync; echo 3 > /proc/sys/vm/drop_caches')
if __name__ == "__main__": check_system()
Automatically dropping system caches without warning or approval is a dangerous self-healing action because it changes global kernel memory behavior and may harm performance or destabilize workloads. In this watchdog context, the action is triggered unattended under resource pressure, increasing the chance of repeated disruptive behavior at the worst possible time.
This is the same unsafe shell-based auto-commit behavior identified earlier: it executes a constructed shell command and modifies the repository without consent. In a workspace-monitoring skill, silently committing all changes can hide tampering, capture secrets into version control, and alter the user's project state.
status = subprocess.check_output(['git', '-C', str(WORKSPACE), 'status', '--porcelain']).decode()
if status:
# If there are uncommitted changes, auto-commit them (backup mode) / Nếu có thay đổi chưa commit, tự động commit (backup kiểu git)
os.system(f"git -C {WORKSPACE} add . && git -C {WORKSPACE} commit -m 'Auto-backup: {os.popen('date').read().strip()}'")
# Kiểm tra crontab
cron = subprocess.check_output(['crontab', '-l']).decode()
The script modifies crontab entries to maintain recurring execution of local scripts, which is a broad persistence capability unrelated to normal one-shot skill operation. In this context, persistence is especially dangerous because the skill has no disclosed justification for installing scheduled tasks and can continue executing after the user stops interacting with it.
This code silently modifies the user's crontab to ensure periodic execution of additional scripts, which is a persistence mechanism. Establishing scheduled execution without user consent is dangerous in a skill context because it survives beyond the original run and can repeatedly execute arbitrary code every 30 minutes.
required = ["backup.py", "health_check.py", "system_watchdog.py"]
for r in required:
if r not in cron:
os.system(f"crontab -l | {{ cat; echo '*/30 * * * * /root/.openclaw/.venv/bin/python /root/.openclaw/scripts/self-healing/{r}'; }} | crontab -")
if __name__ == "__main__": check_integrity()
The YARA match is credible because the code exhibits a classic persistence pattern: appending recurring cron jobs that launch local scripts. Even without an overt payload in this file, persistence in an undisclosed self-healing script is strongly suspicious and materially increases the chance of long-lived unauthorized code execution.
động commit (backup kiểu git)
os.system(f"git -C {WORKSPACE} add . && git -C {WORKSPACE} commit -m 'Auto-backup: {os.popen('date').read().strip()}'")
# Kiểm tra crontab
cron = subprocess.check_output(['crontab', '-l']).decode()
required = ["backup.py", "health_check.py", "system_watchdog.py"]
for r in required:
if r not in cron:
os.system(f"crontab -l | {{ cat; echo '*/30 * * * * /root/.openclaw/.venv/bin/python /root/.openclaw/scripts/self-healing/{r}'; }} | crontab -")
if __name__ == "__main__": check_integrity()
The installation guide instructs users to copy a crontab entry into the system scheduler, creating persistence and ongoing execution, but it does not explain what will run, how often it runs, or the operational and security implications. In the context of an agent skill, undocumented persistence is risky because it can continuously execute code outside normal user awareness and expand the blast radius if the skill is malicious or compromised.
The script modifies a root-owned configuration file and immediately restarts a gateway service without any prompt, dry-run, or rollback path. In an agent or automation context, this can cause unauthorized configuration changes, service disruption, or redirection of model traffic to a newly configured provider without meaningful operator awareness.
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.
"baseUrl": "https://openrouter.ai/api/v1",
"models": [{"id": model_id, "name": model_name}]
}
CONFIG.write_text(json.dumps(data, indent=2))
os.system('openclaw gateway restart')
print(f"Added {model_name} to 9Router.")
This code performs filesystem writes and copies potentially sensitive local data from /root/.openclaw, including the main config and agent directory, into a backup location. While it prints the destination afterward, there is no prior warning, confirmation, or explanatory docstring/comment disclosing that sensitive configuration data will be duplicated.
The health-check reads local Telegram credentials and sends outbound notifications to an external service without clear necessity or user consent. In this context, that expands the trust boundary, creates a covert-ish exfiltration path for operational state, and is more concerning because the skill also performs self-healing actions automatically.
Credential-backed outbound messaging occurs silently and without any user-facing disclosure or approval flow. That is dangerous because it can leak system status and establish unauthorized communications using stored credentials, especially in an agent skill where users may not expect hidden external notifications.
This code transmits data to api.telegram.org using locally stored credentials, creating an external communication channel from a privileged health-check component. In skill context, undisclosed external transmission is more dangerous because it can leak service status and support unauthorized monitoring or abuse if the configuration is tampered with.
cfg[k.strip()] = v.strip()
token, chat_id = cfg.get('BOT_TOKEN'), cfg.get('CHAT_ID')
if token and chat_id:
os.system(f"curl -s 'https://api.telegram.org/bot{token}/sendMessage?chat_id={chat_id}&text={urllib.parse.quote_plus(msg)}' > /dev/null")
def check():
try:
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
def check():
try:
subprocess.check_call(['openclaw', 'gateway', 'status'], timeout=5)
except:
msg = "Meo meo! Gateway is down. Đào is auto-restoring/restarting now!"
send_telegram(msg)
The script blindly copies the newest backup's openclaw.json over the active configuration without validation, integrity checking, backup provenance checks, or operator confirmation. If an attacker can place or modify a backup directory, they could cause restoration of a malicious configuration and the script will immediately apply it by restarting the gateway.
No manifest is available to justify capabilities, so only clearly extra operational actions should be flagged. After restoring a backup config file, the script invokes a system-level command to restart the gateway, which is a broader administrative capability than simple file restoration and is not justified by any stated purpose.
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
]
SKILL_API = 'https://clawhub.ai/api/skills/{slug}'
GITHUB_API = 'https://api.github.com/repos/{owner}/{repo}'
COUNTER = 0
No suspicious patterns detected.