Back to skill

Security audit

Clash Verge Auto Switch

Security checks for vulnerabilities and agentic risk

Overview

The skill is mostly coherent for proxy testing, but it can install a persistent macOS background job and handles controller secrets in ways users should review carefully.

Install only if you are comfortable with a macOS LaunchAgent that keeps running at your chosen interval and can keep changing Clash proxy selections until uninstalled. Prefer dry-run or list mode first, avoid passing controller secrets on the command line, use a Unix socket or loopback HTTPS controller where possible, and verify the installed plist and script path before enabling scheduled switching.

Vulnerability Patterns
  • System PersistenceInstalls backdoors, hooks, services, or scheduled tasks that survive the run
  • 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 (2)

T06 · System Persistence

Error
Location
scripts/install_launch_agent.sh:14
Finding

Persistent LaunchAgent Executes the Proxy-Switching Script Across User Sessions

Content
View full analysis

Vulnerability Details

File Location: scripts/install_launch_agent.sh, lines 14-18 and 52-76
Vulnerability Type: T06: System Persistence
Risk Level: High

Vulnerable Code:

sh
LABEL="com.codex.clash-verge-auto-switch"
PLIST_PATH="$HOME/Library/LaunchAgents/${LABEL}.plist"
LOG_DIR="$HOME/Library/Logs"
OUT_LOG="$LOG_DIR/clash-verge-auto-switch.log"
ERR_LOG="$LOG_DIR/clash-verge-auto-switch.err.log"
sh
"$PYTHON_BIN" - "$PLIST_PATH" "$OUT_LOG" "$ERR_LOG" "$INTERVAL_SECONDS" "$PYTHON_BIN" "$SCRIPT_DIR/switch_fastest.py" "${SCRIPT_ARGS[@]}" <<'PY'
import plistlib
import sys

plist_path, out_log, err_log, interval_seconds, *program_arguments = sys.argv[1:]
payload = {
    "Label": "com.codex.clash-verge-auto-switch",
    "ProgramArguments": program_arguments,
    "RunAtLoad": True,
    "StartInterval": int(interval_seconds),
    "StandardOutPath": out_log,
    "StandardErrorPath": err_log,
    "WorkingDirectory": "/tmp",
}

with open(plist_path, "wb") as handle:
    plistlib.dump(payload, handle)
PY

launchctl bootout "gui/$(id -u)" "$PLIST_PATH" >/dev/null 2>&1 || true
launchctl bootstrap "gui/$(id -u)" "$PLIST_PATH"
launchctl enable "gui/$(id -u)/$LABEL" >/dev/null 2>&1 || true
launchctl kickstart -k "gui/$(id -u)/$LABEL" >/dev/null 2>&1 || true

Technical Analysis

The installer creates a macOS LaunchAgent under ~/Library/LaunchAgents, registers it with launchctl, enables it, and immediately starts it. The generated configuration uses both RunAtLoad and StartInterval, causing switch_fastest.py to execute after the agent is loaded and repeatedly at the user-selected interval.

This is an explicit cross-session persistence mechanism. Although the scheduling feature is documented and an uninstaller is supplied, the installed job remains active after the original skill invocation and continues modifying proxy sele ...[truncated 1658 chars]

Remediation
View remediation

Remediation Suggestions

  1. Prefer a scheduler that the user explicitly configures and manages outside the skill package.
  2. Before installation, display the complete plist, executable path, arguments, interval, and persistence implications, and require explicit confirmation.
  3. Copy the scheduled script into a dedicated, user-owned directory with restrictive permissions instead of executing a mutable file from the skill directory.
  4. Record and verify a cryptographic hash or signature before each scheduled execution to detect replacement or modification.
  5. Validate that the script and its parent directories are owned by the expected user and are not writable by other accounts.
  6. Create the plist with restrictive permissions and reject existing plist files that are symlinks or have unexpected ownership.
  7. Provide status and removal commands alongside installation instructions, and clearly state that the job remains active until uninstalled.
  8. Consider omitting RunAtLoad and immediate kickstart unless the user separately requests those behaviors.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/switch_fastest.py:83
Finding

Controller Credentials May Be Exposed Through Process Arguments and Plaintext HTTP

Content
View full analysis

Vulnerability Details

File Location: scripts/switch_fastest.py, lines 83-86, 111-112, and 124-128
Vulnerability Type: T09: Insecure Skill Coding Practices
Risk Level: Medium

Vulnerable Code:

python
if config_url:
    if not config_url.startswith("http://") and not config_url.startswith("https://"):
        config_url = f"http://{config_url}"
    candidates.append({"kind": "http", "address": config_url, "secret": config_secret})
python
def run_curl(command: list[str]) -> subprocess.CompletedProcess[str]:
    return subprocess.run(command, capture_output=True, text=True, check=False)
