T09 · Insecure Skill Coding Practices
- Location
- scripts/heartbeat-dispatch.sh:35
- Finding
- Python Code Injection Through Unescaped Filesystem Paths<![CDATA[ ## Vulnerability Details **File Location**: `scripts/heartbeat-dispatch.sh:35-64`; `scripts/security-audit.sh:79-87` **Vulnerability Type**: Python source injection through shell-variable interpolation **Risk Level**: High ### Vulnerable Code ```bash get_last_check() { local key="$1" if [ -f "$STATE_FILE" ]; then python3 -c " import json with open('$STATE_FILE') as f: d = json.load(f) v = d.get('lastChecks', {}).get('$key', 0) print(v if isinstance(v, (int, float)) and v is not None else 0) " 2>/dev/null || echo "0" else echo "0" fi } update_state() { local key="$1" python3 -c " import json, os path = '$STATE_FILE' os.makedirs(os.path.dirname(path), exist_ok=True) try: with open(path) as f: d = json.load(f) except: d = {'lastChecks': {}} d.setdefault('lastChecks', {})['$key'] = $NOW with open(path, 'w') as f: json.dump(d, f, indent=2) " 2>/dev/null } ``` ```bash CONFIG="$HOME/.openclaw/openclaw.json" if [ -f "$CONFIG" ]; then # Check if elevated commands are restricted ELEVATED=$(python3 -c " import json with open('$CONFIG') as f: c = json.load(f) sec = c.get('security', {}) elevated = sec.get('elevated', 'unknown') print(f'elevated={elevated}') " 2>/dev/null || echo "error reading config") ``` ### Technical Analysis Paths derived from the shell environment are embedded directly into Python source code passed to `python3 -c`. In particular, `STATE_FILE` and `CONFIG` are derived from the value of `HOME`. Shell expansion does not sanitize quotes contained in variable values. If a path contains a single quote, that quote becomes part of the generated Python program and can terminate the Python string literal. An attacker who controls the execution environment can append Python statements and comment out the remaining generated source. The `key` argument is interpolated using the same unsafe technique. Current call sites use fixed internal key names, which reduces immedia ...[truncated 1466 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions Do not interpolate shell values into Python source code. Pass every path, timestamp, and key as a positional argument or environment variable. For example: ```bash python3 - "$STATE_FILE" "$key" "$NOW" <<'PY' import json import os import sys path = sys.argv[1] key = sys.argv[2] now = int(sys.argv[3]) os.makedirs(os.path.dirname(path), exist_ok=True) try: with open(path, encoding="utf-8") as handle: data = json.load(handle) except (FileNotFoundError, json.JSONDecodeError, OSError): data = {"lastChecks": {}} data.setdefault("lastChecks", {})[key] = now with open(path, "w", encoding="utf-8") as handle: json.dump(data, handle, indent=2) PY ``` Apply the same pattern to `CONFIG`. Additional hardening should include: 1. Validate that resolved paths remain under the expected OpenClaw directory. 2. Reject unexpected control characters in relevant environment variables. 3. Avoid broad `except:` clauses; catch expected exceptions explicitly. 4. Run scheduled maintenance under a dedicated, minimally privileged account. 5. Add tests using paths containing quotes, spaces, newlines, and shell metacharacters. ]]>
