Back to skill

Security audit

Openrouter Free

Security checks for vulnerabilities and agentic risk

Overview

This skill does what it advertises, but it silently searches a fixed root workspace .env file for an OpenRouter API key, which is broader than the documentation discloses.

Install only if you are comfortable with prompts and model requests being sent to OpenRouter. Before use, remove or review the script's fallback read of /root/.openclaw/workspace/.env, provide OPENROUTER_API_KEY explicitly, keep .env out of version control, and avoid sending secrets or private data in chat prompts.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (1)

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
openrouter_free.py:14
Finding

Silent Access to a Hard-Coded Root Workspace Credential File

Content
View full analysis

Vulnerability Details

File Location: openrouter_free.py, lines 14–33
Vulnerability Type: Unauthorized credential access and insecure credential handling
Risk Level: Medium

Vulnerable Code

python
API_KEY = os.environ.get('OPENROUTER_API_KEY', '')
if not API_KEY:
    try:
        with open('/root/.openclaw/workspace/.env', 'r') as f:
            for line in f:
                line = line.strip()
                if line and not line.startswith('#') and 'OPENROUTER_API_KEY' in line:
                    API_KEY = line.split('=', 1)[1].strip()
    except:
        pass

API_BASE = "https://openrouter.ai/api/v1"
HEADERS = {
    "Authorization": f"Bearer {API_KEY}",
    "Content-Type": "application/json",
    "HTTP-Referer": "https://openclaw.ai",
    "X-Title": "OpenClaw Free Models"
}

The credential is transmitted by the following request operations:

python
resp = requests.get(f"{API_BASE}/models", headers=HEADERS, timeout=15)
python
resp = requests.post(url, headers=HEADERS, json=data, timeout=60)

Technical Analysis

When OPENROUTER_API_KEY is absent from the process environment, the program silently attempts to read /root/.openclaw/workspace/.env. This fixed, privileged path may belong to a different workspace or execution context and is not the project-local .env implied by the documentation.

This behavior violates least privilege because listing models or submitting a chat request does not require searching another workspace for credentials. If the process can read the file, it may acquire and use a key that the invoking user did not explicitly provide to this Skill.

Credential parsing is also imprecise: the condition checks whether OPENROUTER_API_KEY occurs anywhere in a line rather than requiring an exact variable name. A line such as BACKUP_OPENROUTER_API_KEY=value could therefore be selected unintentionally. The bare except suppr ...[truncated 1969 chars]

Remediation
View remediation

Remediation Suggestions

  1. Remove the fallback that reads /root/.openclaw/workspace/.env.
  2. Require callers to provide OPENROUTER_API_KEY explicitly through the process environment or an approved secret manager.
  3. If .env support is required, load only a project-local or explicitly supplied path after obtaining user consent. Resolve and validate the path to prevent crossing workspace boundaries.
  4. Parse environment files with a maintained parser and require an exact key-name match rather than substring matching.
  5. Replace the bare except with specific exception handling, such as FileNotFoundError, PermissionError, and parsing errors. Report failures without printing secret values.
  6. Do not place an authorization header on endpoints that can be accessed without authentication. Construct request headers per operation.
  7. Document exactly which information is transmitted to OpenRouter: the API key, model identifier, and user-provided chat content.
  8. Keep credentials out of logs and error messages, apply restrictive file permissions where local secret files are explicitly used, and rotate any key suspected of having been accessed by an unintended workspace.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (12)

