In this article
Cómo crear servidores MCP seguros: guía para desarrolladores para evitar vulnerabilidades críticas
El Model Context Protocol (MCP) se ha convertido rápidamente en el estándar de facto para ampliar las capacidades de la IA. Desde que Anthropic lo anunció en noviembre de 2024, los desarrolladores se han apresurado a crear servidores MCP que conectan los LLM con bases de datos, sistemas de archivos, API y herramientas de línea de comandos. Sin embargo, en la carrera por liberar la autonomía de la IA, la seguridad suele quedar en segundo plano, aunque cada vez se conocen más las consecuencias de seguridad previsibles.
La realidad es contundente: los servidores MCP son simplemente más código, y más código implica más errores, una mayor superficie de ataque y más vulnerabilidades de seguridad. Lo que hace que MCP sea particularmente peligroso es el contexto en el que operan estos servidores. Se ejecutan en las máquinas de los desarrolladores, que contienen claves de API, credenciales y propiedad intelectual. Los controlan agentes de IA que pueden ser manipulados mediante inyección de prompts (e inyección indirecta de prompts). Y a menudo incluyen la misma clase de vulnerabilidades que han afectado a las aplicaciones web durante décadas.
Esta guía examina las vulnerabilidades críticas de seguridad que afectan actualmente a los servidores MCP, como la inyección de comandos, el path traversal y la inyección SQL, a través de CVE reales e investigaciones de seguridad. Aprenderás no solo cómo son estas vulnerabilidades, sino también cómo crear servidores MCP que las resistan desde el principio.

Inyección de comandos: la vulnerabilidad más común de MCP
La inyección de comandos es una de las vulnerabilidades que se detectan con más frecuencia en los servidores MCP. El patrón resulta inquietantemente familiar: un servidor MCP inicia comandos del sistema como npm, git o lsof e incorpora entradas controladas por el usuario sin la sanitización adecuada.
Puedes verlo desde la perspectiva de la inyección de prompts, pero se trata de un prompt adversario que, en última instancia, permite la ejecución remota de comandos.
Cómo entender la vulnerabilidad
Considera un servidor MCP aparentemente inofensivo que busca información de paquetes npm:
server.tool(
"getNpmPackageInfo",
"Get information about an npm package",
{ packageName: z.string() },
async ({ packageName }) => {
const output = execSync(`npm view ${packageName}`, {
encoding: "utf-8",
});
return {
content: [{ type: "text", text: output }],
};
}
);La vulnerabilidad es sutil, pero grave. El parámetro packageName se incorpora directamente a un comando de shell mediante interpolación de cadenas. Cuando un usuario le pregunta a su asistente de IA: "¿Cuál es la fecha de lanzamiento más reciente de react; touch /tmp/pwned?", el comando queda así:
npm view react; touch /tmp/pwnedEl punto y coma termina el primer comando, y el shell ejecuta sin problemas touch /tmp/pwned como un comando aparte. Un atacante que controla el prompt ahora puede ejecutar comandos arbitrarios en la máquina del desarrollador.
Los prompts se convierten en una responsabilidad real por CVE
Esto no es teórico. Se han detectado vulnerabilidades de inyección de comandos en varios servidores MCP:
El GitHub Kanban MCP Server (GHSA-6jx8-rcjx-vmwf) no sanitizaba las entradas del usuario, lo que permitía ejecutar comandos arbitrarios mediante operaciones de Git
El iOS Simulator MCP Server (GHSA-6f6r-m9pv-67jw) exponía vulnerabilidades similares
El paquete Create MCP Server STDIO contenía definiciones de herramientas defectuosas, que exponían la supervisión del sistema a ataques de inyección

La solución de seguridad: usa la API segura de Node.js execFile en lugar de exec
La función exec de Node.js inicia un shell y ejecuta comandos en él, lo que hace peligrosos los metacaracteres del shell como ;, | y $(). La función execFile ejecuta archivos directamente, sin interpretación del shell:
// VULNERABLE: Uses shell interpolation
execSync(`npm view ${packageName}`);
// SECURE: Arguments passed as array, no shell
execFile('npm', ['view', packageName], (error, stdout) => {
// handle result
});Esta es la implementación segura:
import { execFile } from 'child_process';
server.tool(
"getNpmPackageInfo",
{ packageName: z.string() },
async ({ packageName }) => {
return new Promise((resolve, reject) => {
execFile('npm', ['view', packageName], { encoding: 'utf-8' },
(error, stdout) => {
if (error) reject(error);
resolve({
content: [{ type: "text", text: stdout }],
});
}
);
});
}
);Con execFile, la entrada maliciosa react; touch /tmp/pwned se convierte en un único argumento que se pasa a npm view, que falla sin causar daños en lugar de ejecutar comandos arbitrarios.
Medidas adicionales para mitigar la inyección de comandos
Valida las entradas mediante listas de permitidos: si esperas nombres de paquetes, valídalos según las convenciones de nomenclatura de npm
Separa los comandos de los argumentos: usa API seguras para evitar la inyección de comandos y prefiere
execFileen lugar deexecEscapa los argumentos del shell: cuando no puedas evitar ejecutar comandos del shell, usa bibliotecas como
shell-quoteUsa API específicas del lenguaje: en lugar de iniciar
npm, usa directamente la API del registro de npmEstablece límites en la longitud de las entradas: las entradas inusualmente largas suelen indicar intentos de inyección

