Back to skill

Security audit

Updating OpenRouter Free Models

Security checks for vulnerabilities and agentic risk

Overview

This skill has a coherent model-update purpose, but it can expose the wrong API token and makes persistent live agent configuration and service changes with weak safeguards.

Review carefully before installing. Use only an OpenRouter-scoped API key, do not rely on ANTHROPIC_AUTH_TOKEN, back up ~/.claude/settings.json and ~/.openclaw/openclaw.json, avoid complete_test.sh on a live setup, inspect /tmp/verified_models.txt before applying, and confirm any OpenClaw restart manually.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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
Findings (3)

T09 · Insecure Skill Coding Practices

Error
Location
test_models.py:14
Finding
Cross-Provider API Credential Disclosure<![CDATA[ ## Vulnerability Details **File Location**: `test_models.py`, lines 14–28 and 41–55 **Vulnerability Type**: Cross-provider credential disclosure **Risk Level**: High ### Vulnerable Code ```python def test_model(model_id, token, timeout=30): """Test if a model is available. Returns (success, error_msg).""" cmd = [ "curl", "-s", "-m", str(timeout), "-X", "POST", "https://openrouter.ai/api/v1/chat/completions", "-H", f"Authorization: Bearer {token}", "-H", "Content-Type: application/json", "-d", json.dumps({ "model": model_id, "messages": [{"role": "user", "content": "test"}], "max_tokens": 5 }) ] result = subprocess.run(cmd, capture_output=True, text=True) ``` ```python # Try ANTHROPIC_AUTH_TOKEN first (for Claude Code compatibility) token = os.environ.get('ANTHROPIC_AUTH_TOKEN', '').strip() # If not set, try OPENROUTER_API_KEY (for OpenRouter) if not token: token = os.environ.get('OPENROUTER_API_KEY', '').strip() # If still not set, try reading from OpenClaw config if not token: try: with open(Path.home() / '.openclaw' / 'openclaw.json') as f: cfg = json.load(f) token = cfg.get('models', {}).get('providers', {}).get('openrouter', {}).get('apiKey', '').strip() except Exception: pass ``` ### Technical Analysis The script prioritizes `ANTHROPIC_AUTH_TOKEN` over `OPENROUTER_API_KEY` and then unconditionally sends the selected value to the OpenRouter chat-completions endpoint as a bearer credential. Environment-variable names normally establish a provider trust boundary. A genuine Anthropic credential stored in `ANTHROPIC_AUTH_TOKEN` is not necessarily intended for disclosure to OpenRouter. Treating it as an OpenRouter credential can therefore transmit a secret to a different service without an explicit provider check or informed user opt-in. The a ...[truncated 1917 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove support for `ANTHROPIC_AUTH_TOKEN` from requests sent to OpenRouter. 2. Accept only an explicitly OpenRouter-scoped source: - `OPENROUTER_API_KEY`; or - `models.providers.openrouter.apiKey` from the OpenClaw configuration. 3. Prefer `OPENROUTER_API_KEY` over configuration-file fallback to reduce unnecessary access to persistent secrets. 4. If compatibility mode is essential, require an explicit command-line option such as `--use-anthropic-auth-token-for-openrouter` and display the destination before sending the value. 5. Update `README.md`, `SKILL.md`, and related installation instructions so they do not encourage cross-provider credential reuse. 6. Avoid placing the authorization header in a command-line argument. Use a native HTTPS client or otherwise pass secrets through a mechanism that does not expose them in the spawned process's argument list. 7. Add tests confirming that a populated `ANTHROPIC_AUTH_TOKEN` is never transmitted to OpenRouter by default. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
fetch_models.py:67
Finding
Predictable Shared Temporary Files Allow Symlink and Workflow-Result Manipulation<![CDATA[ ## Vulnerability Details **File Location**: `fetch_models.py`, lines 67–70; `test_models.py`, lines 64–70 and 86–89; `apply_updates.py`, lines 99–105; `apply_updates_openclaw.js`, lines 13 and 85–94 **Vulnerability Type**: Unsafe predictable temporary files **Risk Level**: Medium ### Vulnerable Code From `fetch_models.py`: ```python def main(): output_file = Path('/tmp/free_models.txt') models = fetch_free_models() output_file.write_text('\n'.join(models)) ``` From `test_models.py`: ```python models_file = Path('/tmp/free_models.txt') if not models_file.exists(): print(f"Error: {models_file} not found. Run fetch_models.py first.", file=sys.stderr) sys.exit(1) models = models_file.read_text().strip().split('\n') ``` ```python # Save results Path('/tmp/verified_models.txt').write_text('\n'.join(verified)) Path('/tmp/failed_models.txt').write_text( '\n'.join(f"{m}:{e}" for m, e in failed) ) ``` From `apply_updates.py`: ```python def main(): verified_file = Path('/tmp/verified_models.txt') if not verified_file.exists(): print("Error: /tmp/verified_models.txt not found. Run test_models.py first.", file=sys.stderr) sys.exit(1) verified_models = verified_file.read_text().strip().split('\n') ``` From `apply_updates_openclaw.js`: ```javascript const VERIFIED_MODELS_FILE = '/tmp/verified_models.txt'; ``` ```javascript if (!fs.existsSync(VERIFIED_MODELS_FILE)) { log(`${VERIFIED_MODELS_FILE} not found. Please run test_models.js first.`, 'error'); process.exit(1); } const verifiedModels = fs.readFileSync(VERIFIED_MODELS_FILE, 'utf8') .trim() .split('\n') .filter(line => line.trim().length > 0); ``` ### Technical Analysis The workflow exchanges security-relevant data through fixed, globally predictable paths in `/tmp`. It does not create a private working directory, verify file ownership, verify that each pa ...[truncated 2914 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Create a private per-run directory using `tempfile.TemporaryDirectory()` or `tempfile.mkdtemp()`. 2. Set directory permissions to `0700` and file permissions to `0600`. 3. Pass the private directory or explicit result path between workflow stages instead of using global fixed filenames. 4. Create output files atomically and exclusively, with no-follow semantics where supported. 5. Before consuming a file: - use `lstat`; - reject symbolic links and non-regular files; - verify that the current user owns the file; - verify restrictive permissions; and - enforce a reasonable maximum size. 6. Use atomic replacement for generated output and live configuration files. 7. Cryptographically bind or directly pipe verified results to the updater if stages must remain separate. 8. Validate every model identifier against the fetched OpenRouter response and an allowlisted identifier format before updating configuration. 9. Delete the private temporary directory after completion and ensure cleanup occurs on errors and signals. ]]>

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
complete_test.sh:25
Finding
Installation Test Mutates Live OpenClaw Configuration Without Explicit Confirmation<![CDATA[ ## Vulnerability Details **File Location**: `complete_test.sh`, lines 25–34 **Vulnerability Type**: Unexpected live configuration modification during testing **Risk Level**: Medium ### Vulnerable Code ```bash # Test 3: Apply updates echo -e "\n[3/4] Testing apply_updates_openclaw.js..." node apply_updates_openclaw.js # Test 4: Validate JSON echo -e "\n[4/4] Validating configurations..." python3 -c " import json from pathlib import Path files = [ str(Path.home() / '.claude' / 'settings.json'), str(Path.home() / '.openclaw' / 'openclaw.json') ] ``` The invoked updater performs a live write in `apply_updates_openclaw.js`: ```javascript // Write back with formatting fs.writeFileSync(OPENCLAW_CONFIG, JSON.stringify(config, null, 2) + '\n'); ``` ### Technical Analysis `complete_test.sh` is presented as a test and is recommended as an installation-verification command. However, its third stage invokes the production updater against the user’s real `~/.openclaw/openclaw.json`. The script does not use a temporary configuration fixture, does not provide a dry-run mode, does not create a backup, and does not request confirmation before replacing live model and fallback sections. Consequently, running what appears to be an integration or installation test grants the test workflow write access to persistent user configuration beyond what is required to verify that the package is installed. The modification broadly relates to the Skill’s declared configuration-management functionality, so it is not a hidden backdoor. The security concern is the mismatch between the command’s presentation as a test and its production side effects. ### Attack Path 1. A user or AI Agent follows the installation instructions that recommend running `./complete_test.sh`. 2. The command fetches models and reduces the fetched list to a five-model sample. 3. `test_models.py` writes the successfully tested sample to `/tmp/verified_models.txt`. 4. The “Test 3” stage ...[truncated 1023 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Make `complete_test.sh` non-destructive by default. 2. Copy representative configuration into a temporary directory and run the updater against that fixture. 3. Add an explicit target-path option to each updater rather than hardcoding home-directory configuration paths. 4. Implement a `--dry-run` mode that displays a structured diff without writing changes. 5. Require an explicit flag such as `--apply-live` and interactive confirmation before modifying real configuration. 6. Create a timestamped backup before any live update and restore it automatically if validation fails. 7. Generate and validate the complete new configuration before atomically replacing the original. 8. Rename any destructive command from “test” to “apply” or “update” so its side effects are clear. 9. Update `README.md` and `INSTALLATION.md` to distinguish safe installation checks from production configuration updates. 10. Add regression tests verifying that ordinary test commands never modify files under `~/.openclaw` or `~/.claude`. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Rogue AgentSelf-Modification, Session Persistence
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (34)

Tool Parameter Abuse

High
Category
Tool Misuse
Content
```bash
cd ~/.openclaw/workspace/skills/updating-openrouter-free-models
# Optional: clean old cache
rm -f /tmp/free_models.txt /tmp/verified_models.txt /tmp/failed_models.txt
# Run full update
python3 fetch_models.py && python3 test_models.py && node apply_updates_openclaw.js
# Restart
Confidence
85% confidence
Finding
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Agent Config Directory Access

High
Category
Agent Snooping
Content
./restart_openclaw.sh

# 5. Validate
python3 -m json.tool ~/.claude/settings.json  # Claude
python3 -m json.tool ~/.openclaw/openclaw.json  # OpenClaw
```
Confidence
91% confidence
Finding
This documentation directs interaction with sensitive agent configuration paths under the user's home directory. In the context of an install/update skill, access to ~/.claude/settings.json and ~/.openclaw/openclaw.json is security-relevant because these files control model/provider behavior and can be altered to change agent operation or break service availability.

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding
The skill’s declared purpose is configuration maintenance, but it also directs service management actions including restarting OpenClaw, interacting with launchctl, killing processes, and spawning background services. Hidden operational side effects increase risk because an agent or user may approve a config-update workflow without realizing it includes system-level process control.

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding
The skill’s declared purpose is configuration maintenance, but it also directs service management actions including restarting OpenClaw, interacting with launchctl, killing processes, and spawning background services. Hidden operational side effects increase risk because an agent or user may approve a config-update workflow without realizing it includes system-level process control.

Agent Config Directory Access

High
Category
Agent Snooping
Content
")
```

Then manually or programmatically insert into `~/.claude/settings.json`.

### Step 4: Update OpenClaw Configuration
Confidence
97% confidence
Finding
The skill targets `~/.claude/settings.json`, a sensitive agent configuration file that influences model availability and behavior. Writing to agent config directories is security-relevant because it can persistently alter how the assistant operates, potentially causing denial of service, misrouting, or unsafe defaults if abused or performed incorrectly.

Agent Config Directory Access

High
Category
Agent Snooping
Content
return False, str(e)

def update_claude_settings(verified_models):
    """Update ~/.claude/settings.json with verified models."""
    settings_path = Path.home() / '.claude' / 'settings.json'

    if not settings_path.exists():
Confidence
90% confidence
Finding
Skill reads from agent configuration directories (.claude/, .codex/, .gemini/). These directories may contain API keys, personal settings, and other credentials that the skill has no legitimate need to access.

Agent Config Directory Access

High
Category
Agent Snooping
Content
return False, str(e)

def update_claude_settings(verified_models):
    """Update ~/.claude/settings.json with verified models."""
    settings_path = Path.home() / '.claude' / 'settings.json'

    if not settings_path.exists():
Confidence
90% confidence
Finding
Skill reads from agent configuration directories (.claude/, .codex/, .gemini/). These directories may contain API keys, personal settings, and other credentials that the skill has no legitimate need to access.

Agent Config Directory Access

High
Category
Agent Snooping
Content
return False, str(e)

def update_claude_settings(verified_models):
    """Update ~/.claude/settings.json with verified models."""
    settings_path = Path.home() / '.claude' / 'settings.json'

    if not settings_path.exists():
Confidence
90% confidence
Finding
Skill reads from agent configuration directories (.claude/, .codex/, .gemini/). These directories may contain API keys, personal settings, and other credentials that the skill has no legitimate need to access.

Agent Config Directory Access

High
Category
Agent Snooping
Content
return False, str(e)

def update_claude_settings(verified_models):
    """Update ~/.claude/settings.json with verified models."""
    settings_path = Path.home() / '.claude' / 'settings.json'

    if not settings_path.exists():
Confidence
90% confidence
Finding
Skill reads from agent configuration directories (.claude/, .codex/, .gemini/). These directories may contain API keys, personal settings, and other credentials that the skill has no legitimate need to access.

Agent Config Directory Access

High
Category
Agent Snooping
Content
return False, str(e)

def update_claude_settings(verified_models):
    """Update ~/.claude/settings.json with verified models."""
    settings_path = Path.home() / '.claude' / 'settings.json'

    if not settings_path.exists():
Confidence
90% confidence
Finding
Skill reads from agent configuration directories (.claude/, .codex/, .gemini/). These directories may contain API keys, personal settings, and other credentials that the skill has no legitimate need to access.

Agent Config Directory Access

High
Category
Agent Snooping
Content
return False, str(e)

def update_claude_settings(verified_models):
    """Update ~/.claude/settings.json with verified models."""
    settings_path = Path.home() / '.claude' / 'settings.json'

    if not settings_path.exists():
Confidence
90% confidence
Finding
Skill reads from agent configuration directories (.claude/, .codex/, .gemini/). These directories may contain API keys, personal settings, and other credentials that the skill has no legitimate need to access.

Agent Config Directory Access

High
Category
Agent Snooping
Content
return False, str(e)

def update_claude_settings(verified_models):
    """Update ~/.claude/settings.json with verified models."""
    settings_path = Path.home() / '.claude' / 'settings.json'

    if not settings_path.exists():
Confidence
90% confidence
Finding
Skill reads from agent configuration directories (.claude/, .codex/, .gemini/). These directories may contain API keys, personal settings, and other credentials that the skill has no legitimate need to access.

Agent Config Directory Access

High
Category
Agent Snooping
Content
return False, str(e)

def update_claude_settings(verified_models):
    """Update ~/.claude/settings.json with verified models."""
    settings_path = Path.home() / '.claude' / 'settings.json'

    if not settings_path.exists():
Confidence
90% confidence
Finding
Skill reads from agent configuration directories (.claude/, .codex/, .gemini/). These directories may contain API keys, personal settings, and other credentials that the skill has no legitimate need to access.

Agent Config Directory Access

High
Category
Agent Snooping
Content
return False, str(e)

def update_claude_settings(verified_models):
    """Update ~/.claude/settings.json with verified models."""
    settings_path = Path.home() / '.claude' / 'settings.json'

    if not settings_path.exists():
Confidence
90% confidence
Finding
Skill reads from agent configuration directories (.claude/, .codex/, .gemini/). These directories may contain API keys, personal settings, and other credentials that the skill has no legitimate need to access.

Agent Config Directory Access

High
Category
Agent Snooping
Content
return False, str(e)

def update_claude_settings(verified_models):
    """Update ~/.claude/settings.json with verified models."""
    settings_path = Path.home() / '.claude' / 'settings.json'

    if not settings_path.exists():
Confidence
90% confidence
Finding
Skill reads from agent configuration directories (.claude/, .codex/, .gemini/). These directories may contain API keys, personal settings, and other credentials that the skill has no legitimate need to access.

Agent Config Directory Access

High
Category
Agent Snooping
Content
return False, str(e)

def update_claude_settings(verified_models):
    """Update ~/.claude/settings.json with verified models."""
    settings_path = Path.home() / '.claude' / 'settings.json'

    if not settings_path.exists():
Confidence
90% confidence
Finding
Skill reads from agent configuration directories (.claude/, .codex/, .gemini/). These directories may contain API keys, personal settings, and other credentials that the skill has no legitimate need to access.

Agent Config Directory Access

High
Category
Agent Snooping
Content
return False, str(e)

def update_claude_settings(verified_models):
    """Update ~/.claude/settings.json with verified models."""
    settings_path = Path.home() / '.claude' / 'settings.json'

    if not settings_path.exists():
Confidence
90% confidence
Finding
Skill reads from agent configuration directories (.claude/, .codex/, .gemini/). These directories may contain API keys, personal settings, and other credentials that the skill has no legitimate need to access.

Agent Config Directory Access

High
Category
Agent Snooping
Content
return False, str(e)

def update_claude_settings(verified_models):
    """Update ~/.claude/settings.json with verified models."""
    settings_path = Path.home() / '.claude' / 'settings.json'

    if not settings_path.exists():
Confidence
90% confidence
Finding
Skill reads from agent configuration directories (.claude/, .codex/, .gemini/). These directories may contain API keys, personal settings, and other credentials that the skill has no legitimate need to access.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The documentation instructs users to apply configuration changes and restart a running service without an explicit warning that this modifies user state and may interrupt availability. In a skill context, this can lead to unintended downtime or accidental overwriting of a user's working configuration, especially if followed blindly or automated by an agent.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The one-command workflow hides several state-changing operations behind a single command, including testing, config modification, and restart behavior, without warning the user. This increases the risk that a user or agent executes it without understanding its side effects, causing unexpected service interruption or configuration drift.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The README prominently describes automated configuration changes and service restarts as core behavior but does not provide an upfront safety warning about modifying local agent config files and disrupting running services. In an agent-executed workflow, this can lead users or downstream agents to perform impactful actions without informed consent, increasing the chance of unintended local misconfiguration or service interruption.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The installation prompts are written for an AI agent and instruct it to create directories, copy files, change permissions, and execute scripts and tests, but they omit explicit warnings about filesystem writes, network access, and execution side effects. This is dangerous because agentic installers can perform these actions automatically, potentially altering user environments and running unreviewed code with little friction.

Lp3

Medium
Category
MCP Least Privilege
Confidence
96% confidence
Finding
The skill clearly instructs use of shell commands, environment-based secrets, and reads/writes user configuration files, yet it declares no tool scope or permission boundary. This creates an overbroad execution surface where an agent may perform sensitive actions without explicit capability disclosure or user awareness.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The instructions modify `~/.claude/settings.json` and `~/.openclaw/openclaw.json` without clear backup, diff review, or confirmation guidance. Direct edits to agent configuration can break tooling, alter model routing, or persist unintended settings changes if the generated model list or update logic is wrong.

Session Persistence

Medium
Category
Rogue Agent
Content
config['model'] = primary
    config['availableModels'] = verified_models

    # Write with proper formatting
    with open(settings_path, 'w') as f:
        json.dump(config, f, indent=2)
        f.write('\n')
Confidence
84% confidence
Finding
The script persistently overwrites agent configuration based on unvalidated content from /tmp/verified_models.txt, causing long-lived changes to future tool behavior. Because /tmp is a shared, world-writable location on many systems, another local process could tamper with that file and inject arbitrary model IDs into the user's Claude/OpenClaw config, leading to persistent misconfiguration or redirection to unintended providers/models.

Static analysis

No suspicious patterns detected.