bigcodegen/mcp-neovim-server
criticalControl Neovim using Model Context Protocol (MCP) and the official neovim/node-client JavaScript library
This MCP server connects to a running Neovim instance via a Unix socket, exposing Neovim's editing, navigation, search, and window management capabili...
352server.tool(
353 "vim_file_open",
354 "Open files into new buffers",
355 {
356 filename: z.string().describe("Path to the file to open")
357 },
358 async ({ filename }) => {
359 try {
360 const result = await neovimManager.openFile(filename);
361 return {
362 content: [{
363 type: "text",
364 text: result
365 }]
366 };
367 } catch (error) { ... }
368 }
369);// Local-only MCP, requires compromised LLM to exploit
The vim_file_open tool accepts a filename parameter as a string with no validation or sanitization. It passes the user-supplied path directly to neovimManager.openFile(), which likely opens the file in Neovim. An attacker (compromised LLM) could read arbitrary files on the system, such as /etc/passwd, SSH keys, or other sensitive files, by providing absolute paths or path traversal sequences like '../../etc/passwd'.
ImpactAn attacker could read any file the Neovim process has access to, leading to information disclosure of sensitive data (credentials, configuration, source code).
FixValidate and restrict the filename parameter to a specific working directory. Use path.resolve() and check that the resolved path is within an allowed base directory. Alternatively, reject absolute paths and path traversal sequences.
326server.tool(
327 "vim_buffer_save",
328 "Save current buffer or save to specific filename",
329 {
330 filename: z.string().optional().describe("Optional filename to save buffer to (defaults to current buffer's filename)")
331 },
332 async ({ filename }) => {
333 try {
334 const result = await neovimManager.saveBuffer(filename);
335 return {
336 content: [{
337 type: "text",
338 text: result
339 }]
340 };
341 } catch (error) { ... }
342 }
343);// Local-only MCP, requires compromised LLM to exploit
The vim_buffer_save tool accepts an optional filename parameter with no validation. It passes the user-supplied path directly to neovimManager.saveBuffer(). An attacker could write arbitrary content to any file the Neovim process can write to, potentially overwriting system files, configuration files, or planting malicious scripts.
ImpactAn attacker could write arbitrary data to any file on the system, leading to data corruption, privilege escalation (e.g., overwriting ~/.ssh/authorized_keys), or code execution.
FixValidate and restrict the filename parameter to a specific working directory. Use path.resolve() and ensure the resolved path is within an allowed base directory. Reject absolute paths and path traversal sequences.
99server.tool(
100 "vim_command",
101 "Execute Vim commands with optional shell command support",
102 { command: z.string().describe("Vim command to execute (use ! prefix for shell commands if enabled)") },
103 async ({ command }) => {
104 try {
105 if (command.startsWith('!')) {
106 const allowShellCommands = process.env.ALLOW_SHELL_COMMANDS === 'true';
107 if (!allowShellCommands) {
108 return {
109 content: [{
110 type: "text",
111 text: "Shell command execution is disabled..."
112 }]
113 };
114 }
115 }
116 const result = await neovimManager.sendCommand(command);
117 ...
118 } catch (error) { ... }
119 }
120);// Local-only MCP, requires compromised LLM to exploit
The vim_command tool accepts any Vim command string and executes it via neovimManager.sendCommand(). While shell commands are gated by an environment variable, arbitrary Vim commands can still be used to read/write files (e.g., :r /etc/passwd, :w! /tmp/evil), execute arbitrary Ex commands, and potentially escape the intended editing scope. The tool's purpose is to execute Vim commands, but the scope is too broad as it allows any Vim command, including those that can read/write arbitrary files.
ImpactAn attacker could use Vim commands to read arbitrary files, write to arbitrary files, or execute shell commands (if enabled), leading to information disclosure, data corruption, or code execution.
FixRestrict the allowed Vim commands to a safe subset (e.g., only navigation, editing, and search commands). Alternatively, implement a whitelist of allowed commands or validate that the command does not contain file I/O operations.
99server.tool(
100 "vim_command",
101 "Execute Vim commands with optional shell command support",
102 { command: z.string().describe("Vim command to execute (use ! prefix for shell commands if enabled)") },
103 async ({ command }) => {
104 try {
105 // Check if this is a shell command
106 if (command.startsWith('!')) {
107 const allowShellCommands = process.env.ALLOW_SHELL_COMMANDS === 'true';
108 if (!allowShellCommands) {
109 return {
110 content: [{
111 type: "text",
112 text: "Shell command execution is disabled. Set ALLOW_SHELL_COMMANDS=true environment variable to enable shell commands."
113 }]
114 };
115 }
116 }
117
118 const result = await neovimManager.sendCommand(command);// Local-only MCP, requires compromised LLM to exploit. Only exploitable if ALLOW_SHELL_COMMANDS=true is set.
The vim_command tool allows executing arbitrary Vim commands, and when the ALLOW_SHELL_COMMANDS environment variable is set to 'true', it also allows shell commands via the '!' prefix. This is a documented feature, but it exposes arbitrary shell execution capability that goes beyond the intended purpose of editing text. Even though it is gated by an environment variable, if enabled, a compromised LLM can execute arbitrary shell commands on the host.
ImpactAn attacker (compromised LLM) could execute arbitrary shell commands on the host system, leading to full system compromise, data exfiltration, or persistent access.
FixRemove shell command support entirely, or add additional restrictions such as a whitelist of allowed commands, or require user confirmation for shell commands.
73server.tool(
74 "vim_buffer",
75 "Get buffer contents with line numbers",
76 { filename: z.string().optional().describe("Optional file name to view a specific buffer") },
77 async ({ filename }) => {
78 try {
79 const bufferContents = await neovimManager.getBufferContents(filename);
80 return {
81 content: [{
82 type: "text",
83 text: Array.from(bufferContents.entries())
84 .map(([lineNum, lineText]) => `${lineNum}: ${lineText}`)
85 .join('\n')
86 }]
87 };
88 } catch (error) { ... }
89 }
90);// Local-only MCP, requires compromised LLM to exploit
The vim_buffer tool accepts an optional filename parameter with no validation. It passes the user-supplied path directly to neovimManager.getBufferContents(). An attacker could read arbitrary files by providing absolute paths or path traversal sequences, similar to vim_file_open.
ImpactAn attacker could read any file the Neovim process has access to, leading to information disclosure.
FixValidate and restrict the filename parameter to a specific working directory. Use path.resolve() and check that the resolved path is within an allowed base directory.
357server.tool(
358 "vim_file_open",
359 "Open files into new buffers",
360 {
361 filename: z.string().describe("Path to the file to open")
362 },
363 async ({ filename }) => {
364 try {
365 const result = await neovimManager.openFile(filename);
366 return {
367 content: [{
368 type: "text",
369 text: result
370 }]
371 };// Local-only MCP, requires compromised LLM to exploit
The vim_file_open tool accepts a filename parameter that is passed directly to neovimManager.openFile(). If the underlying implementation does not validate or restrict the path, an attacker can open and read arbitrary files on the system by providing an absolute path or path traversal sequence. The tool's purpose is to open files into buffers for editing, but without path validation it can be abused to read any file.
ImpactAn attacker (compromised LLM) could read any file the Neovim process has access to, including sensitive files like SSH keys, configuration files, or system secrets.
FixValidate that the filename parameter is a relative path within the project directory or restrict it to a whitelist of allowed directories. Use path resolution and ensure it does not escape the intended workspace.
332server.tool(
333 "vim_buffer_save",
334 "Save current buffer or save to specific filename",
335 {
336 filename: z.string().optional().describe("Optional filename to save buffer to (defaults to current buffer's filename)")
337 },
338 async ({ filename }) => {
339 try {
340 const result = await neovimManager.saveBuffer(filename);
341 return {
342 content: [{
343 type: "text",
344 text: result
345 }]
346 };// Local-only MCP, requires compromised LLM to exploit
The vim_buffer_save tool accepts an optional filename parameter that is passed directly to neovimManager.saveBuffer(). If the underlying implementation does not validate or restrict the path, an attacker can write arbitrary content to any file the Neovim process has write access to, by providing an absolute path or path traversal sequence. The tool's purpose is to save the current buffer, not to write arbitrary files.
ImpactAn attacker (compromised LLM) could overwrite critical system files, inject malicious code into startup scripts, or modify configuration files, potentially leading to privilege escalation or persistent compromise.
FixValidate that the filename parameter is a relative path within the project directory or restrict it to existing buffer names only. Use path resolution and ensure it does not escape the intended workspace.
76server.tool(
77 "vim_buffer",
78 "Get buffer contents with line numbers",
79 { filename: z.string().optional().describe("Optional file name to view a specific buffer") },
80 async ({ filename }) => {
81 try {
82 const bufferContents = await neovimManager.getBufferContents(filename);
83 return {
84 content: [{
85 type: "text",
86 text: Array.from(bufferContents.entries())
87 .map(([lineNum, lineText]) => `${lineNum}: ${lineText}`)
88 .join('\n')
89 }]
90 };// Local-only MCP, requires compromised LLM to exploit
The vim_buffer tool accepts an optional filename parameter that is passed directly to neovimManager.getBufferContents(). If the underlying implementation does not validate or restrict the path, an attacker can read arbitrary files on the system by providing an absolute path or path traversal sequence (e.g., /etc/passwd or ../../etc/shadow). The tool's purpose is to view buffer contents, not arbitrary files.
ImpactAn attacker (compromised LLM) could read any file the Neovim process has access to, including sensitive files like SSH keys, configuration files, or system secrets.
FixValidate that the filename parameter is a relative path within the project directory or restrict it to existing buffer names only. Use path resolution and ensure it does not escape the intended workspace.
437server.tool(
438 "vim_grep",
439 "Project-wide search using vimgrep with quickfix list",
440 {
441 pattern: z.string().describe("Search pattern to grep for"),
442 filePattern: z.string().optional().describe("File pattern to search in (default: **/* for all files)")
443 },
444 async ({ pattern, filePattern = "**/*" }) => {
445 try {
446 const result = await neovimManager.grepInProject(pattern, filePattern);
447 return {
448 content: [{
449 type: "text",
450 text: result
451 }]
452 };
453 } catch (error) { ... }
454 }
455);// Local-only MCP, requires compromised LLM to exploit
The vim_grep tool accepts a filePattern parameter with no validation. It passes the user-supplied pattern directly to neovimManager.grepInProject(). An attacker could use path traversal in the filePattern to search files outside the intended project directory, potentially reading sensitive files.
ImpactAn attacker could search for patterns in arbitrary files on the system, leading to information disclosure.
FixValidate and restrict the filePattern to a specific working directory. Use path.resolve() and ensure the resolved path is within an allowed base directory. Reject patterns containing '..' or absolute paths.
509server.tool(
510 "vim_tab",
511 "Manage Neovim tabs: create, close, and navigate between tabs",
512 {
513 action: z.enum(["new", "close", "next", "prev", "first", "last", "list"]).describe("Tab action to perform"),
514 filename: z.string().optional().describe("Filename for new tab (optional)")
515 },
516 async ({ action, filename }) => {
517 try {
518 const result = await neovimManager.manageTab(action, filename);
519 return {
520 content: [{
521 type: "text",
522 text: result
523 }]
524 };
525 } catch (error) { ... }
526 }
527);// Local-only MCP, requires compromised LLM to exploit
The vim_tab tool accepts an optional filename parameter with no validation. It passes the user-supplied path directly to neovimManager.manageTab(). An attacker could open arbitrary files in a new tab, reading sensitive files.
ImpactAn attacker could open and read any file the Neovim process has access to, leading to information disclosure.
FixValidate and restrict the filename parameter to a specific working directory. Use path.resolve() and check that the resolved path is within an allowed base directory.
437server.tool(
438 "vim_grep",
439 "Project-wide search using vimgrep with quickfix list",
440 {
441 pattern: z.string().describe("Search pattern to grep for"),
442 filePattern: z.string().optional().describe("File pattern to search in (default: **/* for all files)")
443 },
444 async ({ pattern, filePattern = "**/*" }) => {
445 try {
446 const result = await neovimManager.grepInProject(pattern, filePattern);
447 return {
448 content: [{
449 type: "text",
450 text: result
451 }]
452 };// Local-only MCP, requires compromised LLM to exploit
The vim_grep tool accepts a filePattern parameter that defaults to '**/*' and is passed directly to neovimManager.grepInProject(). If the underlying implementation does not restrict the search scope, an attacker can search for patterns across the entire filesystem, potentially reading sensitive files. The tool's purpose is project-wide search, but without path restrictions it can be abused to search arbitrary files.
ImpactAn attacker (compromised LLM) could search for sensitive patterns (e.g., passwords, keys) across the entire filesystem, leading to information disclosure.
FixRestrict the search scope to the current project directory or a configurable workspace. Validate that filePattern does not contain path traversal sequences.