Path traversal: escapar del entorno aislado
Los servidores MCP suelen exponer operaciones del sistema de archivos, como leer archivos de configuración, acceder a documentación o servir recursos estáticos. Las vulnerabilidades de path traversal ocurren cuando los atacantes manipulan rutas de archivos para acceder a archivos fuera del directorio previsto.
El patrón de vulnerabilidad en el código del servidor MCP
Un patrón común en los controladores de recursos de MCP:
server.resource(
"pipeline-workflows",
new ResourceTemplate("pipeline-workflows://{name}", { list: undefined }),
async (uri, { name }) => {
const decodedName = decodeURIComponent(name);
let filePath = path.resolve(
process.cwd(),
'.github/workflows/',
`${decodedName}.yaml`
);
const fileContents = readFileSync(filePath, 'utf-8');
return {
contents: [{ uri: uri.href, text: fileContents }]
};
}
);Un atacante puede solicitar pipeline-workflows://../../../../../../etc/passwd y el servidor leerá obedientemente /etc/passwd. En la máquina de un desarrollador, hay objetivos más valiosos, entre ellos:
~/.ssh/id_rsa(claves privadas SSH)~/.aws/credentials(claves de acceso de AWS)archivos
.env(secretos de la aplicación)~/.npmrc(tokens de autenticación de npm)
Caso práctico: se detectó una vulnerabilidad de path traversal en el framework de IA Mastra
El paquete @mastra/mcp-docs-server contenía una vulnerabilidad de path traversal sutil. Aunque el código incluía una comprobación de seguridad, la ejecución continuaba incluso después de detectar el traversal:
async function readMdxContent(docPath, queryKeywords) {
const fullPath = path.resolve(path.join(docsBaseDir, docPath));
// Security check exists...
if (!fullPath.startsWith(path.resolve(docsBaseDir))) {
logger.error(`Path traversal attempt detected`);
return { found: false };
}
// ...
}
execute: async (args) => {
const result = await readMdxContent(path, queryKeywords);
if (result.found) {
return { /* ... */ };
}
// VULNERABILITY: Executes even after traversal detection!
const directorySuggestions = await findNearestDirectory(path, availablePaths);
// ...
}La función findNearestDirectory operaba sobre la ruta no validada y filtraba listados de directorios fuera del alcance previsto.
Manejo seguro de rutas para los recursos del servidor MCP
Implementa una defensa en profundidad para las operaciones con archivos:
import path from 'path';
import { accessSync, readFileSync, constants } from 'fs';
function secureFilePath(baseDir, userPath) {
// Normalize and resolve the full path
const normalizedBase = path.resolve(baseDir);
const requestedPath = path.resolve(baseDir, userPath);
// Verify the path stays within bounds
if (!requestedPath.startsWith(normalizedBase + path.sep)) {
throw new Error('Path traversal detected');
}
// Verify the file exists and is readable
accessSync(requestedPath, constants.R_OK);
return requestedPath;
}
server.resource(
"docs",
new ResourceTemplate("docs://{filename}", { list: undefined }),
async (uri, { filename }) => {
const baseDir = path.resolve(process.cwd(), 'docs');
// Halt execution immediately on invalid paths
const safePath = secureFilePath(baseDir, filename);
const contents = readFileSync(safePath, 'utf-8');
return {
contents: [{ uri: uri.href, text: contents }]
};
}
);Lista de verificación para prevenir el path traversal
Resuelve siempre las rutas: usa
path.resolve()para convertir las rutas relativas en absolutasValida el resultado: asegúrate de que las rutas resueltas comiencen con el directorio base previsto
Rechaza de inmediato: nunca continúes el procesamiento después de detectar intentos de traversal
Usa listas de permitidos: cuando sea posible, restringe el acceso a los archivos autorizados explícitamente
Evita las extensiones controladas por el usuario: no permitas que los usuarios especifiquen extensiones de archivo