python
if controller.get("secret"):
    command.extend(["-H", f"Authorization: Bearer {controller['secret']}"])
if body is not None:
    command.extend(["-H", "Content-Type: application/json", "--data", json.dumps(body)])

Technical Analysis

The controller secret is interpolated into a curl header argument and supplied through the child process argument vector. While curl is running, process-inspection facilities may expose the complete command line, including the bearer token, to other processes with sufficient local visibility.

In addition, controller addresses read from configuration default to http:// when no scheme is present. No check restricts plaintext HTTP to loopback addresses. If a non-loopback controller is configured without an explicit scheme, controller requests—including the bearer authorization header—can traverse the network without transport encryption.

Possession of the bearer token can allow an observer to authenticate to the Mihomo controller with the same authority as the script. The audited workflow demonstrates that this authority includes reading controller and proxy state, invoking delay tests, and changing selector-group choices.

Attack Path

Local process-argument exposure:

  1. The script obtains a controller secret from a command- ...[truncated 1580 chars]
Remediation
View remediation

Remediation Suggestions

  1. Replace the curl subprocess with an in-process HTTP client so authorization headers are not placed in a child process argument vector.
  2. For Unix-socket communication, use a library that supports Unix-domain HTTP connections without exposing secrets in process metadata.
  3. Reject plaintext HTTP for non-loopback controller addresses by default.
  4. Require HTTPS for remote controllers and validate certificates using the operating system’s trusted certificate store.
  5. If remote plaintext HTTP must be supported for compatibility, require an explicit option such as --allow-insecure-http and display a prominent warning.
  6. Treat IPv4 loopback, IPv6 loopback, and Unix sockets separately from remote addresses when enforcing transport policy.
  7. Avoid accepting secrets directly through command-line arguments because the parent Python process command line can also expose --secret values. Prefer protected configuration files, secure prompts, or operating-system credential storage.
  8. Limit controller exposure at the network layer and bind it to a Unix socket or loopback interface whenever remote access is unnecessary.
  9. Rotate the controller secret if process or network exposure is suspected, and use a high-entropy credential.
Vulnerability Patterns
  • Rogue AgentSelf-Modification, Session Persistence
  • 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
Findings (22)

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding

The skill description emphasizes proxy testing and switching, but the documented behavior also includes removing a macOS launchd job, which is a persistent system-configuration action. Behavior that modifies or removes scheduled jobs without being clearly and separately declared can mislead users about the operational scope and trust boundary of the skill.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding

The skill invokes Python and shell scripts that can read environment data, access files, make network requests to the Clash controller, and execute shell commands, yet it declares no tool scope or permission boundaries. That omission makes the effective capability set opaque to the operator and increases the chance of unintended command execution or access beyond what a user expects from the skill description.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
84% confidence
Finding

The workflow auto-discovers live selector groups and switches them to a measured fastest node, which changes the user's active network-routing state for the current Clash session. In this context the persistence is application/session state rather than covert host persistence, but it still alters connectivity behavior and could disrupt expected routing if done automatically or on a schedule.

Content

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

md
1. Check whether the Mihomo controller is reachable.
2. If the user did not name target groups, inspect the live `/proxies` tree and auto-discover groups from the current active selection chain.
3. Expand `url-test`, `fallback`, and `load-balance` groups into leaf proxies, but do not rewrite nested selector groups unless the user explicitly targets them.
4. Test candidate proxies with the controller delay API and switch the selector group to the lowest-latency healthy node.
5. Report the winning node, measured latency, and whether a switch happened.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The scheduling section provides direct install and remove commands for launchd agents but does not clearly warn that these commands create or delete persistent background execution on macOS. Persistent job changes can surprise users, survive across sessions, and be abused to maintain repeated execution of network-affecting commands.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
90% confidence
Finding

This invocation starts creation of a LaunchAgent plist that will later be loaded for recurring execution. In context, it is part of installing persistence for a helper script, which is legitimate for the skill's stated purpose but still a persistence mechanism that could be abused if the target script or passed arguments are unsafe.

Content

Scanner excerpt · scripts/install_launch_agent.sh (reported line 56)May include surrounding context.

sh
mkdir -p "$HOME/Library/LaunchAgents" "$LOG_DIR"

"$PYTHON_BIN" - "$PLIST_PATH" "$OUT_LOG" "$ERR_LOG" "$INTERVAL_SECONDS" "$PYTHON_BIN" "$SCRIPT_DIR/switch_fastest.py" "${SCRIPT_ARGS[@]}" <<'PY'
import plistlib
import sys

Session Persistence

Medium
Category
Rogue Agent
Confidence
91% confidence
Finding

