[ ⌘K ]
← BACK TO SEARCH

mbosley/llm-mcp-server

critical

MCP server providing access to various LLM APIs as tools for Claude Code

MCP server (purpose undetermined)

purpose: MCP server (purpose undetermined)threat: network exposed
Python0May 20, 2026May 20, 2026GITHUB
5/20/2026
critical1 finding
server.py
1037        if dynamic_tools:
1038            for tool_def in dynamic_tools:
1039                tool_name = tool_def["name"]
1040                all_tools[tool_name] = {
1041                    "command": tool_def["command"],
1042                    "schema": tool_def["schema"]
1043                }
server.py:1037

// Exploitable via LLM prompt injection; network_exposed means any user can trigger this.

The kimi_chat tool accepts a 'dynamic_tools' parameter that allows the LLM to define arbitrary shell commands. These commands are later executed via execute_cli_tool() with shell=True. The safety checks in is_command_safe() are bypassed because the command is provided directly by the user/LLM, not from a predefined list. The dangerous command blocklist can be easily circumvented (e.g., using 'rm' with different casing or encoding).

ImpactAn attacker controlling the LLM prompt can execute arbitrary shell commands on the server, including reading/writing files, exfiltrating data, or taking full control of the system.

FixRemove the dynamic_tools feature entirely, or restrict it to a whitelist of safe commands. Never allow user-provided commands to be executed via shell=True. Use subprocess with a list of arguments instead of a string.

high1 finding
server.py
753        if "{file:" in prompt or "{files:" in prompt:
754            prompt = construct_prompt(prompt)
755        
756        # Load files if provided
757        if files:
758            file_contents = []
759            for pattern in files:
760                for filepath in glob.glob(pattern, recursive=True):
761                    if Path(filepath).is_file():
762                        try:
763                            with open(filepath, 'r', encoding='utf-8') as f:
764                                content = f.read()
server.py:753server.py:757

// Exploitable via LLM prompt injection; network_exposed means any user can trigger this.

Multiple tools (analyze_with_gemini, quick_gpt, balanced_llm, route_to_best_model, kimi_chat) accept file paths via the 'files' parameter or file interpolation syntax {file:path}. These paths are resolved using glob.glob with recursive=True and then read without any restriction on which files can be accessed. An attacker can read arbitrary files on the system, including sensitive files like /etc/passwd, SSH keys, or environment files.

ImpactAn attacker can read any file on the server, leading to credential theft, source code disclosure, or other sensitive data exposure.

FixRestrict file access to a specific allowed directory (e.g., the project root). Validate that resolved paths are within the allowed directory. Disallow absolute paths and path traversal sequences.

high1 finding
server.py
148def save_session(session_id: str, messages: List[Dict], metadata: Dict = None) -> None:
149    """Save a conversation session to disk"""
150    KIMI_SESSIONS_DIR.mkdir(exist_ok=True)  # Ensure directory exists
151    session_file = KIMI_SESSIONS_DIR / f"{session_id}.json"
152    session_data = {
153        "session_id": session_id,
154        "messages": messages,
155        "metadata": metadata or {},
156        ...
157    }
158    
159    with open(session_file, "w") as f:
160        json.dump(session_data, f, indent=2)
server.py:1148server.py:148

// Exploitable via LLM prompt injection; network_exposed means any user can trigger this.

The session_id parameter is used directly in constructing the file path without sanitization. An attacker can use path traversal sequences (e.g., '../../etc/evil') in the session_id to write files to arbitrary locations. The session data is written as JSON, but the content of messages is attacker-controlled, allowing arbitrary file content.

ImpactAn attacker can write arbitrary files to the filesystem, potentially overwriting configuration files, planting malicious scripts, or achieving code execution.

FixSanitize session_id to remove path traversal characters (e.g., '..', '/'). Use a safe filename generation method, such as a UUID or hash. Ensure the final path is within the intended directory.

high1 finding
server.py
165def load_session(session_id: str) -> Optional[Dict]:
166    """Load a conversation session from disk"""
167    if session_id == "@last":
168        sessions = list(KIMI_SESSIONS_DIR.glob("*.json"))
169        if not sessions:
170            return None
171        latest = max(sessions, key=lambda p: p.stat().st_mtime)
172        session_id = latest.stem
173    
174    session_file = KIMI_SESSIONS_DIR / f"{session_id}.json"
175    if not session_file.exists():
176        return None
177    
178    with open(session_file, "r") as f:
179        return json.load(f)
server.py:958server.py:165

// Exploitable via LLM prompt injection; network_exposed means any user can trigger this.

The session_id parameter is used directly in constructing the file path without sanitization. An attacker can use path traversal sequences (e.g., '../../etc/passwd') to read arbitrary JSON files. While the file must have a .json extension, this still allows reading sensitive JSON files (e.g., config files, credential files).

ImpactAn attacker can read arbitrary JSON files on the system, potentially exposing credentials or other sensitive data.

FixSanitize session_id to remove path traversal characters. Ensure the resolved path is within the intended sessions directory.

