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

Content
View full analysis

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
View 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

Content
View full analysis

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
View 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

Content
View full analysis

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
View 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.

Content

No source excerpt is available for this finding.

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.

Content

No source excerpt is available for this finding.

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.

Content

No source excerpt is available for this finding.

Memory Manipulation

High
Category
Memory Poisoning
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.

Content

Scanner excerpt · SKILL.md (reported line 75)May include surrounding context.

md
- 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)

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

Scanner excerpt · SKILL.md (reported line 81)May include surrounding context.

md
- **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

Description-Behavior Mismatch

High
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

High
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Tool Parameter Abuse

High
Category
Tool Misuse
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.

Content

Scanner excerpt · publish_npm.py (reported line 113)May include surrounding context.

python
shell=False,
                    )
                else:
                    subprocess.run([p, version, changelog], cwd=ROOT, shell=True)
                print(f"ran hook: {name}")
                return
    else:

Undeclared Tool Scope

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.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
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.

Content

Scanner excerpt · SKILL.md (reported line 18)May include surrounding context.

md
## 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.

---

Session Persistence

Medium
Category
Rogue Agent
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.

Content

Scanner excerpt · SKILL.md (reported line 114)May include surrounding context.

md
## 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.

---

Vague Triggers

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · publish_npm.py (reported line 48)May include surrounding context.

python
def get_npm_version(package_name: str) -> Optional[str]:
    try:
        result = subprocess.run(
            ["npm", "view", package_name, "version"],
            capture_output=True,
            text=True,

subprocess module call

Medium
Category
Dangerous Code Execution
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.

Content

Scanner excerpt · publish_npm.py (reported line 107)May include surrounding context.

python
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,

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

Medium
Category
Data Flow
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.

Content

Scanner excerpt · publish_npm.py (reported line 107)May include surrounding context.

python
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,

subprocess module call

Medium
Category
Dangerous Code Execution
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.

Content

Scanner excerpt · publish_npm.py (reported line 113)May include surrounding context.

python
shell=False,
                    )
                else:
                    subprocess.run([p, version, changelog], cwd=ROOT, shell=True)
                print(f"ran hook: {name}")
                return
    else:

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

Medium
Category
Data Flow
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.

Content

Scanner excerpt · publish_npm.py (reported line 113)May include surrounding context.

python
shell=False,
                    )
                else:
                    subprocess.run([p, version, changelog], cwd=ROOT, shell=True)
                print(f"ran hook: {name}")
                return
    else:

subprocess module call

Medium
Category
Dangerous Code Execution
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.

Content

Scanner excerpt · publish_npm.py (reported line 119)May include surrounding context.

python
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")

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

Medium
Category
Data Flow
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.

Content

Scanner excerpt · publish_npm.py (reported line 119)May include surrounding context.

python
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")

subprocess module call

Medium
Category
Dangerous Code Execution
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.

Content

Scanner excerpt · publish_npm.py (reported line 145)May include surrounding context.

python
"--tags",
        "latest",
    ]
    return subprocess.run(cmd, cwd=ROOT, shell=(os.name == "nt")).returncode


def main() -> None:

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

Medium
Category
Data Flow
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.

Content

Scanner excerpt · publish_npm.py (reported line 145)May include surrounding context.

python
"--tags",
        "latest",
    ]
    return subprocess.run(cmd, cwd=ROOT, shell=(os.name == "nt")).returncode


def main() -> None:

subprocess module call

Medium
Category
Dangerous Code Execution
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.

Content

Scanner excerpt · publish_npm.py (reported line 197)May include surrounding context.

python
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.",

Intent-Code Divergence

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.