Back to skill

Security audit

OpenRouter Free Model Rotate

Security checks for vulnerabilities and agentic risk

Overview

The skill does what it claims, but it can persistently rewrite OpenClaw model configuration, expose API keys through examples, and signal local gateway processes with limited safeguards.

Review before installing. Use OPENROUTER_API_KEY rather than putting keys on the command line or in cron, run --scan or --no-update first, back up OpenClaw config files before applying changes, and avoid --restart or scheduled auto-rotation unless you accept persistent model-routing changes and possible gateway reload disruption.

Vulnerability Patterns
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • 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 (2)

T09 · Insecure Skill Coding Practices

Warning
Location
SKILL.md:84
Finding
OpenRouter API Key Exposed Through Command-Line and Cron Examples<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:28-46, 84-87`; `scripts/rotate_free_models.py:550-551` **Vulnerability Type**: Credential exposure through process arguments, shell history, and persistent scheduler configuration **Risk Level**: Medium ### Vulnerable Code ```markdown # SKILL.md:84-87 Run via cron every 6 hours for auto-rotation: 0 */6 * * * python3 rotate_free_models.py --api-key "sk-or-xxx" --restart > /var/log/model-rotate.log 2>&1 ``` ```python # scripts/rotate_free_models.py:550-551 parser.add_argument("--api-key", default=os.environ.get("OPENROUTER_API_KEY"), help="OpenRouter API key") ``` The quick-start examples at `SKILL.md:28-46` likewise recommend passing the key directly with `--api-key`. ### Technical Analysis The Skill accepts the OpenRouter API key through a command-line option and repeatedly recommends that method in its documentation. Command-line secrets may be exposed through: - Process listings and process-inspection interfaces while the command is running. - Shell command history. - Monitoring, debugging, or endpoint-management software that records command lines. - Persistent crontab content and administrative backups. - Accidental copying of commands into logs, tickets, or terminal transcripts. The script already supports `OPENROUTER_API_KEY`, so embedding the credential in command arguments or crontab is not necessary for the declared functionality. Although the script only sends the Bearer token to the fixed OpenRouter HTTPS endpoint, its local method of receiving the token is insecure. ### Attack Path 1. A user follows the documented example and invokes the script with a live key: ```bash scripts/rotate_free_models.py --api-key "sk-or-live-key" --restart ``` 2. The complete command is retained in shell history, stored in crontab, or temporarily exposed through process metadata. 3. An attacker with access to the same account, readable operational records, backups, or ...[truncated 669 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove command-line API keys from every example in `SKILL.md` and the script help text. 2. Recommend using `OPENROUTER_API_KEY` from a secret manager or a protected environment file. 3. For scheduled execution, load the secret from a file owned by the service account and restricted to mode `0600`; do not place the value directly in crontab. 4. Consider deprecating `--api-key`, or make it emit a warning explaining that command-line secrets may be observable. 5. Use a dedicated, least-privileged OpenRouter key with spending and usage limits where supported. 6. Ensure logs and exception handling never print request headers or the credential. 7. Rotate any production key that has already been stored in shell history, crontab, logs, or shared documentation. ]]>

T09 · Insecure Skill Coding Practices