Writing the plist to ~/Library/LaunchAgents is a concrete persistence step because it creates the artifact later loaded by launchctl. In this skill context it appears intended and user-facing, but it still establishes recurring execution and so should be treated as a real persistence capability.

Content

Scanner excerpt · scripts/install_launch_agent.sh (reported line 71)May include surrounding context.

sh
"WorkingDirectory": "/tmp",
}

with open(plist_path, "wb") as handle:
    plistlib.dump(payload, handle)
PY

Session Persistence

Medium
Category
Rogue Agent
Confidence
97% confidence
Finding

Bootstrapping the plist with launchctl actively loads the LaunchAgent into the user's GUI domain, making the persistence operational. This creates automatic repeated execution, which is expected for the skill but materially increases risk if the called Python script can be altered or if dangerous arguments are embedded.

Content

Scanner excerpt · scripts/install_launch_agent.sh (reported line 75)May include surrounding context.

sh
plistlib.dump(payload, handle)
PY

launchctl bootout "gui/$(id -u)" "$PLIST_PATH" >/dev/null 2>&1 || true
launchctl bootstrap "gui/$(id -u)" "$PLIST_PATH"
launchctl enable "gui/$(id -u)/$LABEL" >/dev/null 2>&1 || true
launchctl kickstart -k "gui/$(id -u)/$LABEL" >/dev/null 2>&1 || true

Session Persistence

Medium
Category
Rogue Agent
Confidence
96% confidence
Finding

Enabling and kickstarting the LaunchAgent ensures the persisted job is active immediately and on future sessions. This is a true persistence mechanism; while aligned with the feature description, it creates durable automated execution that could be repurposed if downstream files are compromised.

Content

Scanner excerpt · scripts/install_launch_agent.sh (reported line 76)May include surrounding context.

sh
PY

launchctl bootout "gui/$(id -u)" "$PLIST_PATH" >/dev/null 2>&1 || true
launchctl bootstrap "gui/$(id -u)" "$PLIST_PATH"
launchctl enable "gui/$(id -u)/$LABEL" >/dev/null 2>&1 || true
launchctl kickstart -k "gui/$(id -u)/$LABEL" >/dev/null 2>&1 || true

Session Persistence

Medium
Category
Rogue Agent
Confidence
96% confidence
Finding

This script deliberately installs and enables a per-user macOS LaunchAgent that will persistently execute switch_fastest.py at login and on a fixed interval. Persistence is a legitimate feature here, but from a security perspective it creates ongoing code execution from a user-writable location, so if the referenced script or its arguments are later tampered with, the agent will keep running automatically.

Content

Scanner excerpt · scripts/install_launch_agent.sh (reported line 77)May include surrounding context.

sh
launchctl bootout "gui/$(id -u)" "$PLIST_PATH" >/dev/null 2>&1 || true
launchctl bootstrap "gui/$(id -u)" "$PLIST_PATH"
launchctl enable "gui/$(id -u)/$LABEL" >/dev/null 2>&1 || true
launchctl kickstart -k "gui/$(id -u)/$LABEL" >/dev/null 2>&1 || true

echo "Installed $LABEL"

Session Persistence

Medium
Category
Rogue Agent
Confidence
75% confidence
Finding

Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Content

Scanner excerpt · scripts/install_launch_agent.sh (reported line 82)May include surrounding context.

sh
echo "Installed $LABEL"
echo "Interval: ${INTERVAL_MINUTES} minute(s)"
echo "Plist: $PLIST_PATH"
echo "Stdout: $OUT_LOG"
echo "Stderr: $ERR_LOG"

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/switch_fastest.py (reported line 57)May include surrounding context.

python
["open", "-g", "-a", "Clash Verge Rev"],
    ]
    for command in commands:
        subprocess.run(command, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=False)
    if wait_seconds > 0:
        time.sleep(wait_seconds)

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/switch_fastest.py (reported line 110)May include surrounding context.

python
def run_curl(command: list[str]) -> subprocess.CompletedProcess[str]:
    return subprocess.run(command, capture_output=True, text=True, check=False)


