Back to skill

Security audit

OpenClaw State Backup

Security checks for vulnerabilities and agentic risk

Overview

This is a coherent OpenClaw backup tool, but its restore script can let a crafted archive write files outside the intended OpenClaw state paths.

Install only if you trust the backup archives you will restore. Use verify and dry-run, but do not rely on them for untrusted archives until restore_state.py validates tar members and enforces destination containment. Treat generated backups as sensitive because they may include memory, session data, local skills, host metadata, and OpenClaw configuration.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/restore_state.py:214
Finding
Unsafe Archive Extraction and Restore Path Traversal<![CDATA[ ## Vulnerability Details **File Location**: `scripts/restore_state.py:57-68`, `scripts/restore_state.py:214-217`, and `scripts/restore_state.py:248-257` **Vulnerability Type**: Unsafe archive extraction and insufficient destination-path validation **Risk Level**: High ### Vulnerable Code ```python def archive_to_dest(rel: str, workspace: Path, state_dir: Path) -> Path: rel_path = Path(rel) parts = rel_path.parts if len(parts) < 3: raise RuntimeError(f"Unexpected archive path: {rel}") scope = parts[1] remainder = Path(*parts[2:]) if scope == "workspace": return workspace / remainder if scope == "state": return state_dir / remainder raise RuntimeError(f"Unknown archive scope: {scope}") ``` ```python with tempfile.TemporaryDirectory(prefix="openclaw-restore-") as td: tmpdir = Path(td) with tarfile.open(archive, "r:gz") as tar: tar.extractall(tmpdir) ``` ```python rollback_archive = make_rollback(workspace, state_dir, rollback_dir, include_prefixes, exclude_prefixes) restored_paths = [] for item in verified_files: src = tmpdir / item["path"] dst = archive_to_dest(item["path"], workspace, state_dir) dst.parent.mkdir(parents=True, exist_ok=True) shutil.copy2(src, dst) restored_paths.append(str(dst)) ``` ### Technical Analysis The restore operation passes every archive member to `tar.extractall()` before validating the archive manifest, member types, or member paths. On Python versions or configurations that do not enforce a safe extraction filter, malicious absolute paths, parent-directory components, symbolic links, hard links, or special archive entries can cause extraction outside the temporary directory. There is a second, independent path-containment issue in `archive_to_dest()`. The function checks the archive scope but does not reject `..` components, absolute remainders, or resolved paths outside the selected workspace or state directory. For example, ...[truncated 2091 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. **Validate every archive member before extraction** - Reject absolute paths. - Reject paths containing `..`. - Reject symbolic links, hard links, device nodes, FIFOs, and other non-regular entries. - Reject duplicate members and files not declared in the manifest. - Require member names to follow one of the exact permitted layouts: - `mutable/workspace/...` - `mutable/state/...` - `static/workspace/...` 2. **Use safe extraction controls** - On supported Python versions, use an appropriate safe extraction filter. - Retain explicit path and type validation because runtime defaults vary. - Prefer extracting validated regular files individually instead of calling unrestricted `extractall()`. 3. **Enforce source and destination containment** - Resolve the candidate path and its intended root. - Require the candidate to remain under that root using `Path.is_relative_to()` or an equivalent containment check. - Apply this validation both to temporary extraction paths and final restore destinations. ```python def contained_path(root: Path, relative: Path) -> Path: root = root.resolve() if relative.is_absolute() or ".." in relative.parts: raise RuntimeError(f"Unsafe relative path: {relative}") candidate = (root / relative).resolve() if not candidate.is_relative_to(root): raise RuntimeError(f"Path escapes allowed root: {relative}") return candidate ``` 4. **Validate before any filesystem write** - Load the manifest without extracting arbitrary archive content. - Validate its schema, path prefixes, unique entries, checksums, and member correspondence. - Only after successful structural validation should regular files be written to the temporary directory. 5. **Add adversarial tests** - Test absolute member names and `../` traversal. - Test traversal in manifest paths. - Test symbolic-link and hard-link entries. - Test duplicate ...[truncated 121 chars]
Vulnerability Patterns
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
  • 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
Findings (10)

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The skill advertises backup, inspection, restore, rollback safety, and coverage of gateway/cron-related state, but the static finding indicates the actual implementation may not provide those protections or capabilities. This mismatch is dangerous because operators may rely on restore validation or rollback guarantees during recovery, and a failed or partial implementation can cause data loss, incomplete restores, or false confidence in backup integrity.

Description-Behavior Mismatch

High
Confidence
98% confidence
Finding
archive_to_dest derives the restore destination directly from archive metadata and joins the remainder onto workspace/state_dir without checking that the resolved path stays within those roots. If the archive path contains traversal segments like ../../, the restore loop can copy verified content to arbitrary filesystem locations, defeating the claimed rollback safety and enabling destructive overwrite of files accessible to the restoring user.

Context-Inappropriate Capability

High
Confidence
99% confidence
Finding
The code calls tar.extractall(tmpdir) on an attacker-controlled archive without validating member paths or link types. A malicious tar can use path traversal entries or symlinks/hardlinks so extraction writes outside tmpdir, potentially planting or overwriting files before manifest verification even runs.

Session Persistence

Medium
Category
Rogue Agent
Content
- `SKILL.md` — skill instructions for OpenClaw / ClawHub
- `README.md` — human-facing project overview
- `LICENSE` — MIT license
- `scripts/backup_state.py` — create versioned backup archives
- `scripts/restore_state.py` — verify, diff, and restore archives

## What gets backed up
Confidence
60% 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.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
This markdown file documents a restore command that writes archived files back into the active OpenClaw state, affecting runtime, memory, workspace, and skill files. Although the README explains rollback snapshots and that unrelated files are not deleted, it does not give a direct user warning near the restore command that running it will overwrite current files and may revert recent changes.

Lp3

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding
The skill describes operations that read and write substantial local state, including user workspace files and OpenClaw runtime data, but it does not declare an explicit tool scope or permissions boundary. In an agent environment, missing scope declarations can allow the skill to be invoked with broader file access than reviewers or users expect, increasing the chance of unintended backup, overwrite, or restoration of sensitive files.

Session Persistence

Medium
Category
Rogue Agent
Content
# OpenClaw State Backup

Create and restore **versioned, restorable snapshots** of mutable OpenClaw state.

## What changes over time
Confidence
88% confidence
Finding
The skill is explicitly designed to persist and restore session state, memory databases, agent runtime state, and workspace memory artifacts. Even if intended for legitimate backup, this creates a real security concern because it consolidates sensitive operational history and agent context into archives that could expose secrets, personal data, or internal state if stored insecurely, copied elsewhere, or restored from untrusted sources.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
This code performs a rollback snapshot and then copies archived files into the target workspace and state directories, potentially overwriting existing data. Although a report is printed after completion, there is no confirmation prompt, pre-action warning print, or inline comment/docstring disclosing that the restore is a destructive write operation.

Context-Inappropriate Capability

Low
Confidence
83% confidence
Finding
The version-detection helper reads package.json from locations outside the provided workspace and state directories, including a global NVM-managed module path and a home-directory installation path. For a skill presented as a state backup/restore tool, inspecting unrelated installation locations is an extra capability not obviously required to back up declared state.

Description-Behavior Mismatch

Low
Confidence
92% confidence
Finding
The backup manifest records hostname, platform, Python version, workspace path, state directory path, and detected OpenClaw version in addition to the actual backup contents. While this is likely intended for restore/debug compatibility, it expands the sensitivity of the archive and can disclose host-identifying and filesystem layout information if the backup is shared, uploaded, or exfiltrated.

Static analysis

No suspicious patterns detected.