high1 finding
server.py
182def list_sessions() -> List[Dict]:
183    """List all available sessions"""
184    sessions = []
185    if not KIMI_SESSIONS_DIR.exists():
186        return sessions
187    for session_file in KIMI_SESSIONS_DIR.glob("*.json"):
188        try:
189            with open(session_file, "r") as f:
190                data = json.load(f)
191                sessions.append({
192                    "session_id": data["session_id"],
193                    ...
194                })
server.py:928server.py:182

// Exploitable via LLM prompt injection; requires ability to write files to the sessions directory first.

The list_sessions function reads all JSON files in the .kimi_sessions directory and returns their contents. If an attacker can write a symlink or file into that directory (e.g., via the session save path traversal), they could read arbitrary files by having them parsed as JSON. Additionally, the session_id field from the JSON is returned, which could contain sensitive data.

ImpactCombined with the file write vulnerability, an attacker could exfiltrate arbitrary files by creating a symlink to a target file and then listing sessions.

FixValidate that files in the sessions directory are actual session files (e.g., by checking a signature or UUID format). Do not return raw session_id from file content without validation.

medium1 finding
server.py
75def is_command_safe(command: str) -> tuple[bool, str]:
76    """Check if a command is safe to execute"""
77    command_lower = command.lower()
78    
79    # Check for dangerous base commands
80    first_word = command_lower.split()[0] if command_lower.split() else ""
81    if first_word in DANGEROUS_COMMANDS:
82        return False, f"Command '{first_word}' is not allowed for safety reasons"
83    
84    # Check for dangerous patterns
85    for pattern in DANGEROUS_PATTERNS:
86        if pattern in command:
87            return False, f"Pattern '{pattern}' is not allowed in commands"
server.py:1099server.py:75

// Exploitable via LLM prompt injection; network_exposed means any user can trigger this.

The safety checks in is_command_safe() are easily bypassed. The blocklist approach is inherently weak: an attacker can use absolute paths (e.g., /bin/rm), base64-encoded commands, or other obfuscation techniques. The check for dangerous patterns is case-sensitive and can be bypassed with encoding. Additionally, the check for command chaining operators (&&, ||, ;, |) is duplicated and incomplete.

ImpactAn attacker can execute dangerous commands despite the safety checks, leading to system compromise.

FixUse a whitelist of allowed commands instead of a blocklist. Avoid shell=True; use subprocess with a list of arguments. If shell=True is unavoidable, implement strict input validation and consider using a sandbox or container.

medium1 finding
server.py
350    if COST_LOG_FILE:
351        try:
352            with open(COST_LOG_FILE, 'a') as f:
353                log_entry = {
354                    "timestamp": datetime.now().isoformat(),
355                    "model": model,
356                    "cost": cost_info["cost"],
357                    "tokens": cost_info["tokens"],
358                    "cumulative_cost": cost_info["cumulative"]["total_cost"]
359                }
360                f.write(json.dumps(log_entry) + "\n")
server.py:350

// Exploitable via file read vulnerability; network_exposed increases risk.

The cost log file is written to a configurable path (LLM_COST_LOG environment variable). While the log entry itself does not contain API keys, the log file is written to disk and could be read by an attacker via the file read vulnerability. Additionally, API keys are loaded from environment variables and stored in memory, but could be exposed through error messages or debugging output.

ImpactAn attacker could read the cost log file to gather information about usage patterns, and potentially combine with other vulnerabilities to extract API keys from memory or environment.

FixEnsure cost log files are stored in a secure location with restricted permissions. Avoid logging sensitive information. Consider using a secure logging service instead of local files.

low1 finding
server.py
233BUILTIN_TOOLS = {
234    "get_current_time": {
235        "command": "date '+%Y-%m-%d %H:%M:%S'",
236        ...
237    },
238    "add_numbers": {
239        "command": "python3 -c 'import sys; print(float(sys.argv[1]) + float(sys.argv[2]))'",
240        ...
241    },
242    "list_files": {
243        "command": "ls -la",
244        ...
245    }
246}
server.py:233

// Exploitable via LLM prompt injection; network_exposed means any user can trigger this.

The built-in tools include 'list_files' which executes 'ls -la' and 'add_numbers' which executes arbitrary Python code via python3 -c. While these are intended features, they demonstrate that the server is designed to execute arbitrary commands. The 'list_files' tool exposes directory listing, and 'add_numbers' could be abused to execute arbitrary Python code if the arguments are not properly sanitized (though they are converted to float).

ImpactThe built-in tools themselves are limited, but they set a precedent for command execution. The 'add_numbers' tool uses python3 -c with user input, which could be exploited if the input validation is bypassed.

FixRemove the 'add_numbers' tool or implement it using a safe arithmetic library instead of executing Python code. Restrict 'list_files' to a specific directory.

shell.execfilesystem.readenv.exposureaws.integration
100
LLM-based
low findings+5
high findings+100
medium findings+30
critical findings+40