Back to skill

Security audit

Ai Model Router

Security checks for vulnerabilities and agentic risk

Overview

The skill is mostly a disclosed model router, but its privacy mode can select a cloud-configured primary model and it can store message snippets locally without clear safeguards.

Review before installing. Use this only if you are comfortable with automatic routing decisions, verify that primary_model in ~/.model-router/models.json is truly local before entering secrets, and treat context tracking as local plaintext storage of message snippets. Prefer a pinned installer version and avoid running the install command with elevated privileges.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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

Warning
Location
skill/context.py:34
Finding
Conversation Content Is Persisted in Plaintext Without Restrictive File Permissions## Vulnerability Details **File Location**: `skill/context.py:34-36, 65-73` **Vulnerability Type**: Plaintext storage of potentially sensitive conversation data **Risk Level**: Medium ### Vulnerable Code ```python def _save(self): """Save contexts to disk""" with open(self.contexts_file, "w") as f: json.dump(self.contexts, f, indent=2) ``` ```python ctx["last_model"] = model_used ctx["messages"].append({ "role": role, "content": content[:200], # Truncate for storage "model": model_used, "type": model_type, "time": datetime.now().isoformat(), }) self._save() ``` ### Technical Analysis The context manager records the first 200 characters of each conversation message and writes the resulting data to `~/.model-router/contexts.json` as plaintext JSON. The implementation does not redact credentials or personal information before persistence, encrypt the stored content, request consent for retention, or explicitly enforce a restrictive file mode such as `0600`. Truncation does not provide meaningful protection because API keys, passwords, access tokens, email addresses, and other sensitive values commonly occur within the first 200 characters. Opening the file with mode `"w"` also preserves the permissions of an existing file, including permissions that may allow access by other local users. ### Attack Path 1. A caller creates a conversation and submits a message containing a credential, personal information, or proprietary content. 2. The caller invokes `RouterCore.record_message()`, which forwards the message to `ContextManager.add_message()`. 3. `add_message()` stores the first 200 characters of the message without sensitive-data filtering. 4. `_save()` serializes the conversation history to `~/.model-router/contexts.json` in plaintext. 5. A local process or user able to read that file obtains the retained conversation content. ### Impact Assessment Success ...[truncated 443 chars]
Remediation
## Remediation Suggestions - Disable conversation persistence by default and require explicit user consent before storing message content. - Run sensitive-data detection and redaction before adding messages to the persistent context. - Avoid storing raw message text unless it is necessary; prefer non-sensitive summaries or metadata. - Create the context file atomically with owner-only permissions (`0600`) and ensure the containing directory uses restrictive permissions such as `0700`. - Validate and repair permissions when opening an existing contexts file. - Consider authenticated encryption when conversation content must persist. - Define and enforce retention limits, deletion controls, and maximum storage bounds.

T09 · Insecure Skill Coding Practices

Warning
Location
skill/router.py:225
Finding
Privacy Routing Can Select a Cloud Model Instead of a Local Model## Vulnerability Details **File Location**: `skill/router.py:117-120, 225-237` **Vulnerability Type**: Fail-open privacy control caused by trusting the configured primary model **Risk Level**: Medium ### Vulnerable Code ```python if os.path.exists(self.config_path): with open(self.config_path) as f: config = json.load(f) self.primary_id = config.get("primary_model", {}).get("id") self.secondary_id = config.get("secondary_model", {}).get("id") self.models = [self._dict_to_model(m) for m in config.get("models", [])] ``` ```python # Privacy check - always routes to primary (usually local) has_privacy, privacy_detected = self.check_privacy(task) if has_privacy: primary = self.get_model(self.primary_id) if primary: return RouteResult( model_id=primary.id, model_name=primary.name, model_type="primary", reason=f"privacy_detected:{len(privacy_detected)}_patterns", confidence=1.0, privacy_detected=privacy_detected, ) ``` ### Technical Analysis The privacy control assumes that the primary model is local, but this invariant is not enforced. Configuration data can designate any model as `primary_model`, including an entry whose `type` is `"cloud"`. When sensitive content is detected, the router retrieves that configured model and returns it without verifying that it is local. The result is marked as `"primary"` rather than using the model's actual local or cloud type, and the decision receives a confidence value of `1.0`. A downstream model executor that trusts this result may therefore transmit sensitive prompt content to a remote provider while believing privacy protection has been applied. The project itself only returns a routing decision and does not perform the network transmission. Exploitation occurs when the result is consumed by an integration that sends the task to ...[truncated 1121 chars]
Remediation
## Remediation Suggestions - When privacy is detected, select only a model whose trusted metadata explicitly identifies it as local. - Validate model configuration at load time and reject a cloud model where a privacy-safe local model is required. - Fail closed if no verified local model is available. Return an explicit error or refusal rather than silently selecting a cloud or generic primary model. - Report the selected model's actual type instead of labeling every configured primary model as `"primary"`. - Separate routing roles from transport properties by tracking fields such as `route_role` and `execution_location`. - Add tests covering a cloud primary model, missing local models, malformed configuration, and privacy-pattern matches. - Clearly communicate to downstream callers whether remote transmission is permitted.

