safe-mcp-suite
safe-mcp-suite
Dos servidores MCP — un terminal y un organizador de archivos — que comparten un núcleo de seguridad que a ninguno de los dos se le permite eludir.
Qué es esto
Busca en GitHub un servidor MCP que ejecute comandos de shell y encontrarás una y otra vez el mismo archivo: una herramienta decorada con @mcp.tool(), una llamada a subprocess.run(command, shell=True), y el resultado devuelto directamente al modelo. Los de sistema de archivos tienen la misma forma: os.rename en un bucle, quizás con un try/except alrededor.
Funcionan. Ese es el problema. Funcionan hasta que el modelo produce algo que nadie anticipó, y para entonces la eliminación ya ha ocurrido, y no hay registro de qué se ejecutó ni por qué se permitió.
Este repositorio es esos dos servidores reconstruidos para que la seguridad sea el diseño, no un envoltorio añadido encima. Un único paquete safety/ es dueño de cada decisión que podría hacerte daño: qué está permitido, dónde está el límite, qué se registra, qué se oculta. Los dos servidores debajo son cableado. Ninguno puede llegar más allá del núcleo, porque ninguno implementa una regla propia.
La apuesta detrás de esto: la política determinista es más confiable que el juicio del modelo. Un prompt puede convencer a un modelo de dejar de ser cuidadoso. No puede convencer a una comprobación de contención de rutas de devolver True.
Related MCP server: Safe Terminal MCP Server
Inicio rápido
Necesitas Python 3.12+ y uv.
git clone https://github.com/Asaad-Suliman/safe-mcp-suite.git safe-mcp-suite
cd safe-mcp-suite
uv sync
./scripts/make_demo_sandbox.shEse último script siembra sandbox/terminal, sandbox/files y state/ para que ambos servidores tengan un lugar legal donde operar. Luego inicia el que quieras:
uv run safe-mcp terminal --config policy.example.toml
uv run safe-mcp files --config policy.example.tomlSobre la bandera --config
No es opcional, y no hay respaldo. No hay búsqueda automática de ./policy.toml, ni carga de .env, ni una raíz predeterminada que apunte silenciosamente a tu directorio personal. Si ni --config ni SAFE_MCP_POLICY_FILE están configurados, el servidor imprime el motivo y sale.
Esto es deliberado y es la decisión más dogmática de toda la configuración. Una zona de pruebas que silenciosamente usa un valor predeterminado conveniente es una zona de pruebas que algún día usará un valor predeterminado costoso. Negarse a iniciar es el fallo más barato posible.
Dos archivos de política se incluyen con el repositorio, y no son intercambiables:
Archivo | Qué es | ¿Arrancará? |
| Ejemplo funcional, con raíz en | Sí — ejecútalo ya |
| Plantilla anotada con las raíces comentadas | No, a propósito |
policy.toml se niega a iniciar hasta que rellenes jail_root y workspace_root tú mismo. Esa negativa es una característica, y hay una prueba de regresión que la mantiene. Cópialo, edítalo, apúntalo a directorios reales cuando estés listo.
Las raíces también pueden venir del entorno si lo prefieres:
SAFE_MCP_JAIL_ROOT=/path/to/jail
SAFE_MCP_WORKSPACE_ROOT=/path/to/workspaceRegistro con un cliente MCP
{
"mcpServers": {
"safe-mcp terminal": {
"command": "uv",
"args": ["run", "safe-mcp", "terminal"],
"env": {
"SAFE_MCP_POLICY_FILE": "/srv/safe-mcp/policy.example.toml",
"SAFE_MCP_JAIL_ROOT": "/srv/safe-mcp/sandbox"
}
},
"safe-mcp files": {
"command": "uv",
"args": ["run", "safe-mcp", "files"],
"env": {
"SAFE_MCP_POLICY_FILE": "/srv/safe-mcp/policy.example.toml",
"SAFE_MCP_WORKSPACE_ROOT": "/srv/safe-mcp/inbox"
}
}
}
}Demostración
Todo lo siguiente es salida real capturada. Nada aquí está escrito a mano, recortado o embellecido después del hecho — estos son los sobres OperationResult reales devueltos por un cliente MCP en vivo hablando con ambos servidores, ejecutados contra la zona de pruebas sembrada desde ./scripts/make_demo_sandbox.sh con policy.example.toml.
Lee el campo code en cada uno. Esa taxonomía es el punto central: cada resultado, éxito o rechazo, vuelve con la misma forma.
Servidor de terminal
Un comando permitido:
>>> tool: run_command args: {"command": "cat notes.txt"}
{
"action": "run_command",
"code": "OK",
"detail": {
"exit_code": 0,
"stderr": "",
"stdout": "demo file\n"
},
"duration_ms": 1,
"ok": true,
"reason": "'cat' is allowed"
}Un comando denegado:
>>> tool: run_command args: {"command": "rm -rf /"}
{
"action": "run_command",
"code": "POLICY_DENIED",
"detail": {},
"duration_ms": 0,
"ok": false,
"reason": "'rm' is on the denylist"
}Observa lo que la denegación no es: un traceback, una excepción lanzada, o una cadena que el modelo tenga que adivinar. Es un código tipado con una razón adjunta.
Servidor de archivos
Esta secuencia se ejecuta contra un espacio de trabajo sembrado que contiene archivos ordinarios, un instalador, un archivo de punto y un enlace simbólico — uno de cada cosa que la política trata de manera diferente.
plan_organize proponiendo movimientos y listando omisiones:
>>> tool: plan_organize args: {}
{
"action": "plan_organize",
"code": "OK",
"detail": {
"created": "2026-08-07T16:49:00.048899+00:00",
"move_count": 2,
"moves": [
{
"category": "Images",
"dest": "Images/photo.png",
"size": 41,
"src": "photo.png"
},
{
"category": "Documents",
"dest": "Documents/report.pdf",
"size": 16,
"src": "report.pdf"
}
],
"plan_id": "7dcf2cff-399d-4af5-bb9a-a4131e1d5288",
"skip_count": 3,
"skips": [
{
"code": "NEEDS_EXPLICIT_REQUEST",
"name": ".bashrc",
"reason": "dotfiles are configuration, not clutter to be filed",
"rule": "organize"
},
{
"code": "POLICY_DENIED",
"name": "link.pdf",
"reason": "'organize' is not permitted by any rule (deny by default)",
"rule": null
},
{
"code": "NEEDS_EXPLICIT_REQUEST",
"name": "setup.exe",
"reason": "installers, executables and application folders are left where the user put them",
"rule": "organize"
}
],
"truncated": false
},
"duration_ms": 0,
"ok": true,
"reason": "proposed 2 move(s), skipped 3"
}apply_plan ejecutando ese mismo plan:
>>> tool: apply_plan args: {"plan_id": "7dcf2cff-399d-4af5-bb9a-a4131e1d5288"}
{
"action": "apply_plan",
"code": "OK",
"detail": {
"moved": 2,
"moves": [
{
"dest": "Images/photo.png",
"src": "photo.png"
},
{
"dest": "Documents/report.pdf",
"src": "report.pdf"
}
],
"plan_id": "7dcf2cff-399d-4af5-bb9a-a4131e1d5288",
"planned": 2
},
"duration_ms": 2,
"ok": true,
"reason": "moved 2 file(s)"
}Ahora el par interesante. Misma herramienta, dos objetivos nombrados, dos respuestas diferentes.
move_file rechazado por PROTECTION — el mismo enlace simbólico que plan_organize omitió arriba, nombrado explícitamente:
>>> tool: move_file args: {"src": "link.pdf", "dest": "Documents/link.pdf"}
{
"action": "move_file",
"code": "POLICY_DENIED",
"detail": {},
"duration_ms": 0,
"ok": false,
"reason": "'organize' is not permitted by any rule (deny by default)"
}move_file teniendo éxito con el instalador que plan_organize difirió con NEEDS_EXPLICIT_REQUEST, ahora nombrado explícitamente:
>>> tool: move_file args: {"src": "setup.exe", "dest": "Documents/setup.exe"}
{
"action": "move_file",
"code": "OK",
"detail": {
"dest": "Documents/setup.exe",
"moved": 1,
"src": "setup.exe"
},
"duration_ms": 0,
"ok": true,
"reason": "moved setup.exe"
}El planificador rechazó ambos. Preguntado directamente, el movedor rechazó uno e hizo el otro. Esa diferencia no es una inconsistencia — es el modelo de dos capas, y tiene su propia sección más abajo.
Diseño del sistema
Los servidores son cableado, no política
Ningún archivo de servidor contiene una regla de seguridad. Todas viven en safety/, y ambos servidores alcanzan las mismas funciones para obtener las mismas respuestas.
flowchart TD
A["MCP client"] --> B["terminal server"]
A --> C["files server"]
B --> D["safety/policy.py<br/>allow or deny"]
C --> D
D --> E["safety/paths.py<br/>PathJail containment"]
E --> F["execute or move"]
F --> G["safety/redact.py<br/>secrets out, then truncate"]
G --> H["safety/audit.py<br/>append-only JSONL"]
H --> I["OperationResult"]
I --> AEsto no es orden por el orden mismo. Significa que una corrección de seguridad aterriza en exactamente un lugar, y significa que un revisor que audite este repositorio lea safety/ y haya terminado. No hay una segunda implementación escondida en un módulo de servidor, desviándose silenciosamente de la primera.
Cómo fluye realmente una solicitud
Analizar. La cadena de comando se divide en una lista argv. Los metacaracteres de shell se rechazan aquí, antes de que se interprete nada — incluso dentro de comillas. Esa última parte es deliberadamente conservadora y es un techo documentado, no un descuido.
Evaluar. El motor de política responde permitir o denegar. Denegar siempre gana. Cualquier cosa no permitida explícitamente es rechazada, así que una política vacía es un servidor inútil, no uno abierto.
Contener. Cada ruta se resuelve y se comprueba contra la raíz de la jaula. Los enlaces simbólicos se inspeccionan como enlaces y nunca se atraviesan.
Actuar.
subprocess.runconshell=False, un entorno depurado de exactamentePATH,HOME,LANG, un tiempo de espera y un límite de salida. O, en el lado de archivos, un solo movimiento protegido registrado en un diario.Redactar, luego truncar. En ese orden, siempre.
Registrar. Añadir al registro de auditoría. Si esa escritura falla, la operación falla con ella.
Seis decisiones que vale la pena robar
Los errores son datos, no excepciones. Cada resultado es un OperationResult con un ResultCode tipado. Ningún traceback llega al cliente. Un modelo que recibe un stack trace intentará sortearlo; un modelo que recibe POLICY_DENIED ha recibido algo útil e inequívoco.
Denegar gana, siempre. Las reglas de permitir y denegar no se ponderan ni se ordenan en un desempate. Si una denegación coincide, la respuesta es no. Las reglas de argumentos coinciden con tokens sin importar el orden, así que reorganizar banderas no es una vía de escape.
Nada arranca sin una raíz explícita. Cubierto en el Inicio rápido, pero también pertenece a la lista de diseño, porque "valor predeterminado sensato" es como la mayoría de las zonas de pruebas adquieren su primera fuga.
El registro de auditoría tiene permitido detenerte. Cierre por fallo por defecto: si el registro no se puede escribir, la operación no ocurre. Puedes cambiarlo a apertura por fallo con SAFE_MCP_AUDIT_FAIL_MODE, y esa es una decisión que tomas a propósito, en un archivo de configuración, donde alguien pueda verla.
Redactar antes de truncar. Invierte esos dos y un límite de salida de 64KB puede cortar un secreto por la mitad y emitir el primer fragmento, sin haber coincidido con ningún patrón. Esta es una elección de orden de dos líneas que cierra toda una categoría de fuga.
La detección de secretos basada en entropía está desactivada por defecto. Se dispara constantemente con hashes, UUIDs y cargas útiles base64. Un redactor que grita "lobo" termina siendo desactivado por sus propios usuarios, lo cual es peor que uno que admite sus límites desde el principio.
PROTECTION vs RESTRAINT
Las reglas de omisión se dividen en dos capas, y cada regla declara a cuál pertenece.
PROTECTION es aplicada por todas las herramientas, sin excepción. Solo parte de ella es una regla declarada — unsafe-name cubre caracteres de control en un nombre de archivo. El resto es estructural: una fuga de ruta falla la contención en safety/paths.py, un destino ocupado falla la comprobación lstat en servers/files/apply.py, y un enlace simbólico no coincide con ninguna regla de permitir, cayendo en denegar por defecto — que evaluate_layered clasifica deliberadamente como PROTECTION. Ver la nota más abajo. De cualquier manera, no hay bandera, ni anulación, ni argumento de "sé lo que hago". Estos son invariantes.
RESTRAINT es aplicada solo por plan_organize. Instaladores, carpetas de aplicaciones, archivos del sistema, archivos ocultos, directorios. No son peligrosos — son cosas sobre las que un clasificador automático no debería adivinar en tu nombre. El planificador los reporta como NEEDS_EXPLICIT_REQUEST y sigue adelante.
La consecuencia es el par de la demostración anterior. move_file moverá un instalador que tú mismo nombraste, porque rechazarlo sería paternalismo, no seguridad. No moverá un enlace simbólico por muy explícitamente que lo pidas, porque eso es contención.
Un matiz que vale la pena hacer explícito: la razón del rechazo del enlace simbólico dice
"'organize' no está permitido por ninguna regla (denegar por defecto)" en lugar de
cualquier cosa que mencione "enlace simbólico". Un enlace simbólico no coincide con ninguna regla de permitir en ninguna
capa, así que cae en denegar por defecto — y safety.policy.evaluate_layered
clasifica un fallo de denegar por defecto no coincidente como PROTECTION a propósito,
como la lectura más estricta de algo para lo que la política no tiene vocabulario. La
redacción genérica no es una garantía más débil; move_file nombrando el enlace simbólico
directamente, arriba, es la prueba.
Un detalle de implementación que repetiría en cualquier lugar: el conjunto PROTECTION se deriva filtrando el conjunto completo de reglas, nunca se ensambla como su propia lista. Dos listas mantenidas a mano se desvían, y el modo de fallo de la desviación aquí es una regla de protección que desaparece silenciosamente. Un filtro no puede olvidar.
Sobre las pruebas
661 pruebas, todas herméticas. Ninguna prueba escribe estado real: cualquier cosa que se ejecute lo hace contra tmp_path, y las dos suites que leen los policy.toml y policy.example.toml incluidos los copian a tmp_path primero. Nunca se toca un registro de auditoría o espacio de trabajo real. CI ejecuta la suite más un escaneo de gitleaks en cada push y pull request.
El número quedará obsoleto en cuanto añada una prueba. La propiedad que importa es el aislamiento, no el recuento.
Garantías
Cada fila nombra el módulo y la función que la aplica. Si una afirmación aquí no está respaldada por código que puedas abrir, no debería estar en la tabla.
Garantía | Aplicada por |
Denegar por defecto, en ambos servidores |
|
Una denegación coincidente siempre gana a una autorización coincidente |
|
Sin shell en el servidor de terminal |
|
Metacaracteres de shell rechazados antes de cualquier comprobación de política |
|
Los comandos están confinados a un directorio |
|
Los movimientos de archivos están confinados a un espacio de trabajo, seguros frente a enlaces simbólicos en el componente final |
|
Política de archivos de dos capas: invariante de seguridad frente a evitación de conjeturas |
|
Ningún destino se sobrescribe silenciosamente jamás |
|
No existe ninguna herramienta de borrado, protegida o no |
|
Cada movimiento es deshacible, incluido un plan aplicado completo |
|
Un plan que ha quedado obsoleto no mueve nada en absoluto |
|
Un comando colgado se mata en un plazo |
|
La salida está limitada, después de la redacción, nunca antes |
|
El proceso hijo recibe un entorno depurado |
|
Los secretos se redactan tanto de la salida como de los campos de auditoría |
|
El registro de auditoría es de cierre por defecto |
|
Cada rechazo se registra, con un motivo |
|
Modelo de amenazas — explícitamente fuera de alcance
Un comando peligroso que hayas incluido en la lista blanca. Si permites un intérprete o una herramienta similar a un shell (
bash,python,sh,find -exec,awk,env, …), el modelo puede hacer cualquier cosa que esa herramienta pueda. La fortaleza de la política depende enteramente de la lista blanca del operador.Escapes del kernel / sandbox. El confinamiento es una comprobación de contención de rutas, no un sandbox del kernel — sin namespaces, cgroups ni seccomp. Ver "Advertencias honestas".
Acceso del host a los archivos de estado. El registro de auditoría y el diario de deshacer son a prueba de manipulaciones para el servidor, no a prueba de manipulaciones contra cualquiera con acceso al sistema de archivos del host.
Completitud de la redacción. Basada en patrones y de mejor esfuerzo; una forma de secreto que los patrones no reconocen pasa.
Identidad multiinquilino o limitación de velocidad. No hay autenticación por llamante dentro de ninguno de los servidores — el límite de confianza es "quien pueda iniciar este proceso", que un cliente MCP hace cumplir al lanzarlo, no este código.
Una condición de carrera entre validar una ruta y actuar sobre ella (TOCTOU). Ver "Advertencias honestas" más abajo.
Comparado con un servidor MCP ingenuo
Muchos servidores MCP rápidos envuelven subprocess.run(cmd, shell=True) para el acceso a la terminal y os.rename para los movimientos de archivos. Ambos son convenientes e inseguros. Esta tabla es factual, no una afirmación de seguridad perfecta.
Preocupación | Servidor MCP ingenuo | safe-mcp-suite |
Ejecución de comandos |
| Solo |
Metacaracteres de shell | Interpretados ( | Rechazados antes de cualquier comprobación de política |
Movimientos de archivos |
| Confinado a un espacio de trabajo; un enlace simbólico en cualquiera de los extremos se rechaza, nunca se sigue |
Sobrescribir un archivo | Generalmente silencioso — | Siempre se rechaza; nunca se inventa una variante numerada ( |
Deshacer | Ninguno | Cada movimiento se registra en el diario; |
Borrar archivos | A menudo presente, a menudo sin protección | No existe ninguna herramienta de borrado en este servidor, punto |
Entorno dado a un proceso hijo | Entorno completo del padre, incluidos los secretos | Depurado a |
Secretos en la salida o los registros | Se dejan pasar | Redactados, antes de la truncación, tanto en la respuesta como en el registro de auditoría |
Auditabilidad | Ninguna por defecto | JSONL de solo añadir, cierre por defecto |
Acción automática frente a acción solicitada explícitamente | Un solo camino de código trata ambos igual |
|
Ambas herramientas devuelven un OperationResult:
OperationResult {
ok: bool
code: ResultCode
action: str
reason: str
detail: dict # stdout, stderr, exit_code — empty when nothing ran
duration_ms: int
}run_command(command: str, cwd: str | None = None)— evalúacommandcontra la política y, si está permitido, lo ejecuta en un entorno aislado.cwdes opcional y debe resolverse dentro de la raíz del confinamiento; una travesía, un enlace simbólico o una ruta absoluta que escape devuelvePATH_ESCAPEsin ejecutar nada. Cada invocación se audita; bajo auditoría de cierre, un registro no escribible devuelveAUDIT_UNAVAILABLEen lugar de ejecutar sin registrar. Un código de salida distinto de cero sigue siendook: true— el comando se ejecutó; si tuvo éxito es asunto suyo.explain_command(command: str)— la ejecución en seco. Alcanza el mismo análisis y evaluación querun_commandy regresa antes del ejecutor, por lo quedetailnunca llevastdout,stderrni un código de salida — no se ejecutó nada.
Códigos de resultado que este servidor puede devolver: OK, POLICY_DENIED, INVALID_REQUEST, PATH_ESCAPE, TIMEOUT, OUTPUT_TRUNCATED, OPERATION_FAILED, AUDIT_UNAVAILABLE, INTERNAL_ERROR.
Seis herramientas, y la lista es el diseño — no hay una séptima, y ninguna de ellas borra.
list_files(subdir: str | None = None)— solo lectura. Informa del nombre, tamaño, categoría de cada entrada, si el organizador la movería y por qué no cuando no lo haría.plan_organize()— propone movimientos y omisiones. No cambia nada, ni siquiera las carpetas de destino. Devuelve unplan_idpara pasar aapply_plan.apply_plan(plan_id: str)— lleva a cabo un plan. Cada archivo se vuelve a comprobar primero; si alguno cambió, se movió o desapareció desde la planificación, se rechaza el plan completo. De un solo uso — un id no se puede reproducir.move_file(src: str, dest: str)— mueve un archivo nombrado.destes la ruta de destino completa, no una carpeta. Un destino que ya existe se rechaza, nunca se sobrescribe y nunca se renombra alrededor. Obedece solo las reglas de PROTECTION — ver "El modelo de dos capas" más arriba.undo_last_action()— revierte el movimiento o plan aplicado más reciente, como una sola acción. Nada se sobrescribe para hacer espacio para un archivo restaurado.redo_last_action()— vuelve a aplicar la acción deshecha más reciente. La pila de rehacer se limpia cada vez que se registra un nuevo trabajo.
Códigos de resultado que este servidor puede devolver adicionalmente: NEEDS_EXPLICIT_REQUEST (cumplió todos los invariantes de seguridad, rechazado solo porque actuar sin que se lo pidan sería una conjetura — nombra el objetivo directamente y pídelo).
Un archivo, policy.toml, leído por ambos servidores:
audit_log = "audit.jsonl" # shared
audit_fail_mode = "closed" # shared: "closed" or "open"
[redaction] # shared
enabled = true
entropy_fallback = false
extra_patterns = [] # [{ name = "...", regex = "..." }]
[terminal]
# jail_root = "/srv/safe-mcp/sandbox" # REQUIRED — here or via env
[terminal.limits]
timeout_seconds = 30
max_output_bytes = 65536
[terminal.allowlist]
commands = ["ls", "cat", "echo", "pwd", "git"]
[terminal.denylist]
commands = ["rm", "shutdown", "reboot", "curl", "wget", "chmod", "sudo"]
[[terminal.rules]]
command = "git"
deny_args = ["push --force", "push -f"]
reason = "force-push rewrites shared history"
[files]
# workspace_root = "/srv/safe-mcp/inbox" # REQUIRED — here or via env
journal = "organizer-journal.json"
max_plan_moves = 500
[files.categories]
Documents = [".pdf", ".doc", ".docx", "..."]
# ...
[[files.skip]]
layer = "protection" # or "restraint" — required, no default
when = ["unsafe-name"]
reason = "..."El archivo de política en sí no tiene ubicación predeterminada. Apúntalo con --config:
safe-mcp terminal --config /path/to/policy.toml
safe-mcp files --config /path/to/policy.tomlLas variables de entorno siguen siendo compatibles y cada una anula la clave correspondiente de policy.toml. SAFE_MCP_POLICY_FILE es la única excepción que vale la pena mencionar: es una alternativa a --config, no una anulación de este — --config gana si se dan ambos, y el inicio se niega si no se da ninguno.
Variable | Significado | Valor por defecto |
| Ruta a | ninguna — se niega a iniciar |
| Directorio de la jaula del terminal (obligatorio, aquí o | ninguna — se niega a iniciar |
| Directorio del espacio de trabajo de archivos (obligatorio, aquí o | ninguna — se niega a iniciar |
| Ruta del diario de deshacer/rehacer (debe estar fuera del espacio de trabajo) |
|
| Ruta del registro de auditoría compartido (debe estar fuera de ambas jaulas) |
|
|
|
|
El inicio falla de forma evidente — con un mensaje fatal: impreso y una salida distinta de cero — si no se proporciona ninguna ruta de archivo de política (ni --config ni SAFE_MCP_POLICY_FILE), si policy.toml falta o no es válido, si la raíz de la jaula/espacio de trabajo no está definida o no es un directorio, si la expresión regular de redacción del operador no es válida, si hay una entrada [[files.skip]] sin etiquetar, o si el registro de auditoría / diario se encuentra dentro de una jaula que el servidor podría entonces mover o falsificar.
Advertencias honestas
Esta es una capa de endurecimiento, no una caja fuerte. Léelas antes de desplegar cualquiera de los dos servidores.
La jaula es una comprobación de contención de rutas, no un sandbox del kernel. No hay namespaces, cgroups ni seccomp. Una explotación del kernel o una vía de escape accesible desde un binario en la lista de permitidos no está contenida.
El registro de auditoría evidencia manipulaciones, no es a prueba de manipulaciones, y no tiene rotación. Solo añadido con vaciado por registro + fsync significa que no perderá registros por un fallo, pero cualquiera con acceso al sistema de archivos del host a
audit.jsonlpuede leerlo, modificarlo o eliminarlo — y el archivo crece sin límite; no hay rotación ni política de retención integrada.La redacción se basa en patrones y es de mejor esfuerzo. Detecta formas comunes de secretos; un formato novedoso o inusual pasa sin redactar. El respaldo opcional de entropía está desactivado por defecto porque es ruidoso con los SHA de git, los UUID y los datos base64, no porque sea débil.
Los metacaracteres del terminal se rechazan incluso entre comillas — un límite conocido.
echo "a;b"se rechaza aunque el;sea inerte dentro de las comillas, porque el escaneo es una comprobación bruta de subcadenas sin conocimiento de las comillas. Esa es la dirección segura en la que equivocarse — no hay truco de comillas que haga pasar a un operador por un escaneo que ignora las comillas en primer lugar — pero sí significa que se rechaza alguna entrada legítima.TOCTOU: una ruta validada y luego utilizada puede cambiar en el intervalo. Tanto
safety/paths.pycomoservers/files/apply.pycomprueban la contención o la ocupación y luego actúan en una syscall separada; un enlace simbólico intercambiado o un archivo creado en ese intervalo no está cubierto. Documentado en el código como un límite deliberado y nombrado (comentarios# NOTE:en ambos archivos), con una ruta de actualización (O_NOFOLLOWmás operaciones relativas a dir-fd) anotada por si alguna vez se necesita.Los procesos nietos no se recolectan. Los procesos hijos de un comando eliminado o con tiempo agotado no están en un grupo de procesos separado; el ejecutor mata solo al hijo directo, por lo que cualquier cosa que ese comando haya generado puede sobrevivirle.
Trabajo relacionado
hardened-terminal-mcp — el predecesor independiente del servidor de terminal de este conjunto.
MCP-file-organizer — el predecesor independiente del servidor de archivos de este conjunto.
Licencia
MIT — ver LICENSE.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Connectors
- Parallel Task MCPOAuth
An MCP server for deep research or task groups
Run commands and read/write files on your servers over Termalin's keyless tunnels (hosted MCP).
A MCP server built for developers enabling Git based project management with project and personal…
An MCP server that provides read access to your cloud storage providers, bank accounts and more.
Related MCP Servers
- AlicenseNot gradedqualityAmaintenanceA secure MCP server for shell operations, terminal management, and process control, enabling AI assistants to safely execute commands and manage interactive sessions.2046MIT
- FlicenseNot gradedqualityCmaintenanceA secure, controlled terminal MCP server that enables executing whitelisted shell commands safely with multiple security layers.
- AlicenseNot gradedqualityCmaintenanceA production-ready MCP server for secure, session-based command execution, file manipulation, and system inspection via local terminal sessions.14ISC
- AlicenseAqualityAmaintenanceSecurity-hardened MCP server that runs only allowlisted commands with no shell, jailed to a single directory, and bounded execution.2MIT
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/Asaad-Suliman/safe-mcp-suite'
If you have feedback or need assistance with the MCP directory API, please join our Discord server