Back to skill

Security audit

Add Top OpenRouter Models

Security checks for vulnerabilities and agentic risk

Overview

The skill’s main behavior is disclosed and purpose-aligned, but it has a concrete local credential-permission risk when rewriting OpenClaw config files.

Review this before installing if your OpenClaw config contains an OpenRouter API key. The intended model sync is clear, but run dry-run first, confirm the exact models and files to be changed, and avoid using it until the config rewrite preserves restrictive permissions such as 0600. Also watch for accidental invocation from broad model-management prompts.

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 (1)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/sync-openrouter-models.py:185
Finding
Configuration Replacement May Weaken Permissions on Files Containing API Credentials<![CDATA[ ## Vulnerability Details **File Location**: `scripts/sync-openrouter-models.py`, lines 185-195 **Vulnerability Type**: Unsafe temporary-file permissions and failure to preserve destination permissions **Risk Level**: Medium ### Vulnerable Code ```python def safe_write_json(filepath: str, data: dict) -> None: """Write JSON atomically: write to temp, then rename.""" tmp = filepath + ".tmp" try: with open(tmp, "w") as f: json.dump(data, f, indent=2) os.replace(tmp, filepath) # atomic on same filesystem except Exception: if os.path.exists(tmp): os.remove(tmp) raise ``` ### Technical Analysis The function writes updated configuration data to a newly created temporary file and then replaces the original configuration with `os.replace()`. The temporary file is created using the process's default umask-derived permissions; the function neither applies a restrictive mode nor preserves the original destination file's mode. For example, under a common `022` umask, the temporary file will ordinarily be created with mode `0644`. Replacing an original configuration file whose mode was `0600` does not preserve that original mode—the replacement retains the temporary file's permissions. This is security-sensitive because the affected OpenClaw configuration files may contain an OpenRouter API credential. The script explicitly retrieves such credentials from these files: ```python key = root.get("providers", {}).get("openrouter", {}).get("apiKey") ``` Consequently, a normal model synchronization operation can unintentionally make a previously owner-only credential readable by other local users. Exploitation depends on another user being able to traverse the parent directories and read the resulting file. The authenticated OpenRouter request itself is consistent with the Skill's declared model-verification functionality: the credential is sent only to the hard-coded `https://openrouter.ai/a ...[truncated 1391 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Use a securely created temporary file in the destination directory and explicitly preserve or restrict its permissions before replacement. Recommended hardening steps: 1. Read the original file's mode with `os.stat(filepath).st_mode` when it exists. 2. Create the temporary file using `tempfile.mkstemp(dir=destination_directory)` to avoid a predictable shared temporary filename. 3. Apply the original mode with `os.fchmod()`, capped to a secure maximum, or enforce mode `0600` for configuration files containing credentials. 4. Flush buffered data and call `os.fsync()` before replacement if durability is required. 5. Replace the destination atomically only after serialization and permission assignment succeed. 6. Remove the temporary file on every error path. 7. Add a regression test that begins with a `0600` configuration file, performs an update, and verifies that the final file remains `0600`. Example hardened pattern: ```python import os import stat import tempfile def safe_write_json(filepath: str, data: dict) -> None: directory = os.path.dirname(filepath) or "." original_mode = 0o600 if os.path.exists(filepath): original_mode = stat.S_IMODE(os.stat(filepath).st_mode) fd, tmp = tempfile.mkstemp(prefix=".openclaw-", suffix=".tmp", dir=directory) try: os.fchmod(fd, original_mode & 0o600) with os.fdopen(fd, "w") as f: json.dump(data, f, indent=2) f.flush() os.fsync(f.fileno()) os.replace(tmp, filepath) except Exception: try: os.close(fd) except OSError: pass if os.path.exists(tmp): os.remove(tmp) raise ``` If broader permissions are intentionally supported, preserve the exact original mode instead of applying `original_mode & 0o600`; however, credential-bearing configuration files should normally remain owner-readable and owner-writable only. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (6)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The skill description promises a leaderboard-derived sync workflow, but the actual behavior described depends on manually extracted or user-supplied model IDs and a separate catalog verification step. This mismatch is security-relevant because it can cause users to trust the skill to perform bounded, source-verified synchronization when in reality it may accept broader input and make persistent configuration changes based on incomplete or misleading provenance.

Lp3

Medium
Category
MCP Least Privilege
Confidence
90% confidence
Finding
The skill instructs the agent to use browser, network access, environment variables, and write to user configuration files, but it declares no explicit tool scope or permission boundaries. That makes the skill more dangerous because an operator cannot readily see or constrain what resources it may access or modify, increasing the risk of unintended file changes or data exposure if the skill is invoked in a broader tool-enabled environment.

Description-Behavior Mismatch

Medium
Confidence
96% confidence
Finding
The manifest says this skill fetches the OpenClaw app leaderboard from OpenRouter and syncs those models, implying autonomous discovery of relevant models. The code and module docstring show a different workflow: it only accepts model IDs from --models or stdin, then verifies and adds them, with no leaderboard retrieval logic anywhere in the file.

Vague Triggers

Medium
Confidence
88% confidence
Finding
The trigger phrase "add missing models" is broad enough to match common user requests that may not specifically intend to invoke this skill, creating a risk of accidental or inappropriate activation. In this skill's context, unintended execution could modify installation configuration and fetch external model data, so ambiguous routing increases the chance of unauthorized or surprising config changes.

Missing User Warnings

Low
Confidence
82% confidence
Finding
This markdown file describes behavior that performs browser-based extraction and API verification, both of which imply network activity. The changelog does not include any warning or disclosure about contacting external services or transmitting data, which is relevant for markdown files under the missing user warnings category.

Missing User Warnings

Low
Confidence
88% confidence
Finding
This entry states that the skill writes configuration files and creates backups, which affects local user data. The markdown provides no warning that running the skill may modify configuration state, so users may not realize the operation is not read-only.