Back to skill

Security audit

OpenClaw Upgrader

Security checks for vulnerabilities and agentic risk

Overview

The skill is a coherent OpenClaw upgrader, but it grants and scripts high-impact local upgrade authority with under-scoped delegation and concrete runner vulnerabilities.

Review before installing. This skill is meant to change a live OpenClaw installation, restart or repair services, write files under the OpenClaw user area, and potentially contact Codex or Claude Code. It should be tightened before general use: fix the result-path Python injection, make lock ownership cleanup safe, require explicit user confirmation before external AI preflights and service/package changes, and restrict delegate command selection to trusted configured paths.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/run-upgrade-delegation.sh:217
Finding
Arbitrary Python Code Execution Through an Unsafely Embedded Result Path<![CDATA[ ## Vulnerability Details **File Location**: `scripts/run-upgrade-delegation.sh`, lines 217–218 **Vulnerability Type**: Python source injection caused by unsafe string interpolation **Risk Level**: High ### Vulnerable Code ```bash if [[ -f "$RESULT_JSON" ]]; then FINAL_STATUS="$(python3 -c "import json; print(json.load(open('$RESULT_JSON')).get('status','unknown'))")" ``` ### Technical Analysis `RESULT_JSON` is initialized from the script's second positional argument: ```bash RESULT_JSON="${2:-$HOME/.openclaw/.upgrade-result.json}" ``` Although the shell expands `"$RESULT_JSON"` within a double-quoted argument, the expanded value is inserted directly into the Python program passed to `python3 -c`. The single quotes around `$RESULT_JSON` are Python syntax, not a security boundary enforced by the shell. A result path containing an apostrophe and additional Python expressions can terminate the intended Python string and inject arbitrary Python code. The vulnerable command is reached when: 1. `OPENCLAW_UPGRADER_DELEGATE_CMD` is configured. 2. The delegation command returns a nonzero status. 3. A file exists at the attacker-selected `RESULT_JSON` path. No evaluation of the path as source code is necessary. It should instead be passed to Python as a separate command-line argument. ### Attack Path 1. An attacker who can invoke the runner or influence its arguments supplies a crafted second positional argument containing Python syntax. 2. The attacker ensures that a file exists under the resulting crafted pathname. 3. The configured delegation command returns a nonzero exit code, causing execution to enter the error-handling branch. 4. Line 218 interpolates the malicious pathname into the Python source supplied through `python3 -c`. 5. Python evaluates the injected expression or statements with the operating-system privileges of the upgrader process. A conceptual malicious path could close the `open('...')` string and append an expression invoki ...[truncated 891 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Pass the result path as data through `sys.argv` rather than embedding it into Python source: ```bash FINAL_STATUS="$( python3 -c \ 'import json, sys; print(json.load(open(sys.argv[1])).get("status", "unknown"))' \ "$RESULT_JSON" )" ``` Additional hardening should include: 1. Parse the result file in a dedicated quoted heredoc or standalone helper where all dynamic values are supplied as arguments. 2. Validate that the result file is a regular file and, where appropriate, is owned by the expected user. 3. Restrict result files to an expected directory after canonicalizing the path. 4. Avoid dynamically constructing source code from shell variables anywhere in the scripts. 5. Add regression tests using paths containing apostrophes, spaces, newlines, shell metacharacters, and Python syntax. 6. Treat malformed or untrusted result JSON as a controlled delegation failure rather than allowing an unhandled parser error. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/run-upgrade-delegation.sh:34
Finding
Unconditional Cleanup Can Remove a Lock Owned by Another Upgrade Run<![CDATA[ ## Vulnerability Details **File Location**: `scripts/run-upgrade-delegation.sh`, lines 34–37; related control flow at lines 103–114 and 130–137 **Vulnerability Type**: Lock ownership failure and unsafe cleanup **Risk Level**: High ### Vulnerable Code The cleanup function removes the shared lock without verifying ownership: ```bash release_lock() { rm -f "$LOCK_INFO_FILE" >/dev/null 2>&1 || true rmdir "$LOCK_DIR" >/dev/null 2>&1 || true } ``` It is installed as an unconditional exit handler before context collection establishes whether this process acquired the lock: ```bash trap release_lock EXIT # --- Phase 1: Context collection (also claims the lock) --- if [[ -z "$CONTEXT_JSON" ]]; then TS="$(date +%Y%m%d-%H%M%S)" CONTEXT_JSON="$HOME/.openclaw/upgrade-context-$TS.json" fi "$COLLECTOR" "$TARGET_VERSION" "$CONTEXT_JSON" >/dev/null ``` The trap is removed only after the collector successfully returns and the generated context is parsed as a rejected re-entry: ```bash if [[ "$DELEGATION_STATUS" == "rejected_reentry" ]]; then trap - EXIT write_terminal_result "rejected_reentry" \ '"delegation_status": "rejected_reentry", "delegation_block_reason": "active_run_exists"' exit 0 fi ``` ### Technical Analysis The lock directory is host-global by default: ```bash LOCK_DIR="${OPENCLAW_UPGRADER_LOCK_DIR:-$HOME/.openclaw/openclaw-upgrader.lock}" ``` The context collector attempts to acquire that lock with an atomic `mkdir`. When the directory already exists, the collector records a rejected-reentry result rather than taking ownership. However, the outer runner installs `trap release_lock EXIT` before calling the collector. If context collection fails before the runner reaches the explicit `rejected_reentry` branch, `set -e` causes the outer script to exit and invoke `release_lock`. The cleanup function does not compare the current run identifier, PID, or any ownership token against `run.json`; it simply deletes the shared metada ...[truncated 2237 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Track lock ownership explicitly and release the lock only when the current runner has proven ownership. Recommended changes: 1. Initialize an ownership flag to false: ```bash LOCK_OWNED=false EXPECTED_RUN_ID="" ``` 2. Do not install a lock-removal EXIT trap before acquisition has been confirmed. 3. After successful context collection, read the context run ID and compare it with the run ID stored in `$LOCK_INFO_FILE`. 4. Set `LOCK_OWNED=true` only if the IDs match and the context does not represent rejected re-entry. 5. Make cleanup conditional and verify ownership again immediately before deletion: ```bash release_lock() { [[ "$LOCK_OWNED" == true ]] || return 0 local recorded_run_id recorded_run_id="$( python3 -c 'import json,sys; print(json.load(open(sys.argv[1])).get("run_id", ""))' \ "$LOCK_INFO_FILE" 2>/dev/null || true )" [[ -n "$EXPECTED_RUN_ID" && "$recorded_run_id" == "$EXPECTED_RUN_ID" ]] || return 0 rm -f -- "$LOCK_INFO_FILE" rmdir -- "$LOCK_DIR" } ``` 6. Install `trap release_lock EXIT` only after setting and verifying `LOCK_OWNED=true`. 7. Prefer an operating-system lock primitive such as `flock` where supported, while retaining a secure cross-platform alternative. 8. Include an unpredictable ownership token in the lock metadata rather than relying solely on a PID or timestamp. 9. Use atomic writes for `run.json` and validate its ownership and permissions. 10. Add tests covering collector failure, malformed context JSON, unwritable context paths, signals, rejected re-entry, and concurrent invocations. Each test should verify that a non-owner can never remove the active owner's lock. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • 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
Findings (12)

Ae1

High
Category
analysis-evasion
Content
Use `scripts/collect-upgrade-context.sh` to gather a machine-readable context snapshot before delegation. That script must reject re-entry before agent prefligh
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
Use `scripts/collect-upgrade-context.sh` to gather a machine-readable context snapshot before delegation. That script must reject re-entry before agent prefligh
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Lp3

Medium
Category
MCP Least Privilege
Confidence
89% confidence
Finding
The skill describes file-writing behavior such as config backups and result-file creation, and it delegates upgrade actions that can modify the local system, but it does not declare an explicit tool scope like allowed-tools or permissions. That omission weakens least-privilege controls and can cause the runtime or delegated agent to operate with broader capabilities than the user expects, increasing the blast radius if the skill or downstream prompt is misused.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
This section instructs the workflow to back up config and write structured result artifacts, which are persistent local file modifications, but the skill does not explicitly warn the user that files will be created or changed. That creates a transparency and consent problem: users may invoke what sounds like an upgrade helper without realizing it will write backups, lock metadata, or result files onto disk.

Session Persistence

Medium
Category
Rogue Agent
Content
The run lock must cover the entire active upgrader lifecycle, not just context collection. `scripts/collect-upgrade-context.sh` may claim the host-level run lock and emit lock metadata, but the caller/outer runner must retain that lock until the delegated upgrade run reaches a terminal state and then release it deliberately. `scripts/run-upgrade-delegation.sh` is the default outer-runner scaffold for this purpose and must remain responsible for terminal lock release.

If delegation cannot begin, OpenClaw itself must write a structured pre-delegation failure result. Do not leave blocked delegation as an implicit or unstructured failure.
If another upgrader run is already active on this host, reject re-entry or explicitly queue it; do not allow concurrent execution.

Useful facts to pass:
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
94% confidence
Finding
The delegated workflow explicitly includes package upgrades, service-definition refresh, restart, recovery, and verification, all of which can alter system packages and running services, yet the skill does not clearly warn the user that invoking it may change system state and interrupt service availability. In context, this is more dangerous than ordinary file writes because it can restart or reconfigure the active OpenClaw service and potentially affect local clients or system stability.

Description-Behavior Mismatch

Medium
Confidence
95% confidence
Finding
The script’s so-called context collection step executes live AI agent CLIs (`codex exec` and `claude -p`) as a preflight check, which can trigger network access, transmit environment-derived context, and perform side effects before the main upgrade workflow begins. That exceeds passive inspection and creates an unexpected trust boundary crossing during a preparatory phase, especially since users may not realize that merely gathering upgrade context will contact external services.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The preflight commands invoke external AI CLIs without any user-visible warning or confirmation, so a user running an upgrade helper may unknowingly trigger outbound requests and prompt execution. In this script’s context, collected values such as endpoint, auth mode, service identity, and filesystem paths increase the sensitivity of what could be exposed or inferred by those tools.

Context-Inappropriate Capability

Medium
Confidence
94% confidence
Finding
The skill is described as a Codex-supervised upgrader, yet the script silently probes Claude as an alternate execution path and may delegate to it if Codex is unavailable. This expands the external attack and data-exposure surface beyond the documented tool, undermines user expectations, and could route upgrade-related context to an unrelated provider or CLI with different security properties.

Intent-Code Divergence

Medium
Confidence
89% confidence
Finding
The header promises the lock is held until a terminal state, but in the OPENCLAW_UPGRADER_DELEGATE_CMD path the wrapper exits and releases the lock even if the delegate wrote a nonterminal or malformed result. That can allow a second upgrade run to start while the first upgrade may still be incomplete or inconsistent, creating race conditions around service restart, state migration, or recovery.

Intent-Code Divergence

Medium
Confidence
98% confidence
Finding
Lines L106-L110 document the EXIT trap as guaranteeing the lock is always released regardless of how the script terminates. However, lines L229-L231 explicitly remove that trap so the lock survives process exit when no delegate command is wired, which directly contradicts the earlier assurance rather than merely omitting detail.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The script executes a command entirely controlled by the OPENCLAW_UPGRADER_DELEGATE_CMD environment variable. In the context of an upgrade skill that may run with elevated privileges and manipulates service state, any attacker able to influence that environment can achieve arbitrary code execution under the script's privileges.

Static analysis

No suspicious patterns detected.