Back to skill

Security audit

Openclaw User Data Pack

Security checks for vulnerabilities and agentic risk

Overview

This skill is a disclosed OpenClaw backup/restore tool, but it needs review because it can overwrite agent state and its exporter can unintentionally include files outside the workspace through symlinks.

Install only if you need OpenClaw backup/restore and are prepared to review every dry-run and manifest. Avoid using it on untrusted workspaces with symlinks, do not restore archives from untrusted sources, back up the target workspace and OpenClaw home first, and approve sessions/config restore only when you accept transcript and secret exposure risks. Do not run publish_npm.py as part of normal use.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/pack_openclaw.py:21
Finding
Workspace symlinks can disclose files outside the authorized export root## Vulnerability Details **File Location**: `scripts/pack_openclaw.py:21-28`, `scripts/pack_openclaw.py:58-67`, and `scripts/pack_openclaw.py:165-170` **Vulnerability Type**: Missing symlink and source-path containment validation **Risk Level**: High ### Vulnerable Code ```python def iter_files(root: Path, *, exclude_git: bool) -> Iterable[Path]: root = root.resolve() always_skip = {"__pycache__", ".venv", "node_modules"} for dirpath, dirnames, filenames in os.walk(root): remove = [d for d in dirnames if d in always_skip or (d == ".git" and exclude_git)] for d in remove: dirnames.remove(d) for name in filenames: yield Path(dirpath) / name ``` ```python def plan_workspace(root: Path, *, exclude_git: bool) -> PackPlan: plan = PackPlan() root = root.resolve() if not root.is_dir(): plan.warnings.append(f"missing workspace: {root}") return plan prefix = "workspace/" for f in iter_files(root, exclude_git=exclude_git): if not f.is_file(): continue try: rel = f.relative_to(root) except ValueError: continue arc = prefix + rel.as_posix() plan.entries.append((f, arc)) return plan ``` ```python for abs_path, arcname in entries: if not abs_path.is_file(): continue if arcname in seen: continue seen.add(arcname) zf.write(abs_path, arcname) ``` ### Technical Analysis The exporter confirms only that the directory entry appears beneath the workspace path. It does not reject symbolic links or verify that the resolved target of every source file remains beneath the resolved workspace root. `Path.is_file()` follows symbolic links. Likewise, `ZipFile.write()` opens the referenced file and archives its contents. Consequently, a symbolic link located inside the workspace can point to ...[truncated 2117 chars]
Remediation
## Remediation Suggestions 1. Reject all symbolic-link source entries before adding them to a plan: ```python if f.is_symlink() or not f.is_file(): continue ``` 2. Resolve every candidate and enforce containment under the authorized root: ```python root_resolved = root.resolve() source_resolved = f.resolve(strict=True) try: source_resolved.relative_to(root_resolved) except ValueError: plan.warnings.append(f"outside source root via symlink: {f}") continue ``` 3. Apply equivalent checks to workspace files, managed skills, and session files. 4. Prefer descriptor-based file opening with no-follow semantics, such as `O_NOFOLLOW` on supported platforms, and archive from the validated descriptor to reduce time-of-check/time-of-use attacks. 5. Report every skipped symlink prominently in both dry-run and real execution. 6. Add tests covering file symlinks to external files, broken symlinks, symlink replacement races, and links targeting `~/.openclaw/credentials/`.

T09 · Insecure Skill Coding Practices

