Back to skill

Security audit

Openclaw Auto Backup

Security checks for vulnerabilities and agentic risk

Overview

This backup skill is not clearly malicious, but it needs Review because it can copy sensitive OpenClaw state in plaintext and restore or delete local files while its permissions and path scoping are under-disclosed and buggy.

Review carefully before installing. This skill is local-only in the reviewed scripts and I found no network upload, prompt hijacking, or intentional exfiltration, but it handles sensitive OpenClaw state in plaintext and can overwrite or delete local files. Use only after pinning the installed version, fixing path expansion and restore layout, constraining backupDir/watchFiles to intended OpenClaw paths, and treating cleanup/restore as destructive operations that need confirmation and a fresh backup.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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
Findings (4)

T08 · Insecure Dependencies

Warning
Location
SKILL.md:34
Finding
Unpinned Third-Party Installation Creates Supply-Chain Exposure<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:34-38`, `README.md:46-51`, `README_EN.md:20-25` **Vulnerability Type**: Unpinned external package and repository installation **Risk Level**: Medium ### Vulnerable Code ```bash # SKILL.md # If installed using ClawHub npx clawhub install auto-backup ``` ```bash # README.md npx clawhub install openclaw-auto-backup npx clawhub install openclaw-model-switch npx clawhub install openclaw-memory-enhancer ``` ```bash # README_EN.md cd ~/.openclaw/workspace/skills git clone https://github.com/williamwg2025/openclaw-auto-backup.git auto-backup chmod +x auto-backup/scripts/*.py ``` ### Technical Analysis The installation instructions resolve mutable content from a package registry or the tip of a remote Git repository. They do not specify an exact package version, Git commit, cryptographic checksum, or signature. The additional packages recommended in `README.md` are not necessary for the backup functionality and are outside the scope of this audited artifact. Installing them unnecessarily expands the dependency and execution trust boundary. The project scripts themselves contain no remote retrieval or execution logic. This finding is limited to the documented installation procedure and should not be interpreted as evidence that the current remote sources are malicious. ### Attack Path 1. An attacker compromises a referenced registry account, package release process, repository, or maintainer account. 2. The attacker publishes or commits a modified package containing malicious code. 3. A user follows the unpinned installation instructions. 4. The package manager or Git retrieves the current mutable version rather than the version represented by this audit. 5. The user grants execute permission to the scripts and subsequently invokes the modified code. ### Impact Assessment Successful exploitation would execute code with the privileges of the user installing or invoking the Skill. That account is ex ...[truncated 340 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Pin every ClawHub installation to an exact, reviewed version. - Pin Git installations to a specific commit hash or signed release tag. - Publish and verify SHA-256 checksums or signed release attestations. - Document how users can verify the downloaded artifact before making scripts executable. - Remove recommendations to install unrelated executable packages from the core backup installation instructions. - Require a separate security review whenever the pinned package version or commit changes. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
config/backup-config.json:3
Finding
Configured Home-Directory Paths Are Not Expanded<![CDATA[ ## Vulnerability Details **File Location**: `config/backup-config.json:3-16`, `scripts/backup.py:164-165`, `scripts/list.py:72`, `scripts/cleanup.py:90` **Vulnerability Type**: Unsafe and inconsistent filesystem path normalization **Risk Level**: Medium ### Vulnerable Code ```json { "enabled": true, "backupDir": "~/.openclaw/backups", "maxBackups": 30, "autoBackup": true, "watchFiles": [ "~/.openclaw/openclaw.json", "~/.openclaw/workspace/SESSION-STATE.md", "~/.openclaw/workspace/MEMORY.md", "~/.openclaw/workspace/USER.md", "~/.openclaw/workspace/SOUL.md", "~/.openclaw/workspace/AGENTS.md", "~/.openclaw/workspace/HEARTBEAT.md", "~/.openclaw/workspace/IDENTITY.md", "~/.openclaw/workspace/TOOLS.md", "~/.openclaw/workspace/proactive-tracker.md" ] } ``` ```python # scripts/backup.py backup_dir = Path(config.get('backupDir', str(BACKUP_DIR))) watch_files = config.get('watchFiles', []) ``` ```python # scripts/list.py and scripts/cleanup.py backup_dir = Path(config.get('backupDir', str(OPENCLAW_HOME / "backups"))) ``` ### Technical Analysis Tilde expansion is a shell feature. Constructing a `pathlib.Path` from a string beginning with `~` does not expand it, and `glob.glob()` also does not automatically convert it to the current user's home directory. Consequently: - `Path("~/.openclaw/backups")` is treated as a relative path containing a literal directory named `~`. - The configured watched files are generally not found. - The resulting behavior depends on the process working directory, including the working directory used by cron. - `restore.py` uses the separately hardcoded `Path.home() / ".openclaw" / "backups"` path, so restore may search a different directory from backup, list, and cleanup. This defect can cause the application to report that no files were backed up or place data in an unexpected working-directory-relative location. ### Attack Path 1. A user installs the project with its supp ...[truncated 1075 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Normalize every configured filesystem path before use: ```python def normalize_path(value: str) -> Path: return Path(value).expanduser().resolve() ``` - Apply the same normalization to `backupDir` and every `watchFiles` entry. - Use one shared configuration and path-resolution module in backup, list, cleanup, and restore. - Reject relative paths where configuration is expected to identify a trusted absolute location. - Verify that the resolved backup directory remains beneath an explicitly approved root. - Add integration tests that execute the scripts from arbitrary working directories and under a cron-like environment. - Fail with a nonzero exit status when configured source files cannot be found, rather than silently appearing successful. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/backup.py:101
Finding
Nested Symbolic Links Are Followed During Directory Backup<![CDATA[ ## Vulnerability Details **File Location**: `scripts/backup.py:101-114` **Vulnerability Type**: Symbolic-link traversal during recursive copy **Risk Level**: Medium ### Vulnerable Code ```python elif file_path.is_dir(): # Skip a top-level symbolic-link directory if file_path.is_symlink(): log_warning(f"Skipping symbolic-link directory: {file_path}") continue rel_path = ( file_path.relative_to(OPENCLAW_HOME) if str(file_path).startswith(str(OPENCLAW_HOME)) else file_path.name ) dest_path = backup_path / rel_path if dest_path.exists(): shutil.rmtree(dest_path) # The comment claims this avoids copying symbolic links shutil.copytree(file_path, dest_path, symlinks=False) backed_up.append(str(rel_path)) ``` ### Technical Analysis `shutil.copytree(..., symlinks=False)` follows symbolic links encountered inside the copied directory and copies the contents of their targets. It does not skip them. The top-level `file_path.is_symlink()` check only protects the configured root itself and does not inspect nested entries. The current default `watchFiles` entries are individual files, so the vulnerable directory branch is not reached by the supplied configuration. However, directory backup is implemented as a supported code path and the documentation describes backing up directories such as the Skill directory. If a directory is configured, a nested link can escape the intended backup root. The archive is unencrypted, increasing the sensitivity of any external file copied through such a link. The project contains no network upload mechanism, so exploitation alone does not exfiltrate the resulting archive. ### Attack Path 1. A directory is added to `watchFiles`, or a future configuration enables the documented directory-backup behavior. 2. An attacker who can write inside that directory creates a nested symbolic link to a file or directory outside the approved OpenClaw root. ...[truncated 817 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Do not use `symlinks=False` when the intended policy is to skip links. - Recursively enumerate entries without following links and reject every symbolic link encountered. - Resolve each source path and confirm it remains beneath an approved source root using `Path.is_relative_to()` or `os.path.commonpath()`. - Avoid string-prefix containment checks because sibling paths can share the same textual prefix. - Treat symbolic-link detection as a hard failure when backing up security-sensitive directories. - Protect against check-to-use races by using descriptor-relative filesystem operations where supported. - Add tests covering nested file links, directory links, broken links, link chains, and links changed concurrently during backup. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/restore.py:157
Finding
Backup Archive Layout and Restore Destination Mapping Are Incompatible<![CDATA[ ## Vulnerability Details **File Location**: `scripts/backup.py:134-139`, `scripts/restore.py:157-189` **Vulnerability Type**: Incorrect archive validation and restoration path mapping **Risk Level**: Medium ### Vulnerable Code ```python # scripts/backup.py def compress_backup(backup_path: Path): tar_path = backup_path.with_suffix('.tar.gz') with tarfile.open(tar_path, "w:gz") as tar: tar.add(backup_path, arcname=backup_path.name) shutil.rmtree(backup_path) return tar_path ``` ```python # scripts/restore.py with tarfile.open(backup_file, 'r:gz') as tar: extracted = safe_extract(tar, temp_dir) log_info(f"Safely extracted {len(extracted)} files") manifest_file = temp_dir / "manifest.json" if not manifest_file.exists(): log_error("Backup manifest does not exist (manifest.json)") sys.exit(1) with open(manifest_file, 'r', encoding='utf-8') as f: manifest = json.load(f) for file_info in manifest.get('files', []): if isinstance(file_info, str): rel_path = file_info else: rel_path = file_info.get('path', file_info.get('name', '')) if '..' in rel_path or os.path.isabs(rel_path): log_warning(f"Skipping unsafe path: {rel_path}") continue src = temp_dir / rel_path dst = WORKSPACE / rel_path ``` ### Technical Analysis The backup function adds the complete staging directory to the archive with `arcname=backup_path.name`. The resulting layout is therefore: ```text backup-YYYYMMDD-HHMMSS/ ├── manifest.json └── workspace/ └── ... ``` Restore instead searches for: ```text <temporary-directory>/manifest.json ``` The manifest actually resides beneath the timestamped top-level directory, so restoration terminates before copying files. There is a second mapping error. Backup records paths relative to `OPENCLAW_HOME`, producing values such as `workspace/MEMORY.md`. Restore prefixes those paths with `WORKSPACE`, yielding: ```text ~/.openclaw/workspace/workspace/M ...[truncated 1363 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Define and document one canonical archive layout. - Either archive staging-directory contents at the archive root or explicitly identify and validate one timestamped root during restore. - Store all manifest entries relative to `OPENCLAW_HOME` and restore them beneath `OPENCLAW_HOME`, not beneath `WORKSPACE`. - Build source paths from the validated archive root: ```python archive_root = validated_single_root(temp_dir) manifest_file = archive_root / "manifest.json" src = archive_root / rel_path dst = OPENCLAW_HOME / rel_path ``` - Resolve and validate every source and destination with component-aware containment checks before copying. - Verify manifest schema, entry types, file counts, and optional cryptographic hashes before changing live files. - Make restoration transactional: validate the entire archive first, stage restored files, and only then replace live files. - Add automated round-trip tests that create a backup, remove the original test files, restore the archive, and compare content and paths byte-for-byte. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
Findings (24)

Credential Access

High
Category
Privilege Escalation
Content
```bash
# 方法 1: 在 config/backup-config.json 中排除敏感文件
{
  "excludePatterns": ["*.env", "*.key", "secrets/*"]
}

# 方法 2: 使用外部加密
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Tool Parameter Abuse

High
Category
Tool Misuse
Content
parser.add_argument('--keep', type=int, help='保留最近 N 个备份')
    parser.add_argument('--older-than', type=str, help='删除超过指定天数的备份 (如:30d)')
    parser.add_argument('--dry-run', action='store_true', help='仅显示,不实际删除')
    parser.add_argument('--no-confirm', action='store_true', help='跳过确认')
    args = parser.parse_args()
    
    print_header("🧹 清理备份")
Confidence
86% confidence
Finding
The script accepts backupDir from a JSON config and then allows recursive deletion of matching backup-* files/directories without canonical-path validation or confinement to an expected base directory. Combined with --no-confirm, a tampered config can turn this into unattended destructive deletion of attacker-chosen paths, especially in cron or agent-driven contexts where no human reviews the target first.

Rp1

Medium
Category
MCP Rug Pull
Confidence
70% confidence
Finding
npx commands without a version suffix (e.g. @1.0.0) create a rug-pull risk if the upstream server is compromised and publishes a malicious update.

Rp1

Medium
Category
MCP Rug Pull
Confidence
70% confidence
Finding
npx commands without a version suffix (e.g. @1.0.0) create a rug-pull risk if the upstream server is compromised and publishes a malicious update.

Rp1

Medium
Category
MCP Rug Pull
Confidence
70% confidence
Finding
npx commands without a version suffix (e.g. @1.0.0) create a rug-pull risk if the upstream server is compromised and publishes a malicious update.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
This markdown file documents `restore.py` and `cleanup.py`, including commands that restore configurations and delete old backups, but it does not explicitly warn users about the potential for overwriting current configuration state or permanently removing backup files. Because these behaviors can affect user data and system integrity, the README should disclose them clearly.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The README documents restore commands that can overwrite active configuration files but does not warn users about that destructive side effect or recommend verifying the target version first. In a backup/restore skill, omission of overwrite warnings increases the chance of accidental data loss or rollback to an unsafe state, especially when users may run commands verbatim from the documentation.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
The cleanup examples describe deletion operations without clearly warning that backups may be permanently removed. Because this skill is specifically for backup retention and recovery, undocumented destructive cleanup behavior can mislead users into deleting recoverable versions they may later need, resulting in irreversible loss of backup data.

Lp3

Medium
Category
MCP Least Privilege
Confidence
88% confidence
Finding
The skill documents file read/write behavior but does not declare any explicit tool scope such as permissions or allowed-tools. This creates a transparency and least-privilege problem: an installer or host may not clearly understand that the skill can modify local files, including backups and restores under ~/.openclaw, increasing the chance of overbroad execution in a sensitive filesystem context.

Rp1

Medium
Category
MCP Rug Pull
Confidence
84% confidence
Finding
The installation instructions reference 'npx clawhub' without pinning a specific version. Unpinned package execution can introduce supply-chain risk because future package updates, dependency compromise, or registry hijacking could cause different code to run than what was originally reviewed.

Session Persistence

Medium
Category
Rogue Agent
Content
### 系统 Crontab(可选)
如需使用系统 cron:
```bash
crontab -e
# 添加:0 2 * * * cd ~/.openclaw/workspace/skills/auto-backup && python3 scripts/backup.py --note 定时备份
```
Confidence
85% confidence
Finding
The skill encourages configuring a persistent scheduled task via system crontab, which establishes long-lived execution on the host. Persistence is not inherently malicious in a backup skill, but it is security-relevant because recurring unattended execution can amplify the impact of bugs, path mistakes, or later script tampering.

Intent-Code Divergence

Medium
Confidence
95% confidence
Finding
The documentation says only read access to ~/.openclaw/ is required, but the described operations create archives, restore files, and clean up backups, all of which require write access. Misstating required privileges can mislead reviewers and users, causing unsafe approvals or execution under broader access than expected.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
#### 权限设置
```bash
# 设置备份目录权限(推荐)
chmod 700 ~/.openclaw/backups

# 不要以 root 身份运行
# 以普通用户身份运行备份
Confidence
80% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
This configuration enables automatic backups of multiple potentially sensitive workspace and identity files while explicitly disabling encryption. Although there is a note stating encryption is unsupported, the warning is not user-facing in any enforceable way and the backups are directed to local storage by default, which can expose secrets, session state, and personal data if the host is compromised or backups are shared.

Description-Behavior Mismatch

Medium
Confidence
95% confidence
Finding
The backup logic accepts config-supplied glob patterns and plain paths, then copies any matching file or directory from the local filesystem. Although the code skips top-level symlinks, it does not restrict sources to OpenClaw-owned configuration paths, so a modified or overly broad config can collect unrelated sensitive files such as SSH keys, shell history, or other application secrets into backup archives.

Context-Inappropriate Capability

Medium
Confidence
90% confidence
Finding
The script fully trusts backupDir and watchFiles from the JSON config, enabling config-driven collection of arbitrary files and directories and placement of backups in arbitrary writable locations. In the context of a configuration-backup skill, this is broader than necessary and increases the blast radius if the config is tampered with, because the skill can exfiltration-stage or persist copies of sensitive local data.

Natural-Language Policy Violations

Medium
Confidence
97% confidence
Finding
This code presents warnings, prompts, and argument descriptions in Chinese only, including the destructive-deletion confirmation flow. The policy requires avoiding forced language or locale behavior unless the user is given a choice or the constraint is clearly documented and justified.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
parser.add_argument('--keep', type=int, help='保留最近 N 个备份')
    parser.add_argument('--older-than', type=str, help='删除超过指定天数的备份 (如:30d)')
    parser.add_argument('--dry-run', action='store_true', help='仅显示,不实际删除')
    parser.add_argument('--no-confirm', action='store_true', help='跳过确认')
    args = parser.parse_args()
    
    print_header("🧹 清理备份")
Confidence
65% confidence
Finding
Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Natural-Language Policy Violations

Medium
Confidence
96% confidence
Finding
This code file contains multiple user-facing strings in Chinese, including the header, status messages, and usage hints. Under the policy, forcing a specific language without user opt-in is a natural-language policy violation unless the locale restriction is clearly documented and justified, which is not present here.

Natural-Language Policy Violations

Low
Confidence
88% confidence
Finding
The main README content is written in Chinese, while English is relegated to a separate linked document rather than offering language choice inline. This can be interpreted as a language/locale policy issue if users are expected to consume the skill documentation in a specific language without explicit opt-in.

Description-Behavior Mismatch

Low
Confidence
76% confidence
Finding
The manifest-level description emphasizes a fully self-contained local backup skill with '无网络' and '无外部依赖'. However, the same file advertises an external GitHub repository, which weakens the claimed self-contained/local-only framing of the skill package. This is a mild description mismatch because the documented operational scope is local-only while the distribution/source story includes an external dependency surface.

Natural-Language Policy Violations

Low
Confidence
93% confidence
Finding
The natural-language note at L28 is written only in Chinese and does not indicate any language-selection or localization mechanism. This can violate language/locale policy when users are not explicitly opted into that locale.

Natural-Language Policy Violations

Low
Confidence
93% confidence
Finding
The usage text and operational note are presented only in Chinese, and the rest of the script continues this pattern for user-visible output. Under the policy rule for natural-language violations, forcing a specific language without user opt-in can exclude users who do not read that language.

Natural-Language Policy Violations

Low
Confidence
87% confidence
Finding
The top-level docstring presents core usage and security information in Chinese, including the restore description and security note, without indicating any language option or locale-specific justification. Under the policy, forcing a specific language without user opt-in is a natural-language policy issue.

Static analysis

No suspicious patterns detected.