def api_request(

Session Persistence

Medium
Category
Rogue Agent
Confidence
75% confidence
Finding

Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Content

Scanner excerpt · scripts/install_launch_agent.sh (reported line 16)May include surrounding context.

sh
set -euo pipefail

LABEL="com.codex.clash-verge-auto-switch"
PLIST_PATH="$HOME/Library/LaunchAgents/${LABEL}.plist"

launchctl bootout "gui/$(id -u)" "$PLIST_PATH" >/dev/null 2>&1 || true
rm -f "$PLIST_PATH"

Session Persistence

Medium
Category
Rogue Agent
Confidence
75% confidence
Finding

Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Content

Scanner excerpt · scripts/install_launch_agent.sh (reported line 82)May include surrounding context.

sh
set -euo pipefail

LABEL="com.codex.clash-verge-auto-switch"
PLIST_PATH="$HOME/Library/LaunchAgents/${LABEL}.plist"

launchctl bootout "gui/$(id -u)" "$PLIST_PATH" >/dev/null 2>&1 || true
rm -f "$PLIST_PATH"

Session Persistence

Medium
Category
Rogue Agent
Confidence
75% confidence
Finding

Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Content

Scanner excerpt · scripts/uninstall_launch_agent.sh (reported line 5)May include surrounding context.

sh
set -euo pipefail

LABEL="com.codex.clash-verge-auto-switch"
PLIST_PATH="$HOME/Library/LaunchAgents/${LABEL}.plist"

launchctl bootout "gui/$(id -u)" "$PLIST_PATH" >/dev/null 2>&1 || true
rm -f "$PLIST_PATH"

Session Persistence

Medium
Category
Rogue Agent
Confidence
75% confidence
Finding

Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Content

Scanner excerpt · scripts/uninstall_launch_agent.sh (reported line 7)May include surrounding context.

sh
set -euo pipefail

LABEL="com.codex.clash-verge-auto-switch"
PLIST_PATH="$HOME/Library/LaunchAgents/${LABEL}.plist"

launchctl bootout "gui/$(id -u)" "$PLIST_PATH" >/dev/null 2>&1 || true
rm -f "$PLIST_PATH"

Session Persistence

Medium
Category
Rogue Agent
Confidence
75% confidence
Finding

Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Content

Scanner excerpt · scripts/uninstall_launch_agent.sh (reported line 8)May include surrounding context.

sh
set -euo pipefail

LABEL="com.codex.clash-verge-auto-switch"
PLIST_PATH="$HOME/Library/LaunchAgents/${LABEL}.plist"

launchctl bootout "gui/$(id -u)" "$PLIST_PATH" >/dev/null 2>&1 || true
rm -f "$PLIST_PATH"

Session Persistence

Medium
Category
Rogue Agent
Confidence
75% confidence
Finding

Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Content

Scanner excerpt · scripts/install_launch_agent.sh (reported line 16)May include surrounding context.

sh
set -euo pipefail

LABEL="com.codex.clash-verge-auto-switch"
PLIST_PATH="$HOME/Library/LaunchAgents/${LABEL}.plist"

launchctl bootout "gui/$(id -u)" "$PLIST_PATH" >/dev/null 2>&1 || true
rm -f "$PLIST_PATH"

Session Persistence

Medium
Category
Rogue Agent
Confidence
75% confidence
Finding

Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Content

Scanner excerpt · scripts/install_launch_agent.sh (reported line 57)May include surrounding context.

sh
set -euo pipefail

LABEL="com.codex.clash-verge-auto-switch"
PLIST_PATH="$HOME/Library/LaunchAgents/${LABEL}.plist"

launchctl bootout "gui/$(id -u)" "$PLIST_PATH" >/dev/null 2>&1 || true
rm -f "$PLIST_PATH"

Session Persistence

Medium
Category
Rogue Agent
Confidence
75% confidence
Finding

Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Content

Scanner excerpt · scripts/install_launch_agent.sh (reported line 60)May include surrounding context.

sh
set -euo pipefail

LABEL="com.codex.clash-verge-auto-switch"
PLIST_PATH="$HOME/Library/LaunchAgents/${LABEL}.plist"

launchctl bootout "gui/$(id -u)" "$PLIST_PATH" >/dev/null 2>&1 || true
rm -f "$PLIST_PATH"

Session Persistence

Medium
Category
Rogue Agent
Confidence
75% confidence
Finding

Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Content

Scanner excerpt · scripts/install_launch_agent.sh (reported line 72)May include surrounding context.

sh
set -euo pipefail

LABEL="com.codex.clash-verge-auto-switch"
PLIST_PATH="$HOME/Library/LaunchAgents/${LABEL}.plist"

launchctl bootout "gui/$(id -u)" "$PLIST_PATH" >/dev/null 2>&1 || true
rm -f "$PLIST_PATH"

Session Persistence

Medium
Category
Rogue Agent
Confidence
75% confidence
Finding

Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Content

Scanner excerpt · scripts/uninstall_launch_agent.sh (reported line 5)May include surrounding context.

sh
set -euo pipefail

LABEL="com.codex.clash-verge-auto-switch"
PLIST_PATH="$HOME/Library/LaunchAgents/${LABEL}.plist"

launchctl bootout "gui/$(id -u)" "$PLIST_PATH" >/dev/null 2>&1 || true
rm -f "$PLIST_PATH"

Static analysis

No suspicious patterns detected.