Back to skill

Security audit

Multi-Model Router

Security checks for vulnerabilities and agentic risk

Overview

This router is not clearly malicious, but it can automatically route supposedly private context to cloud models despite claiming sensitive data stays local.

Review before installing. This skill may be useful for model routing, but do not rely on its stated privacy guarantee for secrets, credentials, private documents, or sensitive workspace context unless the fallback behavior is changed to fail closed or require explicit consent for cloud routing.

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 (3)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/analyzer.js:25
Finding
Sensitive conversation context is excluded from privacy analysis<![CDATA[ ## Vulnerability Details **File Location**: `scripts/analyzer.js:25-27` and `scripts/analyzer.js:57-82` **Vulnerability Type**: Sensitive-data routing bypass **Risk Level**: High ### Vulnerable Code ```js analyzeTask(prompt, context = "", requirements = {}) { const contextLength = this.estimateContextLength(prompt, context); const privacyLevel = this.detectPrivacyLevel(prompt); const taskType = this.classifyTaskType(prompt); let reason = ""; if (privacyLevel === 'high') { reason = "privacy sensitive content detected"; } else if (contextLength > 32768) { reason = "large context requirement"; } else { reason = "general purpose routing"; } return { contextLength, privacyLevel, taskType, costSensitivity: requirements.costSensitive || false, performanceCritical: requirements.performanceCritical || false, reason }; } ``` ```js detectPrivacyLevel(prompt) { // Enhanced privacy detection with multiple checks const lowerPrompt = prompt.toLowerCase(); // Check for sensitive patterns const hasSensitivePattern = this.sensitivePatterns.some(pattern => pattern.test(prompt) ); // Check for common sensitive contexts const sensitiveContexts = [ 'my password is', 'api key', 'secret key', 'access token', 'authentication', 'login credentials' ]; const hasSensitiveContext = sensitiveContexts.some(ctx => lowerPrompt.includes(ctx) ); return (hasSensitivePattern || hasSensitiveContext) ? 'high' : 'low'; } ``` ### Technical Analysis The analyzer includes both the prompt and conversation context when estimating the total token count, but it passes only `prompt` to `detectPrivacyLevel`. Consequently, passwords, API keys, access tokens, personally identifiable information, private conversation history, and other confidential information contained exclusively in `context` do not affect the privacy classification. A harmless prompt combined with ...[truncated 1440 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Analyze every data field that may be sent to the selected model: ```js const privacyLevel = this.detectPrivacyLevel(`${prompt}\n${context}`); ``` 2. Prefer an API that accepts separate fields and evaluates both, rather than relying on string concatenation. 3. Validate that `prompt` and `context` are strings before analysis. 4. Expand detection beyond keywords to cover structured credentials, private keys, financial identifiers, email addresses, telephone numbers, and configurable organization-specific patterns. 5. Treat uncertain or failed privacy analysis as sensitive and route locally by default. 6. Add tests where the prompt is harmless but the context contains credentials or PII. 7. Apply output-side enforcement before returning a cloud model: reject any cloud decision when the combined request has a high or unknown privacy classification. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/router-engine.js:42
Finding
Long privacy-sensitive input fails open to the configured cloud fallback<![CDATA[ ## Vulnerability Details **File Location**: `scripts/router-engine.js:42-58` and `scripts/router-engine.js:110-114`; `config/default.json:21-26` and `config/default.json:37` **Vulnerability Type**: Privacy constraint bypass through unsafe fallback **Risk Level**: High ### Vulnerable Code ```js selectModel(analysis, userPreferences = {}) { let candidates = Object.keys(this.config.models); // Apply privacy rules first (highest priority) if (analysis.privacyLevel === 'high') { candidates = this.applyPrivacyFilter(candidates); } // Apply context length rules if (analysis.contextLength > 32768) { candidates = this.applyContextFilter(candidates, analysis.contextLength); } // Apply cost optimization if requested if (analysis.costSensitivity || userPreferences.costSensitive) { candidates = this.applyCostOptimization(candidates); } // Apply performance priority if requested if (analysis.performanceCritical || userPreferences.performanceCritical) { candidates = this.applyPerformancePriority(candidates); } // Return the best candidate based on priority return this.selectBestCandidate(candidates); } ``` ```js applyPrivacyFilter(candidates) { return candidates.filter(model => this.config.models[model].privacy_level === 'local' ); } applyContextFilter(candidates, requiredContextLength) { return candidates.filter(model => this.config.models[model].context_window >= requiredContextLength ); } ``` ```js selectBestCandidate(candidates) { if (candidates.length === 0) { console.warn("No suitable models found, using fallback"); return this.config.fallback_strategy; } return candidates[0]; // Return highest priority candidate } ``` The only configured local model has a 32K context window: ```json "offline": { "alias": "ollama/qwen35-32k", "context_window": 32768, "privacy_level": "local", "cost_per_1k_input": 0, "cost_per_1k_output": 0, "priority": 3 } ``` Th ...[truncated 1746 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Model privacy as a mandatory constraint rather than a sortable preference. 2. Pass the analysis into fallback selection and prohibit cloud fallbacks when privacy is `high` or `unknown`. 3. Fail closed when no eligible local model exists: ```js if (candidates.length === 0 && analysis.privacyLevel !== 'low') { throw new PrivacyCapacityError( 'No local model can safely process this request' ); } ``` 4. Offer safe alternatives such as local truncation, local chunking, or selection of a larger local model. 5. Require explicit, informed user consent before relaxing a privacy constraint; do not infer consent from performance or context requirements. 6. Configure a separate local fallback for sensitive requests instead of using one global fallback. 7. Add an invariant immediately before returning a model: ```js if (analysis.privacyLevel === 'high' && selectedModel.privacy_level !== 'local') { throw new PrivacyPolicyViolation(); } ``` 8. Add tests for sensitive inputs immediately below, equal to, and above the local model’s context limit. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/router.js:37
Finding
Routing exceptions return unredacted context with a cloud model<![CDATA[ ## Vulnerability Details **File Location**: `scripts/router.js:37-57` and `scripts/error-handler.js:26-34` **Vulnerability Type**: Fail-open error handling causing sensitive-data exposure **Risk Level**: High ### Vulnerable Code ```js } catch (error) { console.error("Routing error:", error); // Handle error with retry logic const fallbackResponse = this.errorHandler.handleRoutingError(error, context); if (fallbackResponse) { return fallbackResponse; } // If no fallback response, return basic fallback const fallbackModel = this.config.models[this.config.fallback_strategy]; return { model: fallbackModel.alias, context: context, reason: "Fallback due to routing error", analysis: null, error: true }; } ``` ```js createFallbackResponse(context) { return { model: "xinliu/qwen3-max", // Fallback to primary model context: context, reason: "Fallback due to persistent routing errors", error: true }; } ``` ### Technical Analysis Both error-handling paths return the original, unredacted context alongside a cloud model. The immediate fallback uses the configured `fallback_strategy`, which is `high_context`; the persistent-error fallback hardcodes `xinliu/qwen3-max`. Neither path preserves a previous privacy determination, reruns privacy analysis safely, validates the fallback model’s privacy level, or requests user consent. Because analysis and selection are inside the same `try` block, malformed input, unexpected value types, tokenization failures that escape their local handler, invalid configuration, or other routing exceptions can bypass normal privacy filtering. For example, a non-string prompt can cause `prompt.toLowerCase()` to throw while sensitive data remains in the context. Although `ErrorHandler` describes retry behavior, `route` does not actually retry the routing operation. A null result from `handleRoutingError` immediately reaches the basic cloud fallback. ### Attack Path ...[truncated 903 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Fail closed on routing exceptions and return an error without selecting a cloud model. 2. Never hardcode a cloud model as the universal error fallback. 3. Validate request types before analysis: ```js if (typeof prompt !== 'string' || typeof context !== 'string') { throw new TypeError('prompt and context must be strings'); } ``` 4. Establish privacy classification before operations likely to fail, and preserve it in a trusted routing state. 5. For unknown or failed classifications, permit only a verified local fallback. 6. Do not return raw context in an error response unless it is strictly necessary; the caller already possesses the original context. 7. Implement actual bounded retry behavior only for transient internal errors, without weakening privacy constraints. 8. Add tests that induce failures with sensitive context and assert that no cloud model is returned. 9. Validate configuration at startup, including fallback existence, context capacities, aliases, and privacy levels. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • System Prompt LeakageDirect Leakage, Indirect Extraction, Tool-Based Exfiltration
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (12)

Direct Prompt Extraction

High
Category
System Prompt Leakage
Content
// Use precise token counting for better accuracy
      const promptTokens = countTokens(prompt);
      const contextTokens = countTokens(context);
      return promptTokens + contextTokens;
    } catch (error) {
      // Fallback to character-based estimation
      console.warn("Token counting failed, using fallback method");
Confidence
85% confidence
Finding
Skill contains instructions that could directly expose system prompts, internal rules, or hidden instructions to users or external parties.

Credential Access

High
Category
Privilege Escalation
Content
'my password is',
      'api key',
      'secret key', 
      'access token',
      'authentication',
      'login credentials'
    ];
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Natural-Language Policy Violations

Medium
Confidence
89% confidence
Finding
Documentation written entirely in Chinese without language choice can prevent some users from understanding routing behavior, privacy limitations, and consent-relevant details. In a skill that automatically decides whether data stays local or goes to the cloud, lack of accessible disclosure increases the risk of uninformed use and accidental exposure of sensitive data.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The skill claims privacy protections and automatic routing, but it does not clearly warn users that some tasks may still be sent to cloud-hosted models when the system deems them non-sensitive. This can create a misleading privacy expectation, causing users to provide data they would not have shared if cloud routing had been explicitly disclosed.

Natural-Language Policy Violations

Medium
Confidence
84% confidence
Finding
The manifest uses Chinese-language description text and Chinese trigger phrases alongside English ones, but it does not state that the skill is Chinese-specific or offer any user language choice. This creates a natural-language policy concern because the locale behavior appears implicit rather than explicitly documented or opt-in.

Vague Triggers

Medium
Confidence
95% confidence
Finding
The activation contexts are broad and subjective, such as triggering when a user mentions multiple AI models or when privacy-sensitive content is detected. In a skill with access to memory and workspace files, overbroad activation increases the chance the router engages unexpectedly and influences model selection or data handling without clear user intent.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
The documentation presents privacy-sensitive routing as automatic protection without clearly warning that only some content will stay local and that other prompts may still be sent to cloud models. This can mislead developers into assuming broader confidentiality guarantees than actually exist, causing accidental disclosure of sensitive data.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
Automatic fallback to a cloud model can transmit prompts or context to a remote service when the primary route fails, but the documentation does not clearly disclose this behavior. In a privacy-focused router, undisclosed fallback materially increases the risk of sensitive data exfiltration because failures may trigger remote processing unexpectedly.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
The skill persists user preferences to a local JSON file without any consent flow, disclosure, access control, or data-minimization safeguards. While the current fields look low sensitivity, preference data can still reveal privacy posture, usage patterns, or operational choices, and in a multi-user or shared-host environment the file may be readable by other local users or included in backups/logging workflows.

Natural-Language Policy Violations

Medium
Confidence
97% confidence
Finding
The task-type classifier relies entirely on Chinese keywords such as 总结, 分析, 代码, and 日常 to determine behavior. This creates an implicit language restriction in the skill logic without offering a user language choice or documenting that the skill is intentionally limited to Chinese-language inputs.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
This code persistently writes routing metadata, including model choice, reason, context length, privacy level, and task type, to a local audit log without any evidence of user notice, consent, minimization, or retention controls. Even if the fields are not raw user content, they can still reveal sensitive behavioral and privacy-related information, and the danger is increased because the component explicitly records a 'privacyLevel' classification while storing it silently.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
When no candidate models remain after applying privacy and context filters, the router silently falls back to `high_context`, which is configured as a cloud model. For privacy-sensitive requests, this can route data off-device despite the earlier privacy check, creating an implicit privacy-boundary bypass with only a console warning and no user-facing consent or hard failure.

Static analysis

No suspicious patterns detected.