Back to skill

Security audit

claw-backup

Security checks for vulnerabilities and agentic risk

Overview

The skill is a disclosed OpenClaw backup and restore tool, but it deserves review because it bundles sensitive workspace state into a plaintext archive and has restore/archive handling weaknesses.

Install only if you are comfortable creating a portable, unencrypted copy of sensitive OpenClaw memory, identity, user, tool, and skill files. Store backups in a private location, encrypt them yourself, avoid restoring ZIP files from untrusted sources, and review workspaces for symlinks before backing them up.

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 (3)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/backup.py:20
Finding
Unencrypted backup archive contains sensitive Agent state<![CDATA[ ## Vulnerability Details **File Location**: `scripts/backup.py:20-30`, `scripts/backup.py:103-123`, and `scripts/backup.py:151-158` **Vulnerability Type**: Plaintext storage of sensitive data **Risk Level**: Medium ### Vulnerable Code ```python REQUIRED_ITEMS = [ "MEMORY.md", "memory", "skills", "SOUL.md", "IDENTITY.md", "USER.md", "AGENTS.md", "TOOLS.md", "HEARTBEAT.md", ] ``` ```python # 添加必选内容 manifest_lines.append("🟢 REQUIRED ITEMS:") for item in REQUIRED_ITEMS: src_path = WORKSPACE / item if src_path.exists(): if src_path.is_file(): files_to_backup.append((src_path, item)) file_hash = get_file_hash(src_path) manifest_lines.append(f" [FILE] {item} ({file_hash[:16]}...)") else: for root, dirs, files in os.walk(src_path): for file in files: file_path = Path(root) / file rel_path = file_path.relative_to(WORKSPACE) files_to_backup.append((file_path, str(rel_path))) manifest_lines.append(f" [DIR] {item}/") else: manifest_lines.append(f" [SKIP] {item} (不存在)") ``` ```python # 创建压缩包 with zipfile.ZipFile(backup_zip, "w", zipfile.ZIP_DEFLATED) as zipf: for src_path, arc_name in files_to_backup: try: zipf.write(src_path, f"{backup_name}/{arc_name}") except Exception as e: print(f"⚠️ 备份失败 {arc_name}: {e}") ``` ### Technical Analysis The required backup set includes long-term memory, user information, identity definitions, tool configuration, installed Skills, and Agent configuration. These files may contain private data, API credentials, operational instructions, or other sensitive state. The files are stored using `zipfile.ZIP_DEFLATED`, which provides compression but no confidentiality. The project documentation explicitly warns that the archive is unencrypted, but the implementation does not offer ...[truncated 1598 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Add authenticated encryption rather than relying on ZIP compression. Use a maintained encryption format or library with modern password-based key derivation and authenticated encryption. 2. Require explicit confirmation before including memory, user identity, tool configuration, or other highly sensitive categories. 3. Provide a secret-exclusion mechanism for known credential files and sensitive patterns. 4. Create the destination file with restrictive permissions, such as owner read/write only, independently of the user's current `umask`. 5. Warn when the output directory is synchronized, shared, or broadly accessible. 6. Avoid placing sensitive archives on the Desktop by default; prefer a dedicated private backup directory. 7. Clearly distinguish the SHA-256 integrity digest from encryption in user-facing output. 8. Consider generating a redacted manifest that does not reveal sensitive file names or metadata unnecessarily. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/backup.py:113
Finding
Symbolic links can cause files outside the workspace to be archived<![CDATA[ ## Vulnerability Details **File Location**: `scripts/backup.py:113-120` and `scripts/backup.py:134-141` **Vulnerability Type**: Symbolic-link traversal and unintended file disclosure **Risk Level**: Medium ### Vulnerable Code The same unsafe traversal logic is used for required and optional directories: ```python else: for root, dirs, files in os.walk(src_path): for file in files: file_path = Path(root) / file rel_path = file_path.relative_to(WORKSPACE) files_to_backup.append((file_path, str(rel_path))) manifest_lines.append(f" [DIR] {item}/") ``` ```python else: for root, dirs, files in os.walk(src_path): for file in files: file_path = Path(root) / file rel_path = file_path.relative_to(WORKSPACE) files_to_backup.append((file_path, str(rel_path))) manifest_lines.append(f" [DIR] {item}/") ``` The queued paths are subsequently opened by `zipfile.write()`: ```python with zipfile.ZipFile(backup_zip, "w", zipfile.ZIP_DEFLATED) as zipf: for src_path, arc_name in files_to_backup: try: zipf.write(src_path, f"{backup_name}/{arc_name}") except Exception as e: print(f"⚠️ 备份失败 {arc_name}: {e}") ``` ### Technical Analysis The script verifies only that the directory entry's lexical path is relative to `WORKSPACE`. It does not use `lstat()` to identify symbolic links and does not resolve each file before checking containment. A symbolic link can therefore have a path located under a backed-up directory while its target is outside the workspace. When `zipfile.write()` processes a file symlink, it can read the target's contents and place those contents in the archive under the symlink's workspace-relative name. Although `os.walk()` does not follow linked directories by default, linked files encountered in the `files` list remain relevant. Consequently, an attacker able to create or influence files within a re ...[truncated 1373 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Inspect every directory entry with `Path.lstat()` and reject symbolic links by default. 2. Resolve both the workspace root and every source path before adding a file: ```python workspace_root = WORKSPACE.resolve() resolved_file = file_path.resolve(strict=True) if not resolved_file.is_relative_to(workspace_root): raise ValueError(f"Source escapes workspace: {file_path}") ``` 3. Perform the containment check immediately before opening the file to reduce time-of-check/time-of-use exposure. 4. If preserving symbolic links is required, archive link metadata without dereferencing the target and clearly mark links in the manifest. 5. Apply the same validation to individual required and optional items, because a top-level item itself could be a symbolic link. 6. Consider opening files with platform-supported no-follow semantics, such as `O_NOFOLLOW`, where available. 7. Log and abort on unexpected symlinks rather than silently including or skipping sensitive content. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/restore.py:50
Finding
Unbounded ZIP restoration permits memory and disk exhaustion<![CDATA[ ## Vulnerability Details **File Location**: `scripts/restore.py:50-94` **Vulnerability Type**: Uncontrolled resource consumption during archive extraction **Risk Level**: Medium ### Vulnerable Code ```python restored_count = 0 with zipfile.ZipFile(backup_path, "r") as zipf: # 找到根目录 names = zipf.namelist() root_dir = names[0].split("/")[0] if names else "" for name in names: if name.endswith("/"): continue # 跳过根目录前缀 rel_path = name[len(root_dir) + 1:] if root_dir else name # 跳过清单文件 if rel_path == "BACKUP_MANIFEST.txt": continue # 🔒 安全修复:防止 ZipSlip 攻击 # 检查路径是否包含 ".." 或绝对路径 if ".." in rel_path or rel_path.startswith("/"): print(f" ⚠️ 跳过可疑路径:{rel_path}") continue # 确保目标路径在工作区内 target_path = (WORKSPACE / rel_path).resolve() if not str(target_path).startswith(str(WORKSPACE.resolve())): print(f" ⚠️ 跳过路径穿越攻击:{rel_path}") continue # 创建父目录 target_path.parent.mkdir(parents=True, exist_ok=True) # 解压文件 with zipf.open(name) as src: with open(target_path, "wb") as dst: dst.write(src.read()) restored_count += 1 print(f" ✓ {rel_path}") ``` ### Technical Analysis The restore process places no limits on: - The number of archive entries. - The declared or actual uncompressed size of an entry. - The cumulative extracted size. - The compression ratio. - Available disk space. - Extraction duration. Additionally, `src.read()` reads the entire uncompressed entry into memory before writing it. A highly compressed entry with a very large expanded size can therefore exhaust process memory. Multiple large entries can fill the filesystem even if individual entries fit in memory. The path checks address some directory-traversal cases but do not mitigate re ...[truncated 1477 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Inspect every `ZipInfo` entry before extraction and reject archives exceeding configured limits. 2. Enforce limits for: - Maximum entry count. - Maximum uncompressed size per entry. - Maximum cumulative uncompressed size. - Maximum allowed compression ratio. - Maximum path length. 3. Stream each entry in bounded chunks rather than calling `src.read()` without a size: ```python with zipf.open(info) as src, open(target_path, "wb") as dst: while chunk := src.read(1024 * 1024): dst.write(chunk) ``` 4. Track the actual number of bytes emitted while streaming and abort immediately when a limit is exceeded. 5. Check available disk space before extraction and retain a safety margin. 6. Extract into a private staging directory first, validate the completed result, and only then replace workspace files. 7. Remove partial staging data on failure. 8. Replace string-prefix path validation with component-aware containment: ```python workspace_root = WORKSPACE.resolve() target_path = (workspace_root / rel_path).resolve() if not target_path.is_relative_to(workspace_root): continue ``` ]]>
Vulnerability Patterns
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
Findings (9)

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
This is a significant description-behavior mismatch: the skill claims to perform backup with ZIP and SHA256 validation, but analysis indicates restore/overwrite behavior, desktop ZIP enumeration, and failure to generate the promised archive or checksum. For a skill operating on sensitive workspace state, hidden write/restore behavior can cause data loss, unauthorized modification, and user deception about what actions are being taken.

Description-Behavior Mismatch

High
Confidence
98% confidence
Finding
The skill manifest says the skill is for one-click backup of the OpenClaw workspace, generating a ZIP archive with SHA256 verification. This file instead restores files from a ZIP into the workspace and can overwrite existing files, which is a materially different operation from backup creation.

Lp3

Medium
Category
MCP Least Privilege
Confidence
76% confidence
Finding
The skill advertises file-backed behavior and references backup/restore scripts, but the manifest declares no explicit tool scope or permissions. In a backup skill that handles sensitive workspace data, missing scope declarations weakens reviewability and can allow broader file access than users or the platform expect.

Vague Triggers

Medium
Confidence
89% confidence
Finding
The trigger phrase '备份工作区' is broad enough to match ordinary user requests that may not specifically intend this skill. Because the skill can access and package highly sensitive files such as memory, identity, agent, and tool configuration, accidental activation could expose or manipulate confidential workspace data.

Vague Triggers

Medium
Confidence
92% confidence
Finding
The trigger phrases '导出配置' and '打包技能' are ambiguous and may activate the skill for requests that only concern a subset of files or a different packaging task. In this context, overbroad activation is more dangerous because the documented backup scope includes sensitive memory, identity, and tooling files, increasing the chance of unintended disclosure or destructive actions.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
This code creates a backup archive on the user's Desktop and includes numerous workspace files and directories, some of which may contain sensitive user or system data. Although the script prints status messages, it does not clearly warn the user about the privacy impact of writing a consolidated backup archive to a broadly accessible location.

Natural-Language Policy Violations

Medium
Confidence
97% confidence
Finding
This code file contains natural-language strings and documentation entirely in Chinese, including the top-level description and user prompts. Under the policy rule, forcing a specific language without opt-in is a locale/language policy violation unless the constraint is explicitly justified or the user is offered a choice.

Description-Behavior Mismatch

Medium
Confidence
96% confidence
Finding
The restore flow extracts and writes files from a ZIP archive without verifying the advertised SHA256 integrity/authenticity first. An attacker who can replace or tamper with a backup archive could cause restoration of malicious or corrupted files into the OpenClaw workspace, undermining trust in the backup process.

Natural-Language Policy Violations

Low
Confidence
97% confidence
Finding
The script's docstrings, prompts, help text, and status messages are presented in Chinese only, which imposes a specific language on users without offering a choice. This matches the language/locale policy violation criteria because no opt-in or documented locale constraint is provided.

Static analysis

No suspicious patterns detected.