Back to skill

Security audit

Model Switchboard

Security checks for vulnerabilities and agentic risk

Overview

The skill is a coherent OpenClaw model-management tool, but its local dashboard exposes unauthenticated credential and configuration-changing APIs that any browser origin can call while the server is running.

Review before installing. The CLI workflow appears purpose-aligned, but avoid running the dashboard server unless it has authentication and trusted-origin checks added. Remove legacy backup UI/server files from installed artifacts, restrict .env writes to known provider keys, and rotate provider or Telegram tokens if you ran this server while browsing untrusted sites.

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

Error
Location
ui/server.py:846
Finding

Unauthenticated cross-origin access to credential and configuration APIs

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
ui/server_v1_backup.py:429
Finding

Legacy backup server retains unauthenticated cross-origin credential mutation

Content
View full analysis
Remediation
View remediation
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (48)

Chaining Abuse

High
Category
Tool Misuse
Confidence
75% confidence
Finding

Tool calls are chained to bypass individual safety checks or escalate capabilities beyond what any single tool call would allow.

Content

Scanner excerpt · AUDIT-OPUS.md (reported line 102)May include surrounding context.

File: switchboard.sh lines ~130-140 Severity: MEDIUM

The backup pruning logic uses ls -t | tail | xargs rm -f. If two switchboard commands run concurrently (e.g., two agents, or rapid CLI calls), both could backup and prune simultaneously, potentially deleting each other's backups or counting wrong.

Fix: Use a lockfile for backup operations:

bash

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 · AUDIT-OPUS.md (reported line 194)May include surrounding context.

md
4. **No direct JSON editing** — All config changes go through `openclaw models set` CLI. The skill never does raw JSON manipulation of model fields (except import, which validates first).

5. **Model format regex** — `validate_model_ref()` uses strict regex that blocks shell metacharacters, path traversal (`../`), and null bytes. A malicious model name like `anthropic/../../etc/passwd` is rejected by the regex.

6. **Provider diversity enforcement** — redundancy.py correctly ensures fallback chains don't stack the same provider.

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
The Canvas UI at `ui/index.html` shows:

Chaining Abuse

High
Category
Tool Misuse
Confidence
89% confidence
Finding

The prune pipeline uses ls | tail | xargs rm without null-delimited handling, which is unsafe for filenames containing whitespace, newlines, or leading dash characters. Although backup filenames created by this script are predictable, the directory is user-writable and a crafted matching file or symlink placed there could cause unintended deletions during cleanup.

Content

Scanner excerpt · scripts/switchboard.sh (reported line 147)May include surrounding context.

sh
local count
        count=$(ls -1 "$BACKUP_DIR"/openclaw-*.json 2>/dev/null | wc -l | tr -d ' ')
        if [ "$count" -gt "$MAX_BACKUPS" ]; then
            ls -t "$BACKUP_DIR"/openclaw-*.json | tail -n +"$((MAX_BACKUPS + 1))" | xargs rm -f 2>/dev/null || true
            log_dim "Pruned old backups (keeping last $MAX_BACKUPS)"
        fi
    ) 9>"$LOCKFILE"

Hidden Instructions

High
Category
Prompt Injection
Confidence
70% confidence
Finding

Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.

Content

Scanner excerpt · ui/index_v1_backup.html (reported line 420)May include surrounding context.

html
</div>
</div>

<!-- Model Picker Modal (for fix actions) -->
<div class="picker-overlay" id="pickerOverlay">
  <div class="picker">
    <h2 id="pickerTitle">Select Model</h2>

Credential Access

High
Category
Privilege Escalation
Confidence
81% confidence
Finding

This UI sends raw API keys to a backend endpoint that persists them to a .env file, creating a concentrated secret store and increasing exposure if the local app, filesystem, backups, logs, or repo handling are weak. In skill context, this is more dangerous because the page is explicitly an admin-like credential manager, so compromise of the surrounding service could directly yield provider secrets for multiple AI services.

Content

Scanner excerpt · ui/index_v1_backup.html (reported line 1041)May include surrounding context.