Inyección SQL: la ilusión de "solo lectura"
A medida que los servidores MCP amplían su conectividad con bases de datos, las vulnerabilidades de inyección SQL se han convertido en una preocupación crítica. Aún más insidioso es que muchos servidores MCP para bases de datos implementan un modo de "solo lectura" defectuoso que genera una falsa sensación de seguridad.
La comprobación ingenua de solo lectura
Varios servidores MCP para bases de datos intentan imponer el acceso de solo lectura comprobando si las consultas comienzan con SELECT:
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const query = request.params.arguments?.query;
if (!query.trim().toLowerCase().startsWith("select")) {
throw new Error("Only SELECT queries are allowed");
}
const result = await client.query(query);
return { content: [{ type: "text", text: JSON.stringify(result) }] };
});Este enfoque falla estrepitosamente ante la amplia variedad de funciones integradas de PostgreSQL.
Cómo eludir las restricciones inseguras de solo lectura de los servidores MCP
PostgreSQL expone funciones administrativas que se pueden llamar dentro de instrucciones SELECT:
-- Denial of Service: Terminate database connections
SELECT pg_terminate_backend(pid) FROM pg_stat_activity
WHERE usename != 'admin';
-- Long-running resource exhaustion
SELECT pg_sleep(300);
-- Information disclosure: Expose all running queries
SELECT pid, usename, query FROM pg_stat_activity;
-- Configuration extraction
SELECT name, setting FROM pg_settings WHERE name LIKE '%password%';
-- Force checkpoint (performance disruption)
SELECT pg_start_backup('malicious_backup', true);
Caso práctico: vulnerabilidades reales de MCP explotadas mediante inyección SQL
En el Xata's MCP Server, al que no se le asignó un CVE oficial, se podía eludir la transacción de solo lectura del servidor:
COMMIT; INSERT INTO users (name) VALUES ('attacker');Al terminar la transacción de solo lectura con COMMIT, los atacantes podían ejecutar operaciones de escritura.
ExecuteAutomation Database Server (CVE-2025-59333): controles de acceso defectuosos similares permitían encadenar consultas:
SELECT * FROM users; DROP TABLE users; --¿Cómo implementar un acceso seguro a bases de datos para los servidores MCP?
Exige la ejecución de una sola consulta:
// Force prepared statement (prevents multi-query)
const result = await client.query({
name: "single-query",
text: userQuery,
values: [],
});Usa permisos a nivel de base de datos:
-- Create restricted user
CREATE USER mcp_readonly WITH PASSWORD 'secure_password';
-- Grant only SELECT on specific tables
GRANT SELECT ON public.users TO mcp_readonly;
GRANT SELECT ON public.products TO mcp_readonly;
-- Revoke dangerous function access
REVOKE EXECUTE ON ALL FUNCTIONS IN SCHEMA pg_catalog FROM mcp_readonly;Implementa listas de consultas permitidas:
const ALLOWED_QUERIES = {
"get_users": "SELECT id, name FROM users WHERE active = $1",
"get_products": "SELECT id, name, price FROM products WHERE category = $1"
};
server.tool("query_database", { queryName: z.string(), params: z.array(z.any()) },
async ({ queryName, params }) => {
const query = ALLOWED_QUERIES[queryName];
if (!query) throw new Error("Query not permitted");
return await client.query(query, params);
}
);Bloquea las funciones peligrosas:
const DANGEROUS_PATTERNS = [
/pg_\w+\(/i, // PostgreSQL system functions
/\bcopy\b/i, // COPY command
/\binto\s+outfile/i, // File operations
/\bload_file\b/i, // File loading
/\bexecute\b/i, // Dynamic execution
/\bsleep\b/i, // Sleep-based DoS
];
function validateQuery(query) {
for (const pattern of DANGEROUS_PATTERNS) {
if (pattern.test(query)) {
throw new Error("Query contains prohibited patterns");
}
}
}Aprovecha los recursos de MCP para un acceso estructurado:
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
const resourceUrl = new URL(request.params.uri);
const [schema, tableName] = resourceUrl.pathname.split('/').filter(Boolean);
// Validate against allowlist
if (!ALLOWED_TABLES.includes(tableName)) {
throw new Error('Table not permitted');
}
// Use parameterized query
const result = await client.query(
'SELECT * FROM information_schema.columns WHERE table_name = $1',
[tableName]
);
return { contents: [{ uri: request.params.uri, text: JSON.stringify(result) }] };
});
Protección de dependencias de terceros
Los servidores MCP dependen de paquetes npm para controladores de bases de datos, clientes HTTP y funciones de utilidad. Cada dependencia representa un posible riesgo para la cadena de suministro:
Dependencias vulnerables: CVE conocidos en paquetes como
pg,axiosolodashPaquetes maliciosos: ataques de typosquatting dirigidos a desarrolladores de servidores MCP
Vulnerabilidades transitivas: fallas de seguridad en las dependencias de tus dependencias
Estrategias de mitigación
Análisis continuo: integra Snyk u otras herramientas similares en tu pipeline de CI/CD para detectar dependencias vulnerables antes de la implementación
Fija las versiones de las dependencias: usa archivos de bloqueo (
package-lock.json) y verifica su integridadActualizaciones periódicas: monitorea los avisos de seguridad y aplica parches cuanto antes
Reduce al mínimo las dependencias: cada paquete es una superficie de ataque; evalúa si realmente necesitas el paquete en tu servidor MCP.
Audita antes de adoptar: consulta las puntuaciones de estado de los paquetes en la Snyk Security Database
Acciones recomendadas: cómo crear servidores MCP seguros

