T09 · Insecure Skill Coding Practices
- Location
scripts/analyze.sh:24- Finding
Python Code Injection Through Unescaped Filesystem Paths
- Content
View full analysis
Vulnerability Details
File Location:
scripts/analyze.sh:24-49,scripts/analyze.sh:74-86,scripts/heartbeat_diagnosis.sh:19-40, andscripts/heartbeat_diagnosis.sh:49-70
Vulnerability Type: Python source-code injection caused by embedding shell-derived paths intopython3 -cprograms
Risk Level: MediumVulnerable Code
From
scripts/analyze.sh:24-49:bash if [ "$HEARTBEAT_INTERVAL" = "auto" ]; then if [ -f "$CONFIG_FILE" ]; then HEARTBEAT_INTERVAL=$(python3 -c " import json, sys, re try: with open('$CONFIG_FILE', 'r') as f: cfg = json.load(f) # heartbeat is at cfg.agents.defaults.heartbeat.every hb = cfg.get('agents', {}).get('defaults', {}).get('heartbeat', {}) if isinstance(hb, dict): every = hb.get('every', 'auto') elif isinstance(hb, str): every = hb else: every = 'auto' if every not in ('auto', None, '') and every is not None: m = re.match(r'(\d+)([smh])', str(every)) if m: val, unit = int(m.group(1)), m.group(2) print(str(val * {'s': 1, 'm': 60, 'h': 3600}[unit])) else: print('auto') else: print('auto') except: print('auto') " 2>/dev/null || echo "auto") fiFrom
scripts/analyze.sh:74-86:bash if [ -f "$CONFIG_FILE" ]; then CONFIG_TOOLS=$(python3 -c " import json, sys try: with open('$CONFIG_FILE', 'r') as f: cfg = json.load(f) tools = cfg.get('tools', cfg.get('skills', cfg.get('plugins', []))) if isinstance(tools, list): print(len(tools)) else: print(0) except: print(0) " 2>/dev/null || echo "0")From
scripts/heartbeat_diagnosis.sh:19-40:bash if [ -f "$CONFIG_FILE" ]; then # Try to extract heartbeat interval from openclaw.json # heartbeat is at cfg.agents.defaults.heartbeat.every INTERVAL=$(python3 -c " import json, sys, re try: with open('$CONFIG_FILE', 'r') as f: cfg = json.load(f) h ...[truncated 3897 chars]- Remediation
View remediation
Remediation Suggestions
Do not interpolate paths or any other shell-derived values into Python source code. Pass them as positional arguments and retrieve them through
sys.argv.For example:
bash HEARTBEAT_INTERVAL=$( python3 - "$CONFIG_FILE" 2>/dev/null <<'PY' import json import re import sys config_file = sys.argv[1] try: with open(config_file, "r", encoding="utf-8") as f: cfg = json.load(f) hb = cfg.get("agents", {}).get("defaults", {}).get("heartbeat", {}) if isinstance(hb, dict): every = hb.get("every", "auto") elif isinstance(hb, str): every = hb else: every = "auto" if every not in ("auto", None, ""): match = re.fullmatch(r"(\d+)([smh])", str(every)) if match: value = int(match.group(1)) unit = match.group(2) print(value * {"s": 1, "m": 60, "h": 3600}[unit]) else: print("auto") else: print("auto") except (OSError, ValueError, TypeError, json.JSONDecodeError): print("auto") PY ) || HEARTBEAT_INTERVAL="auto"Apply the same pattern to every use of
$CONFIG_FILEand$conf. Additional hardening should include:- Use quoted arrays or null-delimited traversal rather than unquoted glob iteration when scanning agent files.
- Reject unexpected configuration paths where practical.
- Replace broad
except:handlers with explicit exception classes so programming errors and injection attempts are not silently hidden. - Use
re.fullmatch()for heartbeat values to reject trailing data. - Add regression tests with paths containing single quotes, double quotes, backslashes, spaces, newlines, and Python-like expressions.
- Run the scripts with the minimum required account privileges and avoid exposing sensitive credentials through inherited environment variables.