Tainted flow: 'HEADERS' from os.environ.get (line 25, credential/environment) → requests.get (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · openrouter_free.py (reported line 36)May include surrounding context.

python
def list_free_models():
    """获取所有免费模型"""
    resp = requests.get(f"{API_BASE}/models", headers=HEADERS, timeout=15)
    resp.raise_for_status()
    data = resp.json()

Tainted flow: 'HEADERS' from os.environ.get (line 25, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · openrouter_free.py (reported line 66)May include surrounding context.

python
"max_tokens": 2048,
    }
    
    resp = requests.post(url, headers=HEADERS, json=data, timeout=60)
    if resp.status_code == 403 and 'region' in resp.text.lower():
        return None, f"模型 {model} 在当前地区不可用(403)"

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

The documented behavior claims the skill can be used 'for free' but omits a material requirement: the user must supply an OpenRouter API key, and the code reportedly reads a local .env file to obtain it. This mismatch can mislead users about authentication and local secret access, reducing informed consent and increasing the chance of unintended credential exposure.

Content

No source excerpt is available for this finding.

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · SKILL.md (reported line 29)May include surrounding context.

需要配置 OpenRouter API Key:

bash
# 在 .env 文件中添加
OPENROUTER_API_KEY=sk-or-v1-your-key-here

Credential Access

High
Category
Privilege Escalation
Confidence
94% confidence
Finding

Directly reading a local .env file is credential-access behavior that can expose secrets to this skill without explicit scoping or least-privilege controls. In an agent environment, such file access is more dangerous because the skill may inherit access to workspace secrets unrelated to the immediate user action, enabling unauthorized use of paid APIs or later leakage if the code is modified.

Content

Scanner excerpt · openrouter_free.py (reported line 16)May include surrounding context.

python
API_KEY = os.environ.get('OPENROUTER_API_KEY', '')
if not API_KEY:
    try:
        with open('/root/.openclaw/workspace/.env', 'r') as f:
            for line in f:
                line = line.strip()
                if line and not line.startswith('#') and 'OPENROUTER_API_KEY' in line:

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
88% confidence
Finding

The skill advertises functionality that inherently needs network access and likely reads environment-based credentials, but it does not declare any tool scope or permissions. This weakens reviewability and consent because users cannot clearly see that the skill may access secrets and make outbound requests before using it.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The instructions tell users to place a live API key in a local .env file but provide no warning about secret handling, exclusion from version control, or avoiding logging and sharing. This increases the risk of accidental credential disclosure through commits, screenshots, workspace sharing, or downstream tooling that reads local files.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The script reads a bearer token from a local workspace .env file, expanding its access to credentials beyond explicit user input or process environment. In an agent-skill context, this increases the blast radius because the skill can silently consume locally stored secrets, which is unnecessary and surprising unless clearly documented and consented to.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
84% confidence
Finding

The script transmits user prompt content to an external service, which is expected for the feature but still security-relevant because it moves potentially sensitive data off-host. In this skill context, the danger is moderated by the declared purpose, but it remains important because there is no minimization, warning, or consent mechanism.

Content

Scanner excerpt · openrouter_free.py (reported line 66)May include surrounding context.

python
"max_tokens": 2048,
    }
    
    resp = requests.post(url, headers=HEADERS, json=data, timeout=60)
    if resp.status_code == 403 and 'region' in resp.text.lower():
        return None, f"模型 {model} 在当前地区不可用(403)"

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

User-supplied prompts are transmitted to an external provider without any explicit warning, consent flow, or data-handling notice. In a chat utility, users may paste sensitive content, so silent transmission to a third party creates a privacy and compliance risk even if it is core functionality.

Content

No source excerpt is available for this finding.

Tainted flow: 'data' from requests.get (line 38, network input) → requests.post (network output)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · openrouter_free.py (reported line 66)May include surrounding context.

python
"max_tokens": 2048,
    }
    
    resp = requests.post(url, headers=HEADERS, json=data, timeout=60)
    if resp.status_code == 403 and 'region' in resp.text.lower():
        return None, f"模型 {model} 在当前地区不可用(403)"

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
89% confidence
Finding

Natural-language strings throughout the file, including the module description and CLI messages, are presented only in Chinese. This forces a specific language experience without any visible user opt-in or indication that the tool is intentionally limited to a Chinese-speaking audience.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.