T09 · Insecure Skill Coding Practices
- Location
scripts/openrouter_analytics.py:33- Finding
Overbroad Automatic Loading of Credential Files
- Content
View full analysis
Vulnerability Details
File Location:
scripts/openrouter_analytics.py:33-49, 503-506
Vulnerability Type: Overbroad credential-file access and environment pollution
Risk Level: MediumVulnerable Code
python def load_dotenv(path: str) -> None: """Minimal .env loader (KEY=VALUE, ignores comments/blank lines).""" if not os.path.exists(path): return try: with open(path, "r", encoding="utf-8") as f: for line in f: s = line.strip() if not s or s.startswith("#") or "=" not in s: continue k, v = s.split("=", 1) k = k.strip() v = v.strip().strip('"').strip("'") if k and k not in os.environ: os.environ[k] = v except OSError: passpython def main() -> int: # Auto-load .env hints before parsing/executing commands load_dotenv(os.path.expanduser("~/.openclaw/.env")) load_dotenv(os.path.join(os.getcwd(), ".env"))Technical Analysis
The Skill requires only
OPENROUTER_MANAGEMENT_KEYandOPENROUTER_API_KEY, but its generic dotenv loader reads every entry from~/.openclaw/.envand the current working directory’s.env. Every parsed variable that is not already set is copied into the process environment.This exceeds the minimum credential access required for OpenRouter analytics. The files may contain unrelated database passwords, cloud credentials, access tokens, or other secrets. Those values become available to the entire Python process and any code subsequently imported or added to it.
Loading
.envfromos.getcwd()also makes credential selection dependent on the caller’s working directory. An unintended or attacker-controlled directory can therefore supply anOPENROUTER_MANAGEMENT_KEYorOPENROUTER_API_KEYwhen the corresponding variable is not already pres ...[truncated 1957 chars]- Remediation
View remediation
Remediation Suggestions
- Remove automatic loading of generic dotenv files where possible. Prefer credentials already supplied through the environment or an explicit user-selected credential file.
- If dotenv support is retained, parse and retain only
OPENROUTER_MANAGEMENT_KEYandOPENROUTER_API_KEY; do not copy unrelated entries intoos.environ. - Avoid implicit current-working-directory discovery. Use an explicit
--env-fileoption or a fixed, documented configuration path. - Keep parsed credentials in narrowly scoped variables instead of mutating the global process environment.
- Validate explicitly selected credential files, including regular-file status and, where supported, ownership and restrictive permissions. Consider rejecting symlinks and group/world-readable files.
- Report which credential source was selected without ever printing the credential value.
- Update
SKILL.mdto describe the exact credential precedence and restricted variable allowlist. - Add tests proving that unrelated dotenv entries are neither imported into
os.environnor included in outbound requests.
A safer loader should allowlist only the required variable:
python ALLOWED_KEYS = { "OPENROUTER_MANAGEMENT_KEY", "OPENROUTER_API_KEY", } def load_openrouter_credentials(path: str) -> dict[str, str]: values: dict[str, str] = {} with open(path, "r", encoding="utf-8") as f: for line in f: s = line.strip() if not s or s.startswith("#") or "=" not in s: continue key, value = s.split("=", 1) key = key.strip() if key in ALLOWED_KEYS: values[key] = value.strip().strip('"').strip("'") return values
