T09 · Insecure Skill Coding Practices
- Location
scripts/liber_speech_client.py:37- Finding
Overbroad Loading of Secrets from the Shared OpenClaw Environment File
- Content
View full analysis
None: if not dotenv_path.exists(): return for raw_line in dotenv_path.read_text(encoding="utf-8").splitlines(): line = raw_line.strip() if not line or line.startswith("#") or "=" not in line: continue key, value = line.split("=", 1) key = key.strip() value = value.strip().strip('"').strip("'") os.environ.setdefault(key, value) ``` The function is applied to the shared OpenClaw credential file: ```python # If not found in environment variables, try loading from ~/.openclaw/.env if not base_url or not api_key: openclaw_env_path = Path.home() / ".openclaw" / ".env" load_dotenv(openclaw_env_path) base_url = os.getenv("LIBER_API_BASE_URL", "").strip().rstrip("/") api_key = os.getenv("LIBER_API_KEY", "").strip() ``` ### Technical Analysis The Skill only requires `LIBER_API_BASE_URL` and `LIBER_API_KEY`. However, `load_dotenv()` reads every assignment in `~/.openclaw/.env` and inserts every parsed value into the process environment. A shared OpenClaw environment file may contain credentials for unrelated services. Importing all of those values violates least privilege because the Skill does not need them for ASR or TTS operations. It also unnecessarily exposes the values to every module running in the same Python process. The reviewed code does not deliberately transmit unrelated environment variables. Nevertheless, the expanded credential scope increases the effect of a compromised dependency, future code defect, diagnostic routine, or other code executing in the process. ### Attack Path 1. A user stores Liber credentials and unrelated service credent ...[truncated 890 chars]- Remediation
View remediation
dict[str, str]: allowed = {"LIBER_API_BASE_URL", "LIBER_API_KEY"} result: dict[str, str] = {} if not path.is_file(): return result for raw_line in path.read_text(encoding="utf-8").splitlines(): line = raw_line.strip() if not line or line.startswith("#") or "=" not in line: continue key, value = line.split("=", 1) key = key.strip() if key in allowed: result[key] = value.strip().strip('"').strip("'") return result ``` 2. Return credentials as local values rather than writing them to `os.environ`. 3. Prefer a dedicated Skill-specific credential file over a shared global environment file. 4. Validate that credential files are regular files and, where supported, reject files with overly permissive filesystem permissions. 5. Document precisely which keys are read and why they are required. ]]>