Note
Location
scripts/apply_openclaw.py:229
Finding
Apply dry-run creates the target workspace directory## Vulnerability Details **File Location**: `scripts/apply_openclaw.py:229-232` **Vulnerability Type**: Preview mode performs an undocumented filesystem write **Risk Level**: Low ### Vulnerable Code ```python if args.workspace is not None: ws = args.workspace.expanduser().resolve() ws.mkdir(parents=True, exist_ok=True) else: ws = resolve_workspace(workspace=None, openclaw_home_dir=home, config_path=cfg_path) ``` The dry-run check occurs only later: ```python if args.dry_run: for arc, layer, dest in sorted(ops, key=lambda x: x[2].as_posix()): print(f" [{layer}] {arc} -> {dest}") return ``` ### Technical Analysis When an explicit workspace does not exist, the script calls `mkdir(parents=True, exist_ok=True)` before checking `args.dry_run`. This creates the complete target directory hierarchy during an operation documented as printing planned writes only. The behavior directly contradicts the Skill instruction that dry-run does not change disk. It weakens the informed-consent boundary because users are told that preview is non-mutating. Directory creation is constrained to a user-supplied path and does not, by itself, write archive contents or overwrite files. The security impact is therefore limited, but the discrepancy can affect automation and sensitive filesystem locations writable by the invoking account. ### Attack Path 1. A user or Agent invokes `apply_openclaw.py` with `--dry-run` and an explicit, nonexistent `--workspace` path. 2. The script resolves the supplied target. 3. Before evaluating the dry-run branch, it calls `mkdir()` with `parents=True`. 4. The requested directory and any missing parent directories are created despite the preview-only request. 5. Automation relying on dry-run as a side-effect-free validation step observes an unexpected filesystem change. ### Impact Assessment The script can create directories anywhere the invoking account has ...[truncated 291 chars]
Remediation
## Remediation Suggestions Defer directory creation until the script has completed validation and is about to perform a real extraction: ```python if args.workspace is not None: ws = args.workspace.expanduser().resolve() if ws.exists() and not ws.is_dir(): raise SystemExit(f"workspace is not a directory: {ws}") else: ws = resolve_workspace( workspace=None, openclaw_home_dir=home, config_path=cfg_path, ) # Build and display the plan first. if args.dry_run: for arc, layer, dest in sorted(ops, key=lambda x: x[2].as_posix()): print(f" [{layer}] {arc} -> {dest}") return ws.mkdir(parents=True, exist_ok=True) ``` Add a regression test that runs dry-run against a nonexistent workspace and verifies that neither the workspace nor its missing parents are created.

T08 · Insecure Dependencies

Warning
Location
requirements.txt:1
Finding
Agent instructions install a mutable, unpinned third-party dependency## Vulnerability Details **File Location**: `requirements.txt:1-2`; installation is instructed by `SKILL.md:24`, `SKILL.md:35`, `SKILL.md:57`, and `SKILL.md:187` **Vulnerability Type**: Unpinned dependency without integrity verification **Risk Level**: Medium ### Vulnerable Code ```text # Optional: parse ~/.openclaw/openclaw.json (JSON5 with comments) json5>=0.9.0 ``` The Skill directs the Agent to install it with: ```text pip install -r requirements.txt ``` ### Technical Analysis The version range `json5>=0.9.0` permits any current or future release satisfying the lower bound. No upper bound, exact version, package hash, lock file, or authenticated internal package source is specified. Python package installation and subsequent import place trust in package artifacts that may change after this Skill has been reviewed. If the dependency, maintainer account, package index, or resolution environment is compromised, the Agent can install code that was not part of the audited project. This is a supply-chain weakness rather than evidence that the current `json5` package is malicious. The dependency is also optional because users can supply `--workspace` explicitly or use strict JSON configuration, making unconditional or eager installation broader than necessary. ### Attack Path 1. A future dependency release or resolved package artifact is compromised, or a package source used by the environment is malicious. 2. The Agent follows `SKILL.md` and runs `pip install -r requirements.txt`. 3. Because the requirement accepts every version at or above `0.9.0`, the installer selects the compromised artifact. 4. Malicious package behavior may execute during installation or when `openclaw_paths.py` imports `json5`. 5. The package executes with the privileges of the Agent process and can access the same workspace, OpenClaw files, credentials, and network resources available to that account. ### Impact Assessment Succes ...[truncated 421 chars]
Remediation
## Remediation Suggestions 1. Pin `json5` to a specifically reviewed version rather than using an open-ended lower bound. 2. Generate a hash-locked requirements file and install with hash enforcement: ```text json5==REVIEWED_VERSION --hash=sha256:REVIEWED_DISTRIBUTION_HASH ``` ```bash python -m pip install --require-hashes -r requirements.txt ``` 3. Use a reproducible lock-file process and review dependency updates before changing the pinned version or hashes. 4. Prefer binary wheels from a trusted index where available, and explicitly configure the approved package source to reduce dependency-source ambiguity. 5. Change the instructions so installation is attempted only after strict JSON parsing fails and only when an explicit workspace path is not available. 6. Document `--workspace` as the dependency-free alternative.
Vulnerability Patterns
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Memory PoisoningPersistent Context Injection, Context Window Stuffing, Memory Manipulation
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
Findings (28)

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
The skill promises safeguards such as reading EXPORT_MANIFEST.txt and a dry-run-first workflow, but the text itself shows these are agent instructions rather than enforced controls in the scripts. If the runtime or operator follows only the script interface, destructive apply operations including config overwrite can occur without mandatory preview or manifest validation, creating a misleading safety model around sensitive data restoration.