Para desarrolladores de servidores MCP
Valida todas las entradas: nunca confíes en los datos de las llamadas a herramientas generadas por LLM
Usa API seguras: prefiere
execFileen lugar deexecy las consultas parametrizadas en lugar de la interpolación de cadenasImplementa una defensa en profundidad: usa varias capas de validación, no una sola comprobación
Integra herramientas SAST: usa Snyk Code u otras herramientas similares para detectar vulnerabilidades durante el desarrollo
Aplica el principio de privilegio mínimo: limita al mínimo los permisos de los usuarios de bases de datos, el acceso al sistema de archivos y los permisos de red
Documenta el modelo de seguridad: especifica claramente los límites de confianza y las suposiciones
Para usuarios de servidores MCP
Audita antes de instalar: revisa el código fuente, especialmente los controladores de herramientas
Monitorea el comportamiento: presta atención al acceso inesperado a archivos, las solicitudes de red o la ejecución de comandos
Aísla los entornos: ejecuta los servidores MCP en entornos aislados
Mantén todo actualizado: aplica los parches de seguridad cuanto antes
Reporta las vulnerabilidades: practica la divulgación responsable cuando encuentres problemas
Para las organizaciones
Establece políticas para MCP: define los servidores aprobados y los requisitos de seguridad
Implementa análisis: analiza automáticamente el código del servidor MCP antes de la implementación
Capacita a los desarrolladores: enseña a los equipos prácticas de desarrollo seguro para MCP
Monitorea y audita: registra la actividad del servidor MCP para revisarla en términos de seguridad
El camino a seguir
El Model Context Protocol representa un paradigma poderoso para ampliar las capacidades de la IA, pero el poder conlleva responsabilidad. Las vulnerabilidades que analizamos —inyección de comandos, path traversal e inyección SQL— no son nuevas. Son las mismas fallas que han afectado a las aplicaciones web durante décadas y que ahora aparecen en un nuevo contexto, con un impacto potencialmente mayor.
La ironía es evidente: en nuestro afán por hacer que la IA sea más capaz, la hemos conectado a sistemas construidos sobre los mismos cimientos frágiles que llevamos años intentando proteger. Un LLM con acceso a un servidor MCP vulnerable se convierte, sin saberlo, en un vector de ataque; su inteligencia queda convertida en arma mediante una simple manipulación de cadenas.

Para avanzar, debemos tratar el desarrollo de servidores MCP con el mismo rigor que aplicamos a cualquier código crítico para la seguridad. Valida las entradas. Usa API seguras. Implementa una defensa en profundidad. Analiza para detectar vulnerabilidades. Y recuerda que "solo lectura" suele ser una ilusión peligrosa.
Existen herramientas para crear servidores MCP seguros. La pregunta es si las usaremos antes de que la próxima ola de vulnerabilidades aparezca en los titulares.
¿Quieres entender las nuevas rutas de ataque a servidores MCP, desde el envenenamiento y la suplantación de herramientas hasta los flujos tóxicos? Analiza incidentes reales y aprende a defenderte con estrategias prácticas que tienen en cuenta los flujos en el eBook Securing The MCP Servers Ecosystem.
¡Compite en Fetch the Flag 2026!
Pon a prueba tus habilidades, resuelve los desafíos y domina la tabla de posiciones. Acompáñanos desde las 12 p. m. ET del 12 de febrero hasta las 12 p. m. ET del 13 de febrero en el evento CTF definitivo.