mbosley/llm-mcp-server
criticalMCP server providing access to various LLM APIs as tools for Claude Code
MCP server (purpose undetermined)
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 }// 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.
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()// 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.
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)// 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.
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)// 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.
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 })// 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.
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"// 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.
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")// 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.
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}// 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.