Back to skill

Security audit

Model Router

Security checks for vulnerabilities and agentic risk

Overview

This skill is a small local model-routing helper with disclosed file input and no evidence of hidden access, persistence, exfiltration, or destructive behavior.

Install only if you want a local helper for selecting among model configurations. Treat its output as a routing hint, especially for legal, medical, security, or other high-stakes tasks, and provide a trusted models JSON rather than arbitrary paths or unvalidated caller input.

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

Note
Location
scripts/router.py:48
Finding

Unhandled Empty Candidate Set Causes Denial of Service

Content
View full analysis

Vulnerability Details

File Location: scripts/router.py, lines 48–49 and 77–79
Vulnerability Type: Unchecked empty list access
Risk Level: Low

Vulnerable Code

python
# Apply min_capability filter if provided
if min_capability:
    candidates = [m for m in candidates if min_capability in m.get("capabilities", [])]

# ...

# Fallback: return cheapest overall
m = candidates[0]
reason = f"fallback cheapest (score={task_score})"
return {"model": m, "reason": reason}

Technical Analysis

The router filters its candidate list when min_capability is supplied but does not verify that any candidates remain. The fallback branch unconditionally accesses candidates[0]. If no configured model provides the requested capability, Python raises an IndexError, terminating the routing operation.

The same failure occurs when the models JSON contains an empty list. Because pick_model() is exposed as both CLI and library functionality, the exception can propagate into middleware or batch-processing systems that do not isolate failures.

Attack Path

  1. An attacker or untrusted caller controls the requested minimum capability, or an untrusted or malformed configuration supplies an empty model list.

  2. The caller requests an unavailable capability, for example:

    bash
    python3 scripts/router.py \
      --models examples/models.json \
      --task "Route this request" \
      --min-capability nonexistent
    
  3. The capability filter removes every candidate.

  4. Neither the low-complexity nor high-complexity selection branch returns a model.

  5. The fallback executes candidates[0].

  6. An IndexError terminates the process and prevents the request from being routed.

Impact Assessment

Successful exploitation does not grant additional privileges, execute arbitrary code, or expose confidential data. Its impact is limited to availability: an attacker who can influence routing parameters or model configuration can reliably fail indiv ...[truncated 193 chars]

Remediation
View remediation

Remediation Suggestions

  • Validate that the decoded JSON root is a non-empty list before routing.
  • Validate every model entry, including the types of name, provider, cost_score, power_score, and capabilities.
  • Check whether candidates is empty immediately after capability filtering.
  • Raise a specific, controlled exception or return a structured “no matching model” result rather than indexing an empty list.
  • Catch validation and selection errors in the CLI entry point, emit a concise diagnostic to standard error, and return a nonzero exit status.
  • Ensure middleware calling pick_model() handles the controlled failure without terminating a worker.

Example hardening:

python
def load_models(path: str) -> List[Dict]:
    with open(path, "r", encoding="utf-8") as f:
        models = json.load(f)

    if not isinstance(models, list) or not models:
        raise ValueError("Models configuration must be a non-empty list")

    return models


def pick_model(models: List[Dict], task: str,
               min_capability: str = None,
               prefer: List[str] = None) -> Dict:
    if not models:
        raise ValueError("No models are configured")

    task_score = score_task(task)
    candidates = sorted(
        models,
        key=lambda m: (
            m.get("cost_score", 1000),
            -m.get("power_score", 0),
        ),
    )

    if min_capability:
        candidates = [
            m for m in candidates
            if min_capability in m.get("capabilities", [])
        ]

    if not candidates:
        raise ValueError(
            f"No model satisfies capability: {min_capability}"
        )

    # Continue with model selection.
Vulnerability Patterns
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • 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
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
Findings (3)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
88% confidence
Finding

The skill describes code that reads a models configuration file, but the manifest does not declare any tool scope or allowed-tools boundaries. This creates an authorization and transparency gap: an agent may invoke file-read behavior without explicit permission constraints, making it easier to access unintended local files if inputs such as the models path are user-controlled.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The docstring says higher-complexity tasks should prefer capable models, but in the high-complexity branch the code returns the first name/provider match from --prefer without validating that the preferred model is actually powerful or has suitable capabilities. In a routing skill, this can be exploited or misused to force sensitive, code, legal, medical, or security-related tasks onto an underpowered or inappropriate model, weakening quality and potentially causing unsafe downstream decisions.

Content

No source excerpt is available for this finding.

Vague Triggers

Low
Category
Not specified by scanner
Confidence
90% confidence
Finding

The 'When to use' section says to trigger the skill whenever you need to programmatically choose which LLM to call for a user request, which is broad and lacks constraints or negative examples. In a markdown skill description, this can cause the skill to match a wide range of ordinary routing situations without clearly defining when it should not be invoked.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.