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