html
api('POST', '/api/key', { key: envVar, value: input.value.trim() })
    .then(function(res) {
      if (res.ok) {
        showModalStatus('success', '✓ Saved ' + envVar + ' to .env');
        toast('✓ ' + envVar + ' saved to .env', 'success');
        setTimeout(function() {
          closeModal();

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 · ui/index_v1_backup.html (reported line 1042)May include surrounding context.

html
.then(function(res) {
      if (res.ok) {
        showModalStatus('success', '✓ Saved ' + envVar + ' to .env');
        toast('✓ ' + envVar + ' saved to .env', 'success');
        setTimeout(function() {
          closeModal();
          loadStatus();

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 · ui/index_v1_backup.html (reported line 1056)May include surrounding context.

html
.then(function(res) {
      if (res.ok) {
        showModalStatus('success', '✓ Saved ' + envVar + ' to .env');
        toast('✓ ' + envVar + ' saved to .env', 'success');
        setTimeout(function() {
          closeModal();
          loadStatus();

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 · ui/index_v1_backup.html (reported line 1068)May include surrounding context.

html
.then(function(res) {
      if (res.ok) {
        showModalStatus('success', '✓ Saved ' + envVar + ' to .env');
        toast('✓ ' + envVar + ' saved to .env', 'success');
        setTimeout(function() {
          closeModal();
          loadStatus();

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 · ui/server.py (reported line 16)May include surrounding context.

python
from pathlib import Path

PORT = int(os.environ.get("SWITCHBOARD_PORT", "8770"))
ENV_FILE = os.environ.get("SWITCHBOARD_ENV", os.path.expanduser("~/.openclaw/workspace/.env"))
REGISTRY_FILE = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "model-registry.json")
CONFIG_FILE = os.path.expanduser("~/.openclaw/openclaw.json")
BACKUP_DIR = os.path.expanduser("~/.openclaw/backups/switchboard")

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 · ui/server_v1_backup.py (reported line 15)May include surrounding context.

python
from pathlib import Path

PORT = int(os.environ.get("SWITCHBOARD_PORT", "8770"))
ENV_FILE = os.environ.get("SWITCHBOARD_ENV", os.path.expanduser("~/.openclaw/workspace/.env"))
REGISTRY_FILE = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "model-registry.json")
CONFIG_FILE = os.path.expanduser("~/.openclaw/openclaw.json")
BACKUP_DIR = os.path.expanduser("~/.openclaw/backups/switchboard")

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 · ui/index_v1_backup.html (reported line 946)May include surrounding context.

html
if __name__ == "__main__":
    print(f"🔀 Model Switchboard server on http://127.0.0.1:{PORT}")
    print(f"   .env file: {ENV_FILE}")
    print(f"   Registry:  {REGISTRY_FILE}")
    print(f"   Config:    {CONFIG_FILE}")
    server = http.server.HTTPServer(("127.0.0.1", PORT), Handler)

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 · ui/server_v1_backup.py (reported line 524)May include surrounding context.

python
if __name__ == "__main__":
    print(f"🔀 Model Switchboard server on http://127.0.0.1:{PORT}")
    print(f"   .env file: {ENV_FILE}")
    print(f"   Registry:  {REGISTRY_FILE}")
    print(f"   Config:    {CONFIG_FILE}")
    server = http.server.HTTPServer(("127.0.0.1", PORT), Handler)

Unsafe Defaults

Medium
Category
Tool Misuse
Confidence
70% confidence
Finding

Tool defaults are unsafe or overly permissive (e.g. disabled TLS verification, no authentication, world-writable permissions). Unsafe defaults widen the attack surface.

Content

Scanner excerpt · AUDIT-OPUS.md (reported line 171)May include surrounding context.

md
### L-4: Backup permissions — umask not set
**File:** `switchboard.sh` line ~125
`chmod 600 "$backup_file"` is good, but the `mkdir -p "$BACKUP_DIR"` doesn't set directory permissions. On a multi-user system, the backup directory could be world-readable.
**Fix:** Add `chmod 700 "$BACKUP_DIR"` after mkdir.

### L-5: model-registry.json — `openai/dall-e-3` has empty safeRoles

Unsafe Defaults

Medium
Category
Tool Misuse
Confidence
75% confidence
Finding

Tool defaults are unsafe or overly permissive (e.g. disabled TLS verification, no authentication, world-writable permissions). Unsafe defaults widen the attack surface.

Content

Scanner excerpt · scripts/redundancy.py (reported line 41)May include surrounding context.

python
for pid, pinfo in registry.get("providers", {}).items():
        auth_envs = pinfo.get("authEnv", [])
        has_auth = False
        auth_via = None

        # Check environment variables

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The introductory comments present a strong safety guarantee that the tool will never crash the gateway. In practice, functions such as set_primary, set_image, import_config, restore_backup, and redundancy_apply modify the active OpenClaw configuration, and the script merely checks status afterward or advises a restart, so the documentation overstates the absence of disruptive side effects.

Content

No source excerpt is available for this finding.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
80% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · AUDIT-OPUS.md (reported line 172)May include surrounding context.

md
# Ensure backup/log directory exists early
mkdir -p "$BACKUP_DIR" 2>/dev/null
chmod 700 "$BACKUP_DIR" 2>/dev/null

# ── Colors & Output ────────────────────────────────────────────
RED='\033[0;31m'

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
80% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · CHANGELOG.md (reported line 28)May include surrounding context.

md
# Ensure backup/log directory exists early
mkdir -p "$BACKUP_DIR" 2>/dev/null
chmod 700 "$BACKUP_DIR" 2>/dev/null

# ── Colors & Output ────────────────────────────────────────────
RED='\033[0;31m'

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
80% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · scripts/switchboard.sh (reported line 18)May include surrounding context.

sh
# Ensure backup/log directory exists early
mkdir -p "$BACKUP_DIR" 2>/dev/null
chmod 700 "$BACKUP_DIR" 2>/dev/null

# ── Colors & Output ────────────────────────────────────────────
RED='\033[0;31m'

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
80% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · scripts/switchboard.sh (reported line 126)May include surrounding context.

sh
# Ensure backup/log directory exists early
mkdir -p "$BACKUP_DIR" 2>/dev/null
chmod 700 "$BACKUP_DIR" 2>/dev/null

# ── Colors & Output ────────────────────────────────────────────
RED='\033[0;31m'

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
80% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · AUDIT-OPUS.md (reported line 171)May include surrounding context.

md
fi

    cp "$OPENCLAW_CONFIG" "$backup_file"
    chmod 600 "$backup_file"
    log_ok "Config backed up: ${DIM}$backup_file${NC}"

    # Prune old backups (keep MAX_BACKUPS) with lockfile to prevent races

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
80% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · scripts/switchboard.sh (reported line 137)May include surrounding context.

sh
fi

    cp "$OPENCLAW_CONFIG" "$backup_file"
    chmod 600 "$backup_file"
    log_ok "Config backed up: ${DIM}$backup_file${NC}"

    # Prune old backups (keep MAX_BACKUPS) with lockfile to prevent races

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
80% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · scripts/switchboard.sh (reported line 788)May include surrounding context.

sh
fi

    cp "$OPENCLAW_CONFIG" "$backup_file"
    chmod 600 "$backup_file"
    log_ok "Config backed up: ${DIM}$backup_file${NC}"

    # Prune old backups (keep MAX_BACKUPS) with lockfile to prevent races

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
80% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · ui/index_v1_backup.html (reported line 946)May include surrounding context.

html
fi

    cp "$OPENCLAW_CONFIG" "$backup_file"
    chmod 600 "$backup_file"
    log_ok "Config backed up: ${DIM}$backup_file${NC}"

    # Prune old backups (keep MAX_BACKUPS) with lockfile to prevent races

Dynamic import via __import__()

Medium
Category
Dangerous Code Execution
Confidence
75% confidence
Finding

Dynamic import() can load arbitrary modules at runtime, bypassing static analysis and potentially importing malicious code.

Content

Scanner excerpt · scripts/validate.py (reported line 441)May include surrounding context.

python
"backupCount": backup_count,
        "issues": issues,
        "registryModels": list(registry.get("models", {}).keys()),
        "timestamp": __import__("datetime").datetime.now().isoformat()
    }

Static analysis

Detected: suspicious.install_untrusted_source

Install source points to URL shortener or raw IP.

Warn
Code
suspicious.install_untrusted_source
Location
model-registry.json:719