Back to skill

Security audit

OpenClaw Model Switcher

Security checks for vulnerabilities and agentic risk

Overview

The skill is purpose-aligned overall, but it can persistently change OpenClaw's default model and restart the gateway without the promised built-in model validation.

Review before installing. Use only with explicit, trusted model-switch requests, verify the target model is already configured in OpenClaw before running it, and expect a gateway restart/session interruption. Treat dry-run as not fully side-effect-free because it can create a backup file.

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/model_switch.py:73
Finding

Target Model Is Written Without Provider Validation

Content
View full analysis

Vulnerability Details

File Location: scripts/model_switch.py:73-77 and duplicate implementation in model_switch.py:61-72
Vulnerability Type: Missing input validation and configuration integrity checks
Risk Level: Medium

Vulnerable Code

python
new_config.setdefault('agents', {}).setdefault('defaults', {}).setdefault('model', {})
new_config['agents']['defaults']['model']['primary'] = self.new_model

with open(self.config_path, 'w', encoding='utf-8') as f:
    json.dump(new_config, f, indent=4, ensure_ascii=False)

The root-level duplicate implements the same unsafe operation:

python
if 'agents' not in new_config:
    new_config['agents'] = {}
if 'defaults' not in new_config['agents']:
    new_config['agents']['defaults'] = {}
if 'model' not in new_config['agents']['defaults']:
    new_config['agents']['defaults']['model'] = {}

new_config['agents']['defaults']['model']['primary'] = self.new_model

with open(OPENCLAW_CONFIG_PATH, 'w', encoding='utf-8') as f:
    json.dump(new_config, f, indent=4, ensure_ascii=False)

Technical Analysis

Both scripts take the target model directly from the positional command-line argument and assign it to agents.defaults.model.primary. Neither implementation parses models.providers or verifies that the supplied identifier corresponds to a configured model.

This contradicts the validation behavior documented in SKILL.md and README.md. Validation is described only as an agent workflow instruction and is not enforced by the code. Consequently, direct standalone execution, invocation by another automation component, or an agent failure to follow the documented workflow bypasses the safeguard entirely.

The post-restart health check only verifies that the gateway command succeeds and that its output contains running. It does not verify that the newly selected model exists, can be initialized, or can successfully process a request. T ...[truncated 1630 chars]

Remediation
View remediation

Remediation Suggestions

  1. Load and normalize the configured model identifiers from models.providers before creating a backup or modifying the configuration.
  2. Require the supplied value to resolve to exactly one configured canonical model identifier. Reject unknown and ambiguous partial matches.
  3. If aliases are supported, maintain an explicit alias map and resolve aliases only to models present in the current configuration.
  4. Apply the same validation in both scripts/model_switch.py and the root-level duplicate, or remove the duplicate and maintain one authoritative implementation.
  5. Validate the configuration schema and ensure agents, defaults, and model are objects before modifying nested fields.
  6. After restart, perform a model-level readiness or minimal inference check for the selected model instead of relying solely on the gateway's generic running status.
  7. If model-level verification fails, restore the original configuration atomically and verify that the previous model is operational.
  8. Write the updated configuration to a securely created temporary file in the same directory, flush and synchronize it, preserve restrictive permissions, and atomically replace the original file.
Vulnerability Patterns
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • 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 (14)

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

The implementation largely matches the stated purpose: it reads and writes the OpenClaw config, creates backups, changes only agents.defaults.model.primary, restarts the gateway, checks status, and rolls back if restart/status checks fail. However, a key declared safety behavior is missing: there is no validation that the requested model exists in the configured provider list. The code accepts any new_model string and writes it directly into the config. This is a material description-to-behavior mismatch because the description explicitly promises validation as part of the safe switching behavior.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding

The overall purpose of the code matches the declared description closely: it targets OpenClaw model switching, reads the config file, creates backups, modifies only the primary default model field, restarts the gateway, and performs rollback if restart/health checks fail. However, a key declared safety behavior is missing: there is no implementation that inspects the configured provider/model list or verifies that the requested model exists before writing it into the configuration. This is a material mismatch because the description explicitly promises validation of the target model, but the code accepts any model string and writes it directly.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

High
Category
Not specified by scanner
Confidence
98% confidence
Finding

The skill metadata claims it validates that the requested model exists in the configured provider list, but the implementation writes any supplied string directly into agents.defaults.model.primary. This can break service startup, force repeated restart failures, or route future requests to an unintended model identifier, making the tool less safe than advertised.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
86% confidence
Finding

The skill advertises broad natural-language triggers such as 'switch model' and colloquial variants without clearly constraining confirmation, authorization, or exact matching behavior. In an agentic environment, this can cause unintended execution from ambiguous user phrasing or prompt-injected text, leading to unauthorized configuration changes and service disruption through gateway restarts.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
93% confidence
Finding

The skill declares no explicit tool scope despite instructing file reads, file writes, environment access, and shell execution. This violates least privilege and can allow the skill to run with broader capabilities than users or policy reviewers expect, increasing the blast radius if the skill is invoked unexpectedly or modified later.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

Broad trigger phrases like 'switch model' or 'change model' can match ordinary conversation and may cause the skill to activate in contexts where the user did not intend a configuration change. Because the skill performs persistent config modification and restarts a gateway, accidental invocation can disrupt service and alter system behavior.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

This Python file contains comments, status messages, errors, and argument descriptions exclusively in Chinese, including the main CLI description and runtime output. That creates a language/locale policy concern because the skill effectively forces a specific language for users without opt-in or justification.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · model_switch.py (reported line 81)May include surrounding context.

python
"""执行Gateway重启命令"""
        try:
            print("🔄 正在重启OpenClaw Gateway...")
            result = subprocess.run(
                ["openclaw", "gateway", "restart"],
                capture_output=True,
                text=True,

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/model_switch.py (reported line 94)May include surrounding context.

python
"""执行Gateway重启命令"""
        try:
            print("🔄 正在重启OpenClaw Gateway...")
            result = subprocess.run(
                ["openclaw", "gateway", "restart"],
                capture_output=True,
                text=True,

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · model_switch.py (reported line 102)May include surrounding context.

python
def check_gateway_status(self) -> bool:
        """检测Gateway运行状态"""
        try:
            result = subprocess.run(
                ["openclaw", "gateway", "status"],
                capture_output=True,
                text=True,

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/model_switch.py (reported line 111)May include surrounding context.

python
def check_gateway_status(self) -> bool:
        """检测Gateway运行状态"""
        try:
            result = subprocess.run(
                ["openclaw", "gateway", "status"],
                capture_output=True,
                text=True,

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The comment in run_switch states '仅修改default_model字段' ('only modify the default_model field'), which contradicts the actual write target used throughout the implementation. The code modifies agents.defaults.model.primary, so the inline documentation misstates what field is being changed.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

The skill metadata promises validation that the requested model exists in the configured provider list, but the implementation writes any supplied string directly into agents.defaults.model.primary. In a privileged config-management skill, that mismatch can let a user set an invalid or unintended model identifier, causing service disruption, failed restarts, or forcing rollback behavior instead of preventing unsafe changes up front.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Low
Category
Not specified by scanner
Confidence
94% confidence
Finding

The inline documentation for --dry-run says 'backup and show diff, no actual changes', but the code does not compute or display a diff. It also calls backup_original_config, which performs an on-disk copy into the backup directory, so the run is not free of side effects despite the messaging.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.