Back to skill

Security audit

sys-updater

Security checks for vulnerabilities and agentic risk

Overview

This is a real system-updater skill, but its code performs privileged package changes and installed-skill updates in places the documentation describes as safer or read-only.

Review this skill carefully before installing. It should not be enabled on a production host until report generation is read-only, dry-run is enforced across child commands, sudo documentation matches actual privileged operations, autoremove is removed or explicitly approved, and OpenClaw skill updates require a separate reviewed action.

Vulnerability Patterns
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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
Findings (4)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/apt_maint.py:558
Finding
Dry-run mode performs real package and OpenClaw Skill updates<![CDATA[ ## Vulnerability Details **File Location**: `scripts/apt_maint.py:166-168, 558-578` **Vulnerability Type**: Incomplete dry-run enforcement **Risk Level**: High ### Technical Analysis Dry-run handling only intercepts commands whose first argument is `sudo`: ```python def sh( cmd: list[str], *, check: bool = True, timeout: int = DEFAULT_TIMEOUT, ) -> subprocess.CompletedProcess[str]: global _dry_run if _dry_run and cmd[0] == "sudo": log.info("[DRY-RUN] Would execute: %s", " ".join(cmd)) return subprocess.CompletedProcess(cmd, 0, stdout="", stderr="") return subprocess.run(cmd, text=True, capture_output=True, check=check, timeout=timeout) ``` Later, `run_6am()` invokes `pkg_maint.py` directly without forwarding the `--dry-run` option: ```python # Run pkg_maint.py for npm/brew checks + planned upgrades try: pkg_maint_path = Path(__file__).parent / "pkg_maint.py" if pkg_maint_path.exists(): log.info("Running: pkg_maint.py check for npm/brew") cp_check = subprocess.run( ["python3", str(pkg_maint_path), "check"], capture_output=True, text=True, timeout=180 ) if cp_check.returncode == 0: log.info("pkg_maint.py check: OK") else: log.warning("pkg_maint.py check: rc=%d, err=%s", cp_check.returncode, cp_check.stderr[:200]) log.info("Running: pkg_maint.py upgrade for npm/brew planned packages") cp_upgrade = subprocess.run( ["python3", str(pkg_maint_path), "upgrade"], capture_output=True, text=True, timeout=600 ) ``` The nested `check` operation calls `check_skills()`, which may update installed OpenClaw Skills. The nested `upgrade` operation performs real npm, pnpm, and Homebrew upgrades. Because neither child command receives `--dry-run`, the parent command's safety option does not cover these mutations. This violates the documented expectation that `run_6am --dry-run` is ...[truncated 1000 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Forward dry-run state to every nested operation: ```python check_cmd = ["python3", str(pkg_maint_path), "check"] upgrade_cmd = ["python3", str(pkg_maint_path), "upgrade"] if _dry_run: check_cmd.append("--dry-run") upgrade_cmd.append("--dry-run") ``` 2. Update `pkg_maint.py check` to accept and enforce `--dry-run`; currently its check path updates Skills. 3. Separate non-mutating discovery from mutating Skill updates. A command named `check` should only inspect state. 4. Centralize process execution so all mutating commands consult one execution policy rather than checking only for `sudo`. 5. Add integration tests that mock `subprocess.run` and verify that dry-run mode never invokes: - `npm update` - `pnpm update` - `brew upgrade` - `clawhub update` - Any mutating APT command 6. Clearly identify any operation that cannot support dry-run and fail closed instead of executing it. ]]>

T03 · Remote Payload Retrieval and Execution

Error
Location
scripts/apt_maint.py:868
Finding
Report generation silently downloads and installs OpenClaw Skill updates<![CDATA[ ## Vulnerability Details **File Location**: `scripts/apt_maint.py:868-878` **Vulnerability Type**: Mutating remote update in a read-only reporting path **Risk Level**: High ### Technical Analysis The report renderer first lists installed Skills and then runs an unattended update of all Skills: ```python # Skills section try: result = subprocess.run( ["clawhub", "list"], capture_output=True, text=True, timeout=30 ) if result.returncode == 0 and result.stdout: skill_count = len([l for l in result.stdout.strip().split('\n') if l.strip()]) # Check for skill updates skills_updated = [] skills_with_changes = [] try: update_result = subprocess.run( ["clawhub", "update", "--all", "--no-input"], capture_output=True, text=True, timeout=60 ) ``` This occurs in `render_report()`, despite `docs/scheduling.md` describing `report_9am` as requiring no sudo and reading state files: ```text # Test report_9am (no sudo needed, reads state files) python3 scripts/apt_maint.py report_9am ``` Although root access is not used, `clawhub update --all --no-input` retrieves and installs remotely maintained executable Skill content. The effective code installed can change after this repository has been reviewed. Performing that action inside a reporting function violates command-query separation and prevents operators from treating reports as read-only. ### Attack Path 1. The scheduled 09:00 report job or an operator invokes: ```bash python3 scripts/apt_maint.py report_9am ``` 2. `render_report()` executes `clawhub list`. 3. If installed Skills are present, it executes: ```bash clawhub update --all --no-input ``` 4. ClawHub retrieves current remote Skill versions. 5. Updated executable Skill files are installed without a separate approval step. 6. A compromised or malicious upstream release can subsequently execute with the OpenClaw ...[truncated 539 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove `clawhub update --all --no-input` from `render_report()`. 2. Use a non-mutating command or API to determine whether updates are available. 3. Move Skill updates into an explicit command such as: ```bash python3 scripts/pkg_maint.py skills ``` 4. Require a separate policy decision before installing updates, particularly for Skills that contain executable instructions or scripts. 5. Pin approved Skill versions or verify cryptographic hashes/signatures where supported. 6. Quarantine downloaded updates and compare their manifests, permissions, and code changes before activation. 7. Ensure the report path remains read-only through tests that reject mutating subprocess invocations. ]]>

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/apt_maint.py:285
Finding
Runtime performs privileged APT installation and autoremove operations excluded by the declared sudo policy<![CDATA[ ## Vulnerability Details **File Location**: `scripts/apt_maint.py:285-297, 543-545` **Vulnerability Type**: Undisclosed privileged package installation and removal **Risk Level**: High ### Technical Analysis The implementation installs planned non-security APT packages with root privileges: ```python def apply_planned_apt_upgrades(planned: list[str], upgradable_set: set[str]) -> tuple[list[str], list[str]]: """Apply explicitly planned non-security upgrades via apt-get install. Returns: (applied_packages, failed_packages) """ # Only attempt packages that are still upgradable now. targets = sorted([p for p in planned if p in upgradable_set]) if not targets: return [], [] try: log.info("Running: sudo apt-get install -y %s", " ".join(targets)) cp = sh(sudo_cmd(["apt-get", "install", "-y", *targets]), check=False, timeout=DEFAULT_TIMEOUT) ``` It also performs unconditional privileged dependency removal during every `run_6am()` execution: ```python # Cleanup orphaned dependencies try: log.info("Running: sudo apt-get autoremove -y") cp = sh(sudo_cmd(["apt-get", "autoremove", "-y"]), check=False, timeout=300) ``` These operations directly contradict the declared security constraints in `SKILL.md:156-158`: ```text - No `apt-get upgrade` without `-s` (simulation only for tracking) - No `apt-get dist-upgrade` or `autoremove` - No package installation/removal through sudo ``` They also conflict with `docs/sudoers.md:45`, which states that no `apt-get install`, `remove`, or `autoremove` permissions are granted. Consequently, either these code paths fail in a correctly configured deployment or administrators must broaden passwordless sudo permissions beyond the documented least-privilege boundary. The package names are passed as an argument list rather than through a shell, which limits shell-injection risk. The primary issue is unauthorized privilege scope and destructive behavior, not s ...[truncated 1332 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove the privileged `apt-get install` and `apt-get autoremove` operations if the declared policy is authoritative. 2. Never run `apt-get autoremove -y` unconditionally from scheduled maintenance. Require explicit administrator review of the simulated removal set. 3. If planned APT installation is an intended feature: - Update all documentation and threat models. - Require explicit human approval. - Protect tracking state with restrictive ownership and permissions. - Validate package names against a strict allowlist. - Run and record a simulation immediately before installation. - Refuse operations that add or remove unexpected packages. 4. Keep `/etc/sudoers.d/sys-updater` limited to exact commands and do not grant broad `apt-get` access. 5. Consider a root-owned helper with a narrowly defined protocol rather than granting a user-controlled Python process general package-management capabilities. 6. Use atomic, securely permissioned state writes to prevent local users from modifying approval decisions. 7. Add tests that compare the documented sudo command allowlist with every command constructed by the implementation. ]]>

T08 · Insecure Dependencies

Error
Location
scripts/pkg_maint.py:385
Finding
Packages with unresolved reviews are automatically approved and installed after four days<![CDATA[ ## Vulnerability Details **File Location**: `scripts/pkg_maint.py:385-418, 592-593` **Vulnerability Type**: Fail-open dependency approval policy **Risk Level**: High ### Technical Analysis The updater defines a four-day threshold after which pending reviews are converted into planned upgrades: ```python AUTO_PLAN_PENDING_AFTER_DAYS = 4 # Escalate long-pending items to planned ``` The approval function explicitly selects unresolved items and marks them as planned: ```python def auto_plan_long_pending(tracked: dict, manager: str, days: int = AUTO_PLAN_PENDING_AFTER_DAYS) -> int: """Escalate long-pending items to planned upgrades. Policy: - only items with reviewResult == "pending" - not blocked - not already planned - older than `days` since firstSeenAt """ changed = 0 items = tracked.get("items", {}) now = now_utc() for name, meta in items.items(): if meta.get("blocked") or meta.get("planned"): continue if meta.get("reviewResult") != "pending": continue first_seen = meta.get("firstSeenAt") if not first_seen: continue try: first_dt = parse_iso(first_seen) except (ValueError, TypeError): continue if (now - first_dt) < timedelta(days=days): continue meta["planned"] = True note = meta.get("note") or "" suffix = f"Auto-planned after pending>{days}d" meta["note"] = f"{note}; {suffix}" if note else suffix meta["reviewedBy"] = meta.get("reviewedBy") or "auto" changed += 1 ``` `upgrade_mode()` calls this function before processing npm, pnpm, and Homebrew installations: ```python auto_planned_by_manager: dict[str, int] = { manager: auto_plan_long_pending(tracked_by_manager[manager], manager) for manager in MANAGER_ORDER } ``` A `pending` decision means the review engine could not establish that an update was safe—for example ...[truncated 1406 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove `auto_plan_long_pending()` from the installation workflow. 2. Treat `pending` as fail-closed indefinitely: - `pending` must never imply `planned`. - Only a successful review or explicit human action may approve installation. 3. Generate alerts for stale pending reviews instead of installing them. 4. Record approval identity, timestamp, reviewed version, and evidence. Invalidate approval if the candidate version changes. 5. Require package provenance checks, registry identity validation, and integrity verification before approval. 6. For npm and pnpm, evaluate lifecycle scripts and consider disabling scripts during staging where operationally possible. 7. Stage upgrades in an isolated environment and run functional/security checks before global installation. 8. Add regression tests asserting that elapsed time alone cannot transition an item from `pending` to `planned`. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Rogue AgentSelf-Modification, Session Persistence
Findings (92)

Ae1

High
Category
analysis-evasion
Content
./scripts/apt_maint.py run_6am
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
./scripts/apt_maint.py run_6am
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Self-Modification

High
Category
Rogue Agent
Content
# Apply planned upgrades
./scripts/pkg_maint.py upgrade

# Update skills only
./scripts/pkg_maint.py skills
```
Confidence
91% confidence
Finding
The skill explicitly includes a capability to update installed skills, which is a self-modification/supply-chain risk because future executions may run newly fetched code or altered automation logic. In an agent environment, code that can update its own operational dependencies is especially sensitive because it can bypass normal review expectations and widen the impact of a compromised upstream source.

Tool Parameter Abuse

High
Category
Tool Misuse
Content
cat state/apt/.run_6am.lock

# If stale (process doesn't exist), remove manually
rm state/apt/.run_6am.lock
```

### "No data" in report
Confidence
94% confidence
Finding
Manual `rm state/apt/.run_6am.lock` is dangerous because it bypasses a synchronization control protecting against concurrent executions. If used incorrectly, it can trigger duplicate maintenance runs, race conditions, and inconsistent or corrupted package-tracking state.

Tool Parameter Abuse

High
Category
Tool Misuse
Content
cat state/apt/tracked.json | jq '.items | to_entries | map(select(.value.blocked)) | .[].key'

# Clear old tracking (reset)
rm state/apt/tracked.json
python3 scripts/apt_maint.py run_6am
```
Confidence
92% confidence
Finding
Deleting `state/apt/tracked.json` resets tracking state and may discard review, block, or recovery metadata without adequate warning. That can alter future updater behavior, make auditing harder, and cause previously blocked or reviewed packages to be reprocessed unexpectedly.

Chaining Abuse

High
Category
Tool Misuse
Content
"""Apt maintenance helper for the OpenClaw host (sys-updater).

Modes:
- run_6am: sudo apt-get update; sudo unattended-upgrade; sudo apt-get -s upgrade; snapshot upgradable pkgs.
- report_9am: render a human report from last run state.

Conservative by design:
Confidence
75% confidence
Finding
Tool calls are chained to bypass individual safety checks or escalate capabilities beyond what any single tool call would allow.

Env Variable Harvesting

High
Category
Data Exfiltration
Content
def list_upgradable() -> list[str]:
    """List upgradable packages using apt directly (no bash wrapper)."""
    # Use apt list directly, suppressing the warning about CLI stability
    env = os.environ.copy()
    env["LANG"] = "C.UTF-8"
    cp = subprocess.run(
        ["apt", "list", "--upgradable"],
Confidence
60% confidence
Finding
Code enumerates, copies, or searches environment variables for secrets. Bulk environment access can collect credentials unrelated to the skill's stated purpose.

Context-Inappropriate Capability

High
Confidence
98% confidence
Finding
A report-generation mode should be observational, but this code inventories and updates skills, creating side effects unrelated to its stated role. Hidden update behavior in a reporting path is especially risky because users may run it frequently and without expecting code or package changes.

Self-Modification

High
Category
Rogue Agent
Content
else:
                log.debug("Skill %s already up to date", slug)
        else:
            log.error("Failed to update skill %s: %s", slug, stderr[:200])
            result["failed"].append({"name": slug, "error": stderr[:200]})
    
    return result
Confidence
97% confidence
Finding
This code path updates installed skills, which are part of the agent's own executable capability set, creating a self-modification mechanism. In an agent-skill ecosystem, auto-updating skills from a package source can import malicious or compromised code and immediately expand or alter runtime behavior without review.

Self-Modification

High
Category
Rogue Agent
Content
blocked_count = _count_flag(tracked_by_manager, "blocked")
    log.info("Summary: planned=%d, blocked=%d", planned_count, blocked_count)

    # Update skills immediately (no quarantine for skills)
    log.info("Updating OpenClaw skills...")
    skills_result = check_skills()
    if skills_result["installed"]:
Confidence
98% confidence
Finding
The check mode triggers skill updates automatically, meaning a routine maintenance run modifies the agent's installed skills without a distinct approval step. Because skills are executable extensions, this materially increases the risk of supply-chain compromise and unintended capability changes.

Self-Modification

High
Category
Rogue Agent
Content
formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
Modes:
  check    - Update tracked state + auto-update skills (run daily in run_6am)
  review   - Show packages due for bug review (after 2 days)
  upgrade  - Apply planned npm/pnpm/brew upgrades
  skills   - Update OpenClaw skills immediately (no quarantine)
Confidence
91% confidence
Finding
The CLI advertises and normalizes immediate skill updates without quarantine, reinforcing an unsafe operational pattern for self-modifying agent components. While this line is descriptive rather than executable, it documents and encourages the risky behavior implemented elsewhere.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
Line L07 states that the tool generates Telegram reports in Russian as a fixed behavior. This is a natural-language locale policy constraint with no indication that users can choose another language or opt in to Russian output.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
python3 scripts/apt_maint.py report_9am
```

No external dependencies—uses only Python stdlib. Requires sudo NOPASSWD for apt-get and unattended-upgrade.

## Architecture
Confidence
87% confidence
Finding
The skill explicitly requires sudo NOPASSWD for apt-get and unattended-upgrade, meaning the automation can invoke privileged package-management operations without interactive authentication. If the script, its execution environment, or related paths are modified or abused, this creates a strong privilege-escalation and system-integrity risk because package operations run as root.

Missing User Warnings

Medium
Confidence
83% confidence
Finding
The markdown states that security updates are applied automatically and shows maintenance commands that perform package updates, which can modify system state and installed software. While the behavior is described, it does not include a clear warning or caution to users that running or enabling this skill will make real system changes and should be used only on intended hosts.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
- Python 3.10+ (stdlib only, no dependencies)
- Ubuntu with `unattended-upgrades` installed
- Sudo NOPASSWD for apt-get and unattended-upgrade (see [docs/sudoers.md](docs/sudoers.md))

## Directory Structure
Confidence
84% confidence
Finding
Requiring Sudo NOPASSWD for apt-get and unattended-upgrade materially increases risk because any compromise of the invoking user, automation account, or script path can lead to passwordless root-level package operations. In the context of an automation skill that regularly runs maintenance, this expands the blast radius and makes post-compromise privilege escalation easier.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The description advertises Telegram reporting but does not prominently warn that package inventory, update status, and potentially hostname/system-maintenance details may be transmitted to an external messaging service. This creates a data exposure risk because operators may enable the skill without understanding that operational metadata leaves the host.

Intent-Code Divergence

Medium
Confidence
90% confidence
Finding
The capability description at L035-L036 says the skill 'checks installed skills and reports update status', which is read/report oriented. But the workflow at L049 says 'skills: auto-update immediately (no quarantine)', implying actual modification of installed skills rather than only checking/reporting. These statements actively describe different behaviors for the same feature.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The workflow states that installed skills are auto-updated immediately with no quarantine, but it does not prominently warn that this modifies the local environment and can execute changed third-party skill content. Immediate self-update behavior increases supply-chain and operational risk because updates may alter automation behavior without prior approval.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
## Sudoers Configuration

For unattended operation, grant the running user passwordless sudo for specific apt commands only. **Do not add the user to full sudoers.**

Create file `/etc/sudoers.d/sys-updater`:
Confidence
84% confidence
Finding
The skill instructs users to grant passwordless sudo to automation for package-management operations, which materially increases risk if the agent, its scripts, or related dependencies are compromised. Even though the allowed commands are constrained, root-capable package update paths can still be abused for persistence, repository-based compromise, or unintended system changes.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
This markdown file presents its operational instructions in Russian, which effectively forces a specific language for users reading or operating the skill. The policy allows locale constraints only when the skill offers user choice or clearly documents a justified region-specific requirement, neither of which is present here.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The documentation describes an automated workflow that runs package maintenance commands and writes persistent state, but it does not prominently warn that the skill modifies the host system and should only be used with informed authorization. Even though the commands are standard administrative operations, omission of an explicit modification warning increases the risk of unintended execution in the wrong environment or by an operator who expects read-only behavior.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
`tracked.json` maintains metadata for each non-security package.

**Automatic cleanup:** Packages that are no longer upgradable (already upgraded or removed from repos) are automatically removed from tracking, unless they are marked as `blocked` or `planned` (indicating admin made a decision about them).

```json
{
Confidence
80% confidence
Finding
Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Session Persistence

Medium
Category
Rogue Agent
Content
Comment out cron entries:

```bash
crontab -e
# Comment the lines with #
```
Confidence
85% 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.

Session Persistence

Medium
Category
Rogue Agent
Content
Comment out cron entries:

```bash
crontab -e
# Comment the lines with #
```
Confidence
85% 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.

Session Persistence

Medium
Category
Rogue Agent
Content
Comment out cron entries:

```bash
crontab -e
# Comment the lines with #
```
Confidence
85% 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.

Static analysis

No suspicious patterns detected.