Note
Location
scripts/rotate_free_models.py:508
Finding
Broad Gateway Process Matching Can Signal Unintended Processes<![CDATA[ ## Vulnerability Details **File Location**: `scripts/rotate_free_models.py:508-516` **Vulnerability Type**: Insufficient process identity validation before sending a signal **Risk Level**: Low ### Vulnerable Code ```python import subprocess result = subprocess.run(["pgrep", "-f", "openclaw-gateway"], capture_output=True, text=True) if result.returncode == 0: for pid in result.stdout.strip().split("\n"): os.kill(int(pid), signal.SIGUSR1) print(f" ✅ Sent SIGUSR1 to gateway (pid {pid.strip()})") return True except Exception: pass ``` ### Technical Analysis When the expected gateway PID file is unavailable or unusable, the restart implementation executes `pgrep -f openclaw-gateway`. The `-f` option searches each process's full command line rather than validating an exact executable identity. Every matching process is sent `SIGUSR1`. The code does not verify: - That the process executable is the genuine OpenClaw gateway. - That the process owner is the expected OpenClaw service account. - That only one intended gateway instance is selected. - That the matched command line has not merely included the search string as an argument. For processes without a custom `SIGUSR1` handler, the signal may terminate the process. The fallback therefore exceeds the minimum process-control scope needed to restart one known gateway. ### Attack Path 1. The normal file `~/.openclaw/state/run/gateway.pid` is absent, unreadable, malformed, or stale. 2. Another process has `openclaw-gateway` somewhere in its full command line, whether accidentally or deliberately. 3. The user runs the Skill with `--restart`. 4. The fallback `pgrep -f` returns both legitimate and unrelated matching process IDs. 5. The script sends `SIGUSR1` to every returned process it has permission to signal. 6. An unrelated process reloads unexpectedly or terminates, depending on its signal handling. An attacker would generally need the ability to start a matching process ...[truncated 514 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove the broad `pgrep -f` fallback and rely on a securely maintained PID file or service manager. 2. Before signaling a PID, verify its process owner and resolve its executable through the platform process interface. 3. Compare the resolved executable against an expected absolute path rather than searching arbitrary command-line text. 4. Signal only one validated gateway process unless multiple managed instances are explicitly supported. 5. If a service manager is used, invoke a narrowly scoped reload operation for the exact OpenClaw gateway service. 6. Treat missing, malformed, or stale PID files as a safe failure requiring explicit user intervention rather than broad process discovery. 7. Log the validated executable identity and PID before signaling, without exposing unrelated process command lines or credentials. ]]>
Vulnerability Patterns
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
Findings (7)

Lp3

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding
The skill advertises and instructs use of network access, file reads/writes, environment-secret handling, and shell execution, but it declares no explicit tool scope or permissions boundaries. In an agent environment, that means a caller may invoke a broadly capable workflow without clear least-privilege constraints, increasing the chance of unintended config modification, secret exposure, or command execution.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The quick-start section presents commands that update configuration and optionally restart a gateway as recommended usage before prominently warning about those side effects. This can lead users or agents to execute disruptive actions reflexively, causing downtime, broken routing, or accidental replacement of working model configuration.

Dynamic import via __import__()

Medium
Category
Dangerous Code Execution
Content
def api_request(api_key, endpoint, body, timeout=30):
    """Generic POST to OpenRouter."""
    req_data = json.dumps(body).encode()
    req = __import__("urllib.request").request.Request(
        f"{API_BASE}/{endpoint}",
        data=req_data,
        headers={
Confidence
75% confidence
Finding
Dynamic __import__() can load arbitrary modules at runtime, bypassing static analysis and potentially importing malicious code.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The script modifies `openclaw.json` and `models.json` automatically unless `--no-update` is supplied, with no confirmation prompt, dry-run default, backup, or explicit upfront warning. In a security-sensitive agent environment, silent configuration mutation can redirect future model selection and persist unintended or harmful changes, especially if run with elevated privileges or automated triggers.

Context-Inappropriate Capability

Medium
Confidence
94% confidence
Finding
The skill goes beyond scanning and config updates by discovering local processes and sending `SIGUSR1` to matching PIDs, including a broad `pgrep -f openclaw-gateway` fallback. This can affect unintended processes if names collide or if the runtime environment is shared, creating local integrity and availability risk through unauthorized process signaling.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
# Fallback
    try:
        import subprocess
        result = subprocess.run(["pgrep", "-f", "openclaw-gateway"], capture_output=True, text=True)
        if result.returncode == 0:
            for pid in result.stdout.strip().split("\n"):
                os.kill(int(pid), signal.SIGUSR1)
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Missing User Warnings

Low
Confidence
82% confidence
Finding
The code creates and writes persistent files under ~/.openclaw/state and optionally writes a JSON report to a user path. While the operations are visible in code, there is no general upfront disclosure that model test results and timestamps will be stored locally, which may matter for users expecting a read-only scan.

Static analysis

No suspicious patterns detected.