T08 · Insecure Dependencies

Note
Location
SKILL.md:16
Finding
Installation Instructions Execute an Unpinned Third-Party Package## Vulnerability Details **File Location**: `SKILL.md:16` **Vulnerability Type**: Mutable third-party dependency execution **Risk Level**: Low ### Vulnerable Code ```bash npx clawhub@latest install ai-model-router ``` ### Technical Analysis The documented installation command directs `npx` to download and execute the mutable `latest` release of the `clawhub` npm package. No exact version, lockfile, integrity hash, or other reproducibility control is specified. Consequently, the code executed by users following the instructions may differ from the code available when this project was audited. A compromised npm account, malicious future release, or upstream supply-chain incident could turn the installation command into an arbitrary-code execution path. No evidence was found that the currently reviewed project retrieves or executes a remote payload itself. The risk is specifically introduced by the documented use of an unpinned executable dependency. ### Attack Path 1. An attacker compromises the upstream package, its publishing account, or a future release associated with the `latest` tag. 2. A user follows the Quick Start instructions. 3. `npx` resolves `clawhub@latest` to the attacker-controlled or compromised release. 4. npm downloads the package and executes its command-line entry point. 5. The malicious package executes with the filesystem, network, and account permissions of the user running the installation command. ### Impact Assessment Impact depends on the privileges of the user executing `npx`. Under a normal user account, a compromised dependency could read or modify user-accessible files, steal credentials, access the network, or establish user-level persistence. If the command is run with elevated privileges, the impact could extend to system-wide compromise. This audit does not establish that the current upstream package is malicious.
Remediation
## Remediation Suggestions - Replace the mutable `@latest` tag with an exact, reviewed package version. - Where supported, verify package integrity using a lockfile, checksum, signature, or trusted provenance information. - Review the selected package version and its transitive dependencies before recommending execution. - Advise users not to run the installer with elevated privileges. - Update the pinned version only after reviewing and testing the new release. - Prefer a reproducible installation mechanism that does not implicitly execute newly resolved package code.
Vulnerability Patterns
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
Findings (7)

Vague Triggers

Medium
Confidence
92% confidence
Finding
The skill description defines broad trigger conditions such as requests to switch models, configuration requests, sensitive-data mentions, and complex-task keywords. Overbroad activation can cause the skill to run in contexts the user did not explicitly intend, which may alter model-selection behavior or intercept sensitive workflows unexpectedly.

Rp1

Medium
Category
MCP Rug Pull
Confidence
94% confidence
Finding
The install command uses `npx clawhub@latest`, which pulls and executes the newest published package without pinning a specific version. This creates a supply-chain risk: if the upstream package is compromised or a breaking/malicious release is published, users of the skill may execute unintended code during installation.

Session Persistence

Medium
Category
Rogue Agent
Content
## Configuration (Optional)

Create `~/.model-router/models.json`:

```json
{
Confidence
60% 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.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
This code persists conversation context, including truncated message content and model metadata, to a local JSON file without any visible consent, disclosure, retention control, or access protection. Even though the storage is local and messages are truncated to 200 characters, sensitive prompts, secrets, or personal data may still be written to disk and later exposed to other local users, backups, logs, or compromise of the host.

Natural-Language Policy Violations

Low
Confidence
79% confidence
Finding
The file states that mentions of API keys/passwords "trigger privacy mode" and that sensitive data detection "forces local" automatically. While this is a privacy-oriented behavior, it removes user choice in handling requests and imposes a fixed routing policy without documenting opt-in or override conditions in the description.

Intent-Code Divergence

Low
Confidence
88% confidence
Finding
The class docstring states the detector 'Only reads from' Ollama config files and environment variables, implying its behavior is limited to those read-only sources. In reality, no environment-variable access occurs anywhere in the file, and the class also exposes a hardcoded cloud registry via get_cloud_registry, so the documentation does not accurately describe what the code does.

Intent-Code Divergence

Low
Confidence
84% confidence
Finding
The phrase 'Detect all available models' suggests the function discovers actual availability. However, the method simply returns detected local models plus a predefined list of cloud models without checking whether those cloud models are actually available to the user or configured in the environment.

Static analysis

No suspicious patterns detected.