Back to skill

Security audit

Operrouter Connect

Security checks for vulnerabilities and agentic risk

Overview

The skill is mostly aligned with using OpenRouter free models, but it reads broad credential sources and uses an unsafe shared temporary cache, so users should review it before installing.

Install only if you are comfortable with the agent reading .env files, using an OpenRouter API key, sending prompts to OpenRouter, and writing a local model cache. Prefer exporting only OPENROUTER_API_KEY for the process, avoid storing unrelated secrets in ~/.env, pass prompts through stdin instead of --prompt, and harden or disable the shared /tmp cache before using this around sensitive data.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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
Findings (4)

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
scripts/resolve_key.py:29
Finding
Overbroad Loading of Unrelated Secrets from Environment Files<![CDATA[ ## Vulnerability Details **File Location**: - `scripts/resolve_key.py:29-38` - `scripts/discover_models.py:34-39` - `scripts/proxy_query.py:64-73` - `references/typescript_template.md:57-67` **Vulnerability Type**: Excessive credential access and violation of least privilege **Risk Level**: Medium ### Vulnerable Code ```python # scripts/resolve_key.py:29-38 # 1. Global ~/.env env.update(load_dotenv_simple(Path.home() / ".env")) # 2. Project .env (overrides global) env.update(load_dotenv_simple(Path(".env"))) # 3. Shell environment (highest priority) env.update(os.environ) key = env.get("OPENROUTER_API_KEY", "").strip() ``` Equivalent behavior exists in the other affected implementations: ```python # scripts/proxy_query.py:64-73 def get_env() -> dict: env = {} env.update(load_env_file(Path.home() / ".env")) env.update(load_env_file(Path(".env"))) env.update(os.environ) return env def get_key(env: dict) -> str: key = env.get("OPENROUTER_API_KEY", "").strip() ``` ```typescript // references/typescript_template.md:57-67 function resolveEnv(): Record<string, string> { // 1. Project .env 2. ~/.env 3. process.env (already exported) const projectEnv = loadEnvFile(path.join(process.cwd(), ".env")); const globalEnv = loadEnvFile(path.join(os.homedir(), ".env")); // Later entries win — process.env takes precedence return { ...globalEnv, ...projectEnv, ...process.env as Record<string, string> }; } const ENV = resolveEnv(); function getApiKey(): string { const key = ENV["OPENROUTER_API_KEY"]?.trim(); ``` ### Technical Analysis The Skill requires only a limited set of OpenRouter-related values, principally `OPENROUTER_API_KEY`, `OPENROUTER_PREFERRED_MODELS`, `OPENROUTER_TIER_A`, and `OPENROUTER_TIER_B`. Instead, the implementations parse every entry in the project `.env`, the global `~/.env`, and the complete process environment into Skill-controlled dictionaries. Global `.env` files commonly contain unrelated da ...[truncated 1370 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Retrieve only explicitly approved keys instead of constructing a dictionary containing every environment entry. - Prefer the process environment and a project-specific configuration file. - Do not inspect global `~/.env` by default. If global configuration is required, use a dedicated file such as `~/.config/openrouter-connect/config.env`. - Apply restrictive permissions to dedicated configuration files, such as mode `0600`. - Replace broad parsing with an allowlist: ```python ALLOWED_KEYS = { "OPENROUTER_API_KEY", "OPENROUTER_PREFERRED_MODELS", "OPENROUTER_TIER_A", "OPENROUTER_TIER_B", } def load_allowed_env(path: Path) -> dict[str, str]: values = load_env_file(path) return {key: values[key] for key in ALLOWED_KEYS if key in values} ``` - Avoid retaining the API key longer than necessary and never include it in logs, exceptions, or diagnostic state. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/discover_models.py:17
Finding
Shared Predictable Temporary Cache Enables Cache Poisoning and Symlink File Overwrite<![CDATA[ ## Vulnerability Details **File Location**: - `scripts/discover_models.py:17-18, 42-57` - `scripts/proxy_query.py:30-31, 88-101` - `references/python_template.md:45-46, 85-105` - `references/typescript_template.md:35-36, 84-104` **Vulnerability Type**: Unsafe temporary-file handling **Risk Level**: High ### Vulnerable Code ```python # scripts/discover_models.py:17-18 CACHE_FILE = Path("/tmp/.openrouter_free_models_cache.json") CACHE_TTL = 3600 # seconds ``` ```python # scripts/discover_models.py:42-57 def fetch_free_models(force_refresh: bool = False) -> list: if not force_refresh and CACHE_FILE.exists(): age = time.time() - CACHE_FILE.stat().st_mtime if age < CACHE_TTL: return json.loads(CACHE_FILE.read_text()) print("[openrouter-connect] Fetching model list from OpenRouter...", file=sys.stderr) with urllib.request.urlopen(MODELS_URL, timeout=10) as resp: all_models = json.loads(resp.read())["data"] free = [ m for m in all_models if m.get("pricing", {}).get("prompt") == "0" and m.get("pricing", {}).get("completion") == "0" ] CACHE_FILE.write_text(json.dumps(free)) ``` The authenticated proxy trusts the same cache: ```python # scripts/proxy_query.py:88-101 def fetch_free_models(force: bool = False) -> list: if not force and CACHE_FILE.exists(): if time.time() - CACHE_FILE.stat().st_mtime < CACHE_TTL: return json.loads(CACHE_FILE.read_text()) with urllib.request.urlopen(MODELS_ENDPOINT, timeout=10) as r: data = json.loads(r.read())["data"] free = [m for m in data if m.get("pricing", {}).get("prompt") == "0" and m.get("pricing", {}).get("completion") == "0"] CACHE_FILE.write_text(json.dumps(free)) return free ``` ### Technical Analysis The cache uses a fixed pathname in a commonly shared temporary directory. The code does not verify: - File ownership or permissions. - Whether the path is ...[truncated 2259 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Store the cache in a user-private location such as `$XDG_CACHE_HOME/openrouter-connect/models.json`. - Create the parent directory with mode `0700` and the cache with mode `0600`. - Reject symbolic links and non-regular files. - Verify that the cache is owned by the current user. - Validate every cached object against an expected schema before use. - Perform atomic writes through a securely created temporary file followed by `os.replace`. - Revalidate that a selected model is currently free before sending an authenticated request. - Consider cryptographically binding cached data to the response source if cache integrity is security-critical. Example hardened layout: ```python cache_root = Path( os.environ.get("XDG_CACHE_HOME", Path.home() / ".cache") ) / "openrouter-connect" cache_root.mkdir(mode=0o700, parents=True, exist_ok=True) CACHE_FILE = cache_root / "models.json" ``` For writes, use `tempfile.NamedTemporaryFile` in the same private directory, set restrictive permissions, flush and synchronize the data, and atomically replace the destination. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
SKILL.md:80
Finding
Sensitive User Prompts Are Passed Through Process Command-Line Arguments<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:80-84` **Vulnerability Type**: Sensitive information exposure through process arguments **Risk Level**: Medium ### Vulnerable Code ```bash python3 /home/claude/openrouter-connect/scripts/proxy_query.py \ --model "mistralai/mistral-7b-instruct:free" \ --prompt "Your question here" ``` ### Technical Analysis The documented primary workflow places the complete user prompt in a command-line argument. Process command lines may be exposed through process-listing interfaces, monitoring systems, audit tools, shell history, job supervisors, crash reports, and execution telemetry. Prompts sent to an LLM may contain proprietary code, personal information, credentials, internal business information, or other sensitive material. OpenRouter transmission is part of the declared functionality, but local disclosure through the command line is unnecessary. The script already supports reading the prompt from standard input, so the additional exposure is avoidable. ### Attack Path 1. A user provides a sensitive prompt to the agent. 2. The agent follows the documented command and places the prompt after `--prompt`. 3. The operating system or an execution wrapper records or exposes the process command line. 4. Another local user, monitoring agent, audit collector, or log reader obtains the prompt. 5. The sensitive content is disclosed outside the intended OpenRouter request channel. ### Impact Assessment The issue does not grant additional operating-system privileges. Its impact is confidentiality loss for the full prompt. Depending on prompt contents, this may expose source code, credentials, personal data, legal material, internal instructions, or commercially sensitive information. ]]>
Remediation
<![CDATA[ ## Remediation Suggestions Use standard input as the default transport for prompts: ```bash printf '%s' "$PROMPT" | python3 /home/claude/openrouter-connect/scripts/proxy_query.py \ --model "$MODEL" ``` Additional hardening: - Deprecate or clearly warn against `--prompt` for sensitive data. - Avoid storing prompts in shell variables where shell tracing is enabled. - For automated integrations, use a protected file descriptor or a temporary file created with mode `0600` and delete it immediately after use. - Ensure process supervisors and application logs do not record prompt bodies. - Document that prompts and system messages are transmitted to OpenRouter and may be processed by downstream model providers. ]]>

T08 · Insecure Dependencies

Warning
Location
references/python_template.md:252
Finding
Generated Workflows Install and Execute Unpinned Third-Party Dependencies<![CDATA[ ## Vulnerability Details **File Location**: - `references/python_template.md:17-20, 252-255` - `references/typescript_template.md:7-14, 215-220` **Vulnerability Type**: Unsafe dependency resolution and execution **Risk Level**: Medium ### Vulnerable Code ```python # references/python_template.md:17-20 try: from openai import OpenAI # openai>=1.0 works with OpenRouter except ImportError: raise SystemExit("Run: pip install openai python-dotenv") ``` ```text # references/python_template.md:252-255 ## Dependencies pip install openai python-dotenv ``` ```typescript // references/typescript_template.md:7-14 // Run: npx ts-node openrouter_connect_client.ts // Or: node openrouter_connect_client.js (after tsc) import * as fs from "fs"; import * as path from "path"; import * as os from "os"; import OpenAI from "openai"; // npm install openai dotenv ``` ```bash # references/typescript_template.md:215-220 npm install openai # TypeScript only: npm install -D typescript ts-node @types/node ``` ### Technical Analysis The templates instruct users to resolve and execute mutable latest versions of packages without exact version pins, lockfiles, package hashes, or integrity verification. Package installation and execution occur in a process that can access the user's files, environment, API key, and prompts. The `npx ts-node` workflow also creates a risk of implicit package retrieval if the expected local executable is unavailable. Package lifecycle scripts may execute during npm installation unless explicitly disabled. No malicious package name or confirmed compromised dependency was found. The vulnerability is the lack of controls against future package compromise, unexpected breaking changes, registry substitution, or compromised transitive dependencies. ### Attack Path 1. A user follows the generated dependency-installation instructions. 2. The package manager resolves the latest available direct and transitive dependency versions. 3. A compr ...[truncated 768 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Pin every direct dependency to a reviewed exact version. - Generate and distribute lockfiles for Python and Node.js examples. - Use hash verification for Python packages, such as `pip install --require-hashes -r requirements.txt`. - Use `npm ci` with a committed `package-lock.json` rather than unconstrained `npm install`. - Review and pin transitive dependencies through the lockfile. - Disable unnecessary npm lifecycle scripts where compatible: ```bash npm ci --ignore-scripts ``` - Avoid commands that may implicitly download executables through `npx`. Invoke a verified local binary instead: ```bash ./node_modules/.bin/ts-node openrouter_connect_client.ts ``` - Run generated clients in a restricted environment containing only the required OpenRouter credential and project files. - Establish an update process that reviews dependency changes before revising pins. ]]>
Vulnerability Patterns
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • 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
Findings (39)

Tp4

High
Category
MCP Tool Poisoning
Confidence
92% confidence
Finding
The declared description promises a broad skill for working with OpenRouter free models, including routing prompts through free models, comparing outputs, testing models, and generating code to call OpenRouter. The supplied code does something narrower: it only queries the OpenRouter models endpoint, filters free models, ranks them, caches the list, and optionally outputs model IDs as JSON. While this supports one part of the description—picking free OpenRouter models—it does not implement the broader primary behaviors described. This is a material description/behavior mismatch because the actual capability is model discovery/ranking only, not general free-tier LLM routing or code scaffolding.

Credential Access

High
Category
Privilege Escalation
Content
# ── Config ────────────────────────────────────────────────────────────────────

# Ranked preference list — tried in order, skipping unavailable free models.
# Override at runtime via OPENROUTER_PREFERRED_MODELS=a,b,c in your .env
PREFERRED_MODELS: list[str] = [
    # ── Tier 1: Qwen ──────────────────────────────────────
    "qwen/qwen-2.5-72b-instruct:free",
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
# ── Config ────────────────────────────────────────────────────────────────────

# Ranked preference list — tried in order, skipping unavailable free models.
# Override at runtime via OPENROUTER_PREFERRED_MODELS=a,b,c in your .env
PREFERRED_MODELS: list[str] = [
    # ── Tier 1: Qwen ──────────────────────────────────────
    "qwen/qwen-2.5-72b-instruct:free",
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
# ── Config ────────────────────────────────────────────────────────────────────

# Ranked preference list — tried in order, skipping unavailable free models.
# Override at runtime via OPENROUTER_PREFERRED_MODELS=a,b,c in your .env
PREFERRED_MODELS: list[str] = [
    # ── Tier 1: Qwen ──────────────────────────────────────
    "qwen/qwen-2.5-72b-instruct:free",
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
# ── Config ────────────────────────────────────────────────────────────────────

# Ranked preference list — tried in order, skipping unavailable free models.
# Override at runtime via OPENROUTER_PREFERRED_MODELS=a,b,c in your .env
PREFERRED_MODELS: list[str] = [
    # ── Tier 1: Qwen ──────────────────────────────────────
    "qwen/qwen-2.5-72b-instruct:free",
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
# ── Config ────────────────────────────────────────────────────────────────────

# Ranked preference list — tried in order, skipping unavailable free models.
# Override at runtime via OPENROUTER_PREFERRED_MODELS=a,b,c in your .env
PREFERRED_MODELS: list[str] = [
    # ── Tier 1: Qwen ──────────────────────────────────────
    "qwen/qwen-2.5-72b-instruct:free",
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
# ── Config ────────────────────────────────────────────────────────────────────

# Ranked preference list — tried in order, skipping unavailable free models.
# Override at runtime via OPENROUTER_PREFERRED_MODELS=a,b,c in your .env
PREFERRED_MODELS: list[str] = [
    # ── Tier 1: Qwen ──────────────────────────────────────
    "qwen/qwen-2.5-72b-instruct:free",
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
# ── Config ────────────────────────────────────────────────────────────────────

# Ranked preference list — tried in order, skipping unavailable free models.
# Override at runtime via OPENROUTER_PREFERRED_MODELS=a,b,c in your .env
PREFERRED_MODELS: list[str] = [
    # ── Tier 1: Qwen ──────────────────────────────────────
    "qwen/qwen-2.5-72b-instruct:free",
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
"""
    Look for OPENROUTER_API_KEY in:
      1. ./env  (project root)
      2. ~/.env (global fallback)
      3. Already-exported shell environment
    Raises RuntimeError if not found.
    """
Confidence
74% confidence
Finding
The function explicitly searches both the project .env and the user's global ~/.env for an API key. In a skill/template context, reading a global secrets file expands the trust boundary and may encourage code that accesses unrelated user secrets beyond the project scope, which is riskier than requiring explicit environment injection.

Credential Access

High
Category
Privilege Escalation
Content
3. Already-exported shell environment
    Raises RuntimeError if not found.
    """
    # 1. Project .env
    project_env = Path(".env")
    if project_env.exists():
        load_dotenv(project_env, override=False)
Confidence
86% confidence
Finding
Automatically loading credentials from a local .env file causes the code to ingest secrets from disk without an explicit consent step at runtime. In an agent skill whose purpose is to relay prompts externally, implicit secret loading increases the chance of unsafe reuse of developer credentials and broadens the impact of accidental invocation.

Credential Access

High
Category
Privilege Escalation
Content
if project_env.exists():
        load_dotenv(project_env, override=False)

    # 2. Global ~/.env
    global_env = Path.home() / ".env"
    if global_env.exists():
        load_dotenv(global_env, override=False)
Confidence
90% confidence
Finding
Loading credentials from ~/.env reaches into a user-wide secret store that may contain unrelated tokens and creates a broader-than-necessary secret access pattern. In a skill that forwards prompts to external providers, this increases the danger of unauthorized secret use if the template is adopted unchanged or invoked unexpectedly in shared or agentic environments.

Credential Access

High
Category
Privilege Escalation
Content
}

function resolveEnv(): Record<string, string> {
  // 1. Project .env  2. ~/.env  3. process.env (already exported)
  const projectEnv = loadEnvFile(path.join(process.cwd(), ".env"));
  const globalEnv  = loadEnvFile(path.join(os.homedir(), ".env"));
  // Later entries win — process.env takes precedence
Confidence
95% confidence
Finding
This code explicitly reads a global `~/.env`, which can contain many unrelated secrets beyond the OpenRouter API key. In a reusable template, this broad credential access is dangerous because it normalizes code that consumes secrets from a location users may not expect, increasing accidental exposure and misuse risk.

Credential Access

High
Category
Privilege Escalation
Content
function resolveEnv(): Record<string, string> {
  // 1. Project .env  2. ~/.env  3. process.env (already exported)
  const projectEnv = loadEnvFile(path.join(process.cwd(), ".env"));
  const globalEnv  = loadEnvFile(path.join(os.homedir(), ".env"));
  // Later entries win — process.env takes precedence
  return { ...globalEnv, ...projectEnv, ...process.env as Record<string, string> };
Confidence
95% confidence
Finding
The specific statement `loadEnvFile(path.join(os.homedir(), ".env"))` accesses a home-directory secret store unrelated to the current project. That widens the blast radius if the generated code is run on a developer workstation containing other credentials, and the skill context makes this more concerning because users may execute scaffolds with minimal review.

Credential Access

High
Category
Privilege Escalation
Content
function resolveEnv(): Record<string, string> {
  // 1. Project .env  2. ~/.env  3. process.env (already exported)
  const projectEnv = loadEnvFile(path.join(process.cwd(), ".env"));
  const globalEnv  = loadEnvFile(path.join(os.homedir(), ".env"));
  // Later entries win — process.env takes precedence
  return { ...globalEnv, ...projectEnv, ...process.env as Record<string, string> };
}
Confidence
91% confidence
Finding
Merging `globalEnv`, `projectEnv`, and `process.env` into a single object compounds the broad secret-ingestion issue by making all discovered environment values available to the program. While this template only uses `OPENROUTER_API_KEY` directly, the pattern encourages over-collection of credentials and increases the chance future modifications expose or misuse them.

Credential Access

High
Category
Privilege Escalation
Content
const key = ENV["OPENROUTER_API_KEY"]?.trim();
  if (!key) throw new Error(
    "OPENROUTER_API_KEY not found.\n" +
    "Add it to ./.env or ~/.env:\n\n" +
    "  OPENROUTER_API_KEY=sk-or-...\n\n" +
    "Get a free key at https://openrouter.ai/keys"
  );
Confidence
86% confidence
Finding
The error message instructs users to place the API key in either `./.env` or `~/.env`, reinforcing the unsafe global secret-loading behavior. In a skill template, prescriptive guidance matters because users often follow it verbatim, which can institutionalize insecure credential storage practices.

Credential Access

High
Category
Privilege Escalation
Content
env = {}

    # 1. Global ~/.env
    env.update(load_dotenv_simple(Path.home() / ".env"))

    # 2. Project .env (overrides global)
    env.update(load_dotenv_simple(Path(".env")))
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
env = {}

    # 1. Global ~/.env
    env.update(load_dotenv_simple(Path.home() / ".env"))

    # 2. Project .env (overrides global)
    env.update(load_dotenv_simple(Path(".env")))
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
env = {}

    # 1. Global ~/.env
    env.update(load_dotenv_simple(Path.home() / ".env"))

    # 2. Project .env (overrides global)
    env.update(load_dotenv_simple(Path(".env")))
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
env = {}

    # 1. Global ~/.env
    env.update(load_dotenv_simple(Path.home() / ".env"))

    # 2. Project .env (overrides global)
    env.update(load_dotenv_simple(Path(".env")))
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
env = {}

    # 1. Global ~/.env
    env.update(load_dotenv_simple(Path.home() / ".env"))

    # 2. Project .env (overrides global)
    env.update(load_dotenv_simple(Path(".env")))
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
env = {}

    # 1. Global ~/.env
    env.update(load_dotenv_simple(Path.home() / ".env"))

    # 2. Project .env (overrides global)
    env.update(load_dotenv_simple(Path(".env")))
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
env = {}

    # 1. Global ~/.env
    env.update(load_dotenv_simple(Path.home() / ".env"))

    # 2. Project .env (overrides global)
    env.update(load_dotenv_simple(Path(".env")))
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
env = {}

    # 1. Global ~/.env
    env.update(load_dotenv_simple(Path.home() / ".env"))

    # 2. Project .env (overrides global)
    env.update(load_dotenv_simple(Path(".env")))
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Lp3

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding
The skill instructs the agent to read local files, access environment variables, and make network calls, but it declares no explicit tool scope or permission boundaries. That increases the chance of unintended credential access or outbound requests occurring without clear review or least-privilege constraints.

Vague Triggers

Medium
Confidence
96% confidence
Finding
The trigger conditions are extremely broad, including any mention of OpenRouter, free models, proxying, code generation, model selection, or comparison. Over-broad activation can cause the skill to run in contexts where the user did not intend file reads, credential resolution, or network activity, increasing the chance of unnecessary sensitive actions.

Static analysis

No suspicious patterns detected.