Tp4

High
Category
MCP Tool Poisoning
Confidence
93% confidence
Finding
The skill promises safeguards such as reading EXPORT_MANIFEST.txt and a dry-run-first workflow, but the text itself shows these are agent instructions rather than enforced controls in the scripts. If the runtime or operator follows only the script interface, destructive apply operations including config overwrite can occur without mandatory preview or manifest validation, creating a misleading safety model around sensitive data restoration.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The skill promises safeguards such as reading EXPORT_MANIFEST.txt and a dry-run-first workflow, but the text itself shows these are agent instructions rather than enforced controls in the scripts. If the runtime or operator follows only the script interface, destructive apply operations including config overwrite can occur without mandatory preview or manifest validation, creating a misleading safety model around sensitive data restoration.

Memory Manipulation

High
Category
Memory Poisoning
Content
- Assume the archive may hold sensitive material: persona, `MEMORY.md`, logs, workspace skills; with optional layers, session JSONL and `openclaw.json` (secrets, channels).
- **Do not** pack or encourage packing `~/.openclaw/credentials/`. Apply never writes credentials; tell the user they must re-login / re-pair on a new machine unless they consciously accept copying secrets (you still do not pack credentials via these scripts).
- Warn against putting the zip on untrusted or public storage.
- **Overwrite rule:** same path ⇒ destination file replaced. Same path ≠ same meaning. Only `openclaw.json` gets a `.bak.<timestamp>` when using `--apply-config`; **other paths are not auto-backed up.**

### Merge and conflicts (your work; not in scripts)
Confidence
93% confidence
Finding
The skill explicitly supports replacing memory, persona, skills, workspace files, sessions, and potentially config by path, with minimal automatic backup outside openclaw.json. Even though the text warns about consent and conflicts, the core operation is still destructive overwrite of sensitive agent state, which could permanently alter behavior, memory, or trust material if a malicious or incorrect archive is applied.

Ae1

High
Category
analysis-evasion
Content
- **Skills (`SKILL.md` etc.):** divergent purpose or triggers ⇒ **do not** pick a winner alone; offer keep local / take zip / merge / rename path so both can ex
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Description-Behavior Mismatch

High
Confidence
98% confidence
Finding
The file's purpose is release publication to npm/ClawHub, which materially diverges from the skill's declared user-data pack/apply behavior. In a security review, capability mismatch is a strong indicator of hidden or unjustified functionality because users of this skill would not reasonably expect code that modifies versions and publishes packages externally.

Context-Inappropriate Capability

High
Confidence
99% confidence
Finding
The code combines external publishing with arbitrary local hook execution, neither of which is justified by the skill's stated purpose. In agent ecosystems, that combination is highly dangerous because it enables both local code execution and outbound transfer/publication paths, creating opportunities for secret theft, persistence, or unauthorized release of local contents.

Tool Parameter Abuse

High
Category
Tool Misuse
Content
shell=False,
                    )
                else:
                    subprocess.run([p, version, changelog], cwd=ROOT, shell=True)
                print(f"ran hook: {name}")
                return
    else:
Confidence
99% confidence
Finding
Using `shell=True` to run a repository-local `.cmd` or `.bat` hook is a direct tool-parameter abuse pattern because it delegates execution semantics to the shell and trusts script content from the repo. In the context of this skill, it creates an unjustified arbitrary command execution path that could be triggered during what appears to be a routine release or packaging step.

Lp3

Medium
Category
MCP Least Privilege
Confidence
90% confidence
Finding
The skill instructs the agent to run shell commands, read files, inspect archives, and apply filesystem changes, but the metadata shown does not declare any tool scope or allowed-tools boundaries. In an agent environment, missing capability constraints increases the chance the skill is granted broader execution or file access than intended, especially because it includes write and shell operations.

Vague Triggers

Medium
Confidence
93% confidence
Finding
The trigger list includes broad phrases like backup, export memory, import zip, and restore requests in multiple languages without clear exclusions. Overbroad activation can cause this high-impact skill to engage in contexts where the user did not intend filesystem packaging or restoration, increasing the risk of unnecessary archive handling or overwrite guidance.

Session Persistence

Medium
Category
Rogue Agent
Content
## Your job in one sentence

Use `scripts/pack_openclaw.py` and `scripts/apply_openclaw.py` from this skill to export or restore workspace data (and optional layers **only** if the user clearly opts in after you warn them). **You** own preview, collision handling, and consent—the scripts only write files by path.

---
Confidence
88% confidence
Finding
The skill enables export and restore of session-related data as an optional layer and states that scripts write files by path. Restoring sessions can reintroduce transcripts and historical state into the agent environment, which is sensitive from both privacy and persistence perspectives and could expose prior conversations or rehydrate unwanted context.

Session Persistence

Medium
Category
Rogue Agent
Content
## Paths (how you resolve them)

- OpenClaw home: `$OPENCLAW_HOME` or `~/.openclaw`; Windows: `%USERPROFILE%\.openclaw`.
- Pack: if `--workspace` omitted, script reads config. Apply: `--workspace` may create the dir; if omitted, config must parse. On a fresh machine, prefer `openclaw onboard` or pass `--workspace` explicitly.
- Run pack and apply in the **same** environment family (e.g. both WSL) so paths mean the same thing.

---
Confidence
72% confidence
Finding
The path guidance indicates apply may create target directories and restore data into environment-specific agent state locations. That makes persistence easier across installations and can silently seed a fresh machine with prior state, including sessions or other runtime artifacts, if the operator follows the instructions without strong review.

Vague Triggers

Medium
Confidence
90% confidence
Finding
The activation hints remain ambiguous and lack clear conditions for when the skill must not activate. Because this skill can direct sensitive export/import and overwrite operations, imprecise activation raises the chance of accidental use in unrelated migration or backup conversations.

Intent-Code Divergence

Medium
Confidence
94% confidence
Finding
The module docstring openly states this is a publish script, which conflicts with the surrounding skill description focused on user-data pack/apply operations. That mismatch makes the file more suspicious because it normalizes unrelated high-risk actions inside a package where users would expect only local data transformation behavior.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def get_npm_version(package_name: str) -> Optional[str]:
    try:
        result = subprocess.run(
            ["npm", "view", package_name, "version"],
            capture_output=True,
            text=True,
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
p = os.path.join(ROOT, name)
            if os.path.isfile(p):
                if name.endswith(".ps1"):
                    subprocess.run(
                        ["powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", p, version, changelog],
                        cwd=ROOT,
                        shell=False,
Confidence
97% confidence
Finding
This launches a repository-local PowerShell script with `-ExecutionPolicy Bypass`, explicitly weakening a platform safety control before executing local code. That creates an easy path for arbitrary code execution from the repo and is particularly dangerous in a skill distribution context where the file may not match the user's expected functionality.

Tainted flow: 'p' from os.environ.get (line 104, credential/environment) → subprocess.run (code execution)

Medium
Category
Data Flow
Content
p = os.path.join(ROOT, name)
            if os.path.isfile(p):
                if name.endswith(".ps1"):
                    subprocess.run(
                        ["powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", p, version, changelog],
                        cwd=ROOT,
                        shell=False,
Confidence
65% confidence
Finding
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
shell=False,
                    )
                else:
                    subprocess.run([p, version, changelog], cwd=ROOT, shell=True)
                print(f"ran hook: {name}")
                return
    else:
Confidence
99% confidence
Finding
This executes a local `.cmd`/`.bat` hook with `shell=True`, which expands the attack surface and enables arbitrary command execution via repository-controlled scripts. Because the skill's declared purpose is unrelated to publishing or hook execution, this behavior is suspicious and could be abused to run unexpected system commands when the script is launched.

Tainted flow: 'p' from os.environ.get (line 104, credential/environment) → subprocess.run (code execution)

Medium
Category
Data Flow
Content
shell=False,
                    )
                else:
                    subprocess.run([p, version, changelog], cwd=ROOT, shell=True)
                print(f"ran hook: {name}")
                return
    else:
Confidence
65% confidence
Finding
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
else:
        p = os.path.join(ROOT, "skill.sh")
        if os.path.isfile(p) and os.access(p, os.X_OK):
            subprocess.run([p, version, changelog], cwd=ROOT)
            print("ran hook: skill.sh")
Confidence
96% confidence
Finding
This executes a local `skill.sh` file if present and executable, which is arbitrary code execution from repository contents. In an agent skill setting, bundling optional hook execution is especially risky because a user or automated system may run the script expecting packaging behavior, not that it will execute additional code from the repo.

Tainted flow: 'p' from os.environ.get (line 104, credential/environment) → subprocess.run (code execution)

Medium
Category
Data Flow
Content
else:
        p = os.path.join(ROOT, "skill.sh")
        if os.path.isfile(p) and os.access(p, os.X_OK):
            subprocess.run([p, version, changelog], cwd=ROOT)
            print("ran hook: skill.sh")
Confidence
65% confidence
Finding
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
"--tags",
        "latest",
    ]
    return subprocess.run(cmd, cwd=ROOT, shell=(os.name == "nt")).returncode


def main() -> None:
Confidence
88% confidence
Finding
This executes an external `clawhub` publish command using values taken from environment variables (`CLAWHUB_SLUG`, `CLAWHUB_NAME`). While the arguments are passed as a list, the script is performing network publication behavior unrelated to the stated user-data pack/apply purpose, which increases risk in an agent skill context because it can exfiltrate package contents or trigger unintended external actions.

Tainted flow: 'cmd' from os.environ.get (line 130, credential/environment) → subprocess.run (code execution)

Medium
Category
Data Flow
Content
"--tags",
        "latest",
    ]
    return subprocess.run(cmd, cwd=ROOT, shell=(os.name == "nt")).returncode


def main() -> None:
Confidence
85% confidence
Finding
Environment-controlled values are incorporated into a publish command that performs external side effects. Even without classic shell injection, this is still dangerous because an attacker controlling the environment can redirect publication metadata or cause unintended publication behavior, which matters more here because publishing is unrelated to the skill's stated function.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
if os.environ.get("SKIP_NPM") == "1":
        print("[SKIP_NPM=1] skip npm publish")
    else:
        result = subprocess.run(["npm", "publish", "--access", "public"], cwd=ROOT, shell=(os.name == "nt"))
        if result.returncode != 0:
            print(
                "npm publish failed. Try: npm login. 403: enable 2FA or granular token.",
Confidence
91% confidence
Finding
This script can perform `npm publish`, causing local repository contents to be uploaded to a public registry. In the context of a skill whose purpose is only user-data pack/apply operations, outbound package publication is unjustified and dangerous because it can leak code, data, tokens embedded in files, or trigger unauthorized releases.

Intent-Code Divergence

Medium
Confidence
95% confidence
Finding
The module docstring states the script never writes to credentials, but the code can overwrite ~/.openclaw/openclaw.json when --apply-config is used. Since the help text and acknowledgement flag explicitly note that config overwrites secrets, the docstring is misleading and could cause unsafe operator assumptions during review or use.

Static analysis

No suspicious patterns detected.