El directorio había desaparecido antes de que nadie leyera la línea de log que explicaba por qué.
Un agente que ejecutábamos sobre un repositorio de prueba había recibido la orden de "limpiar los artefactos de compilación". Razonó hasta llegar a rm -rf con una ruta que ensambló a partir de una variable que, dos turnos antes, se había resuelto silenciosamente a una cadena vacía. rm -rf / es la versión de manual de este chiste. La real es más silenciosa: rm -rf "$BUILD_DIR/" donde $BUILD_DIR es "", y ahora estás borrando desde el directorio de trabajo hacia arriba. Tuvimos suerte: era un clon desechable, y la peor baja fue una tarde. Pero he visto suficientes transcripciones de agentes como para conocer la diferencia entre "esto no nos hizo daño" y "esto no puede hacernos daño", y un modelo con un shell crudo está firmemente en la primera categoría.
Ese incidente es la razón entera por la que me importa cómo un agente toca un disco. La lección no es "los modelos son peligrosos". Los modelos no son maliciosos; se equivocan con total confianza, y equivocarse con confianza teniendo rm -rf a mano es un problema de seguridad, haya o no intención de daño. La solución es la misma que hemos usado para código no confiable durante cincuenta años: no le entregues la máquina entera. Dale una interfaz estrecha, tipada y observable, acota lo que esa interfaz puede alcanzar y haz que las operaciones peligrosas sean o bien imposibles o bien ruidosas.
Esa interfaz, para los archivos, es un servidor MCP de sistema de archivos. Esta entrada trata sobre Octofs — el que construimos y ejecutamos — desde un ángulo de seguridad: qué restringe de verdad, qué deliberadamente no restringe y cómo configurarlo para que el próximo rm -rf simplemente no tenga nada que agarrar.
El modelo de amenazas: qué concede realmente un shell crudo
Empieza por ser honesto sobre lo que estás cediendo cuando le das a un agente bash.
Un shell no es una "herramienta de archivos". Es un lanzador de programas arbitrarios con toda la autoridad ambiental del proceso que lo generó: tu cuenta de usuario, tus variables de entorno, tu agente SSH, tus credenciales en la nube esperando en ~/.aws, tu kubeconfig, cada volumen montado, el árbol entero desde / hacia abajo. Cuando un modelo emite curl ... | sh, el shell lo ejecuta. Cuando emite rm -rf contra una ruta que alucinó, el shell la borra. Cuando ejecuta git push contra el remoto equivocado, el shell empuja. El trabajo del shell es hacer exactamente lo que se le dice, al instante, sin ninguna noción de "¿de verdad querías eso?".
Superpón los modos de fallo del modelo sobre esa autoridad y obtienes un conjunto concreto y recurrente de incidentes:
- Deriva de rutas. El agente construye una ruta a partir de una variable, de un resultado de herramienta previo o de su propia memoria del repositorio. La variable está vacía, el resultado fue truncado, la memoria está obsoleta, y la ruta ahora apunta a algún sitio no deseado. Al shell le da igual.
- Verbos destructivos sobre rutas equivocadas.
rm -rf, unmvsobre un archivo existente, truncamiento con> file,git checkout -- .borrando trabajo sin confirmar. Cada uno está a un token de la versión benigna. - Leer lo que no debería. Un
cat ~/.ssh/id_ed25519o un volcado deenvque arrastra secretos a la ventana de contexto, donde se registran, se cachean y posiblemente se envían a una API de modelo. La exfiltración no necesita malicia; necesita un agente que "amablemente" incluya el archivo equivocado. - Bloqueos interactivos. Un comando que pide una contraseña o abre un paginador, sin TTY que lo conteste, y la sesión se atasca.
- Procesos huérfanos. Un servidor en segundo plano que el agente lanzó y olvidó, todavía ocupando un puerto después de que termine la sesión.
Ninguno de estos requiere un jailbreak. Son el comportamiento esperado de un sistema probabilístico cableado directamente a uno imperativo. La mitigación es arquitectónica: reemplaza el lanzador abierto por un conjunto cerrado de operaciones con nombre, y acota la superficie que esas operaciones pueden alcanzar.
Por qué una herramienta de archivos acotada le gana a bash crudo
El argumento a favor de un servidor MCP de sistema de archivos dedicado frente al acceso a shell crudo es el mismo que hicimos para mantener las herramientas personalizadas en tu repositorio: una herramienta estrecha y con nombre le da al modelo menos formas de equivocarse y te da a ti un lugar donde imponer reglas.
En concreto, cambiar bash por una herramienta de archivos cambia cuatro cosas a la vez:
- Los verbos son finitos y con nombre. En lugar de "cualquier programa en el PATH", el modelo ve
view,text_editor,batch_edit,extract_lines,shell,workdir. Cada uno tiene un esquema. No hay ninguna operación detext_editorque signifique "borra recursivamente un árbol", porque esa operación nunca se definió. No puedes pegarte un tiro en el pie con un arma que no está en el cajón. - La superficie por defecto es tu proyecto, no tu directorio personal. Octofs toma una raíz de trabajo y resuelve las rutas relativas contra ella. El modo natural de operar del agente — "edita
src/main.rs" — nunca sale de esa raíz. - Las lecturas están filtradas. El recorrido de directorios respeta
.gitignorey omite los dotfiles por defecto, así que.env,.ssh,node_modulesy todo lo que ya le dijiste a git que ignorara no aparecen en listados ni en búsquedas de contenido. El agente no lee lo que no puede ver. - Cada operación es observable y reversible. Las ediciones pasan por escrituras atómicas con hasta diez niveles de deshacer por archivo. No existe el estado "el archivo está a medio escribir y el proceso murió", y hay un
undo_editpara "esa edición estuvo mal".
El shell no desaparece — los agentes genuinamente necesitan ejecutar compilaciones y pruebas — pero deja de ser la interfaz de archivos principal, y eso es casi toda la batalla. Cuantas menos operaciones de archivos pasen por un shell crudo, menos oportunidades tiene el modelo de derivar una ruta hacia algo destructivo.
Qué expone Octofs realmente
Seis herramientas MCP, y esa es la superficie entera. Conocer la lista exacta importa, porque el argumento de seguridad es "estas y nada más".
| Herramienta | Qué hace | Por qué es más segura que el equivalente en shell |
|---|---|---|
view |
Leer archivos (uno solo, con rangos de líneas), listar directorios con patrones glob, buscar en el contenido de archivos | Respeta gitignore y filtra dotfiles — reemplaza cat/grep/find/ls, sin exponer rutas ignoradas |
text_editor |
create, str_replace, delete, undo_edit sobre un archivo |
Escrituras atómicas, historial de deshacer, coincidencia difusa en lugar de corrupción silenciosa con sed |
batch_edit |
Múltiples operaciones de inserción/reemplazo en un archivo, aplicadas de forma atómica | La detección de conflictos rechaza ediciones solapadas o ambiguas antes de tocar el disco |
extract_lines |
Copiar un rango de líneas de un archivo y anexarlo a otro | Una copia acotada y tipada en lugar de un pipe hecho a mano con head/tail |
shell |
Ejecutar un comando, en primer plano o en segundo plano | Refuerzo de no interactividad, limpieza de grupos de procesos, avisos de uso indebido |
workdir |
Obtener, fijar o restablecer el directorio de trabajo de la sesión | Hace el alcance explícito e inspeccionable en lugar de implícito en un cd |
Dos flags de configuración dan forma a todo el conjunto, fijados cuando lanzas el servidor:
# acotar la raíz de trabajo y usar IDs de línea estables basados en hash
octofs mcp --path /path/to/your/project --line-mode hash
--path fija la raíz de trabajo. --line-mode hash cambia los identificadores de línea de números secuenciales a hashes de 4 caracteres derivados del contenido que se mantienen estables entre ediciones — una característica de fiabilidad, pero también adyacente a la seguridad, porque las referencias estables significan menos errores del tipo "edité la línea equivocada porque los números se desplazaron". También existe --bind host:port para ejecutar sobre Streamable HTTP en lugar de stdio cuando necesitas una configuración remota o multicliente.
En una configuración de Claude Desktop, Cursor o Windsurf se ve así:
{
"mcpServers": {
"octofs": {
"command": "/path/to/octofs",
"args": ["mcp", "--path", "/path/to/your/project", "--line-mode", "hash"]
}
}
}
Ese --path es la decisión de seguridad más importante que tomas con esta herramienta. Es la diferencia entre "el agente opera sobre este proyecto" y "el agente opera sobre mi portátil". Fíjalo en el directorio más estrecho que la tarea necesite.
Las protecciones, ordenadas por la frecuencia con que te salvan
Esto es lo que Octofs impone realmente, fundamentado en el código y no en el folleto.
Raíz de trabajo acotada
Las rutas relativas se resuelven contra la raíz de trabajo que pasas a --path (o el directorio actual si no lo pasas). El agente que dice view src/main.rs resuelve a <root>/src/main.rs. El modelo mental entero del agente sobre "el proyecto" queda anclado a esa raíz, y mientras trabaje con rutas relativas — que es su comportamiento por defecto — nunca sale.
Recorrido que respeta gitignore y filtra dotfiles
El listado de directorios y la búsqueda de contenido usan el walker del crate ignore con git_ignore(true) y los archivos ocultos excluidos por defecto. El efecto práctico: cuando el agente lista un directorio o busca una cadena, no ve .git/, .env, .ssh/, salida de compilación, node_modules ni nada en tu .gitignore. Los secretos que ya excluiste del control de versiones también quedan excluidos de la vista del agente. Esta es la protección más barata y más infravalorada — el agente no puede filtrar un archivo cuya existencia nunca conoció.
Escrituras atómicas con historial de deshacer
Cada edición escribe en un archivo temporal en el mismo directorio y luego lo renombra sobre el destino — una operación atómica a nivel de sistema de archivos. El archivo nunca se observa a medio escribir; si el proceso muere a mitad de la edición, el original queda intacto. Los permisos originales del archivo se preservan a lo largo de la escritura (un arreglo que llegó en 0.4.2, específicamente para que una escritura no amplíe silenciosamente el modo de un archivo). Antes de cada escritura, el contenido anterior se guarda como instantánea, hasta diez niveles de profundidad por archivo, accesible a través del undo_edit de text_editor. "El agente rompió el archivo" deja de ser una operación de recuperación y se convierte en una llamada de herramienta.
Detección de conflictos y duplicados por lotes
batch_edit valida cada operación contra todas las demás antes de escribir. Los rangos de reemplazo solapados se rechazan. Dos inserciones en el mismo anclaje se rechazan por ambiguas. El lote entero tiene un tope de 50 operaciones. Y cada reemplazo se comprueba contra la duplicación de límites — el fallo clásico en el que el modelo incluye una línea extra de "contexto" y la duplica silenciosamente en el archivo. Nada de esto toca el disco hasta que el plan es internamente consistente.
Refuerzo de shell no interactivo
Cuando el agente sí usa shell, los comandos se ejecutan con stdin puesto a null, en su propio grupo de procesos, con GIT_TERMINAL_PROMPT=0, DEBIAN_FRONTEND=noninteractive, PAGER=cat y GIT_PAGER=cat. Un comando que pediría una credencial falla rápido en lugar de atascar la sesión. Un comando que paginaría la salida la vuelca de forma plana en lugar de invocar less contra un TTY inexistente. La sesión no puede atascarse en un aviso interactivo que el modelo no puede contestar.
Limpieza de grupos de procesos al apagar
Los comandos en primer plano se generan en su propio grupo de procesos y se rastrean por PID. Ante SIGTERM o EOF, Octofs envía SIGKILL a cada grupo rastreado — incluyendo nietos — así que nada que un comando dejara a medio correr sobrevive a la sesión. Los comandos en segundo plano (background: true) son la excepción deliberada: se separan precisamente para poder sobrevivir a la llamada, Octofs le entrega al agente su PID, y matarlos cuando el trabajo termina es tarea del agente — kill <pid>.
Avisos de uso indebido del shell
Este es un empujón, no un muro, pero moldea el comportamiento a lo largo de una sesión. Cuando el agente ejecuta cat, grep, find, ls, sed o awk a través de shell, Octofs anexa un aviso a la respuesta dirigiéndolo hacia la herramienta dedicada: usa view para leer o buscar, usa text_editor para editar. A lo largo de una sesión el agente aprende a encaminar las operaciones de archivos por las herramientas que tienen la red de seguridad, en lugar del shell que no la tiene. Cuantos menos verbos destructivos fluyan por la interfaz abierta, mejor.
Lo que Octofs no hace — y por qué te lo cuento
Una entrada de seguridad que solo enumere protecciones es marketing. Aquí está el límite, dicho claramente, porque configurar a su alrededor correctamente depende de saber dónde está.
Octofs no es una jaula chroot. La raíz de trabajo acota las rutas relativas. No confina las rutas absolutas. resolve_path tiene dos ramas: una ruta relativa se une a la raíz de trabajo; una ruta absoluta se usa tal cual. No hay rechazo de recorrido con .. ni comprobación de que la ruta resuelta permanezca bajo la raíz. Si el modelo emite view /etc/passwd o text_editor contra /Users/you/.ssh/config, Octofs resolverá esa ruta absoluta y operará sobre ella. El alcance es un default fuerte y una convención fuerte — el agente que trabaja con rutas relativas nunca abandona el proyecto — pero no es una frontera endurecida contra una ruta que deliberadamente o accidentalmente se vuelve absoluta.
Este es un punto de diseño deliberado, no un descuido: Octofs es una herramienta de productividad para un agente cooperativo, no una capa de contención para uno hostil. Pero cambia cómo deberías desplegarlo cuando el radio de destrucción importa:
- Ejecútalo contra un directorio dedicado, no tu hogar. El
--pathmás estrecho que puedas permitirte. Si la tarea solo necesita un repo, apúntalo a ese repo. - Para aislamiento estricto, usa el OS. Una herramienta de archivos no puede vencer al kernel en su propio terreno de sandboxing. Si genuinamente no confías en lo que el agente podría hacer — código no confiable, un bucle autónomo, un runner de CI — ejecuta Octofs y el agente dentro de un contenedor, una VM o una cuenta de usuario que físicamente no pueda ver tus secretos, con solo el directorio del proyecto montado. Entonces la brecha de rutas absolutas es irrelevante, porque no hay nada fuera del montaje a lo que llegar. Defensa en profundidad: la herramienta acotada reduce el caso común; el sandbox del OS atrapa la ruta que se vuelve absoluta.
- Mantén los secretos fuera de la raíz de trabajo, y fuera de git. Como el recorrido respeta gitignore, los secretos que ya hayas ignorado en git permanecen invisibles para listados y búsqueda. Esa es una reducción real de exposición accidental — aprópiate de ella. Una credencial que el agente no pueda enumerar es una credencial que no puede pegar en una ventana de contexto.
El resumen honesto en una línea: Octofs elimina los errores accidentales — el rm -rf con variable vacía, el sed corrompido en silencio, el archivo a medio escribir, el paginador colgado — y reduce la superficie por defecto. No contiene, por sí solo, a un agente determinado o profundamente confundido que construya una ruta absoluta. Empárelo con un sandbox del OS cuando necesites esa garantía.
Configurarlo de forma estricta: una lista de verificación
Si quieres la máxima seguridad que esta herramienta puede ofrecerte, configúrala así:
- Fija
--pathal directorio de proyecto más estrecho que la tarea necesite. No tu hogar, no/, no un padre que contenga cinco repos más. Un proyecto, una raíz. - Ignora tus secretos en git antes de apuntar un agente al repo.
.env,*.pem, archivos de credenciales, configuración local. Se vuelven invisibles para listados y búsquedas deview. Es gratis y funciona. - Usa
--line-mode hashpara cualquier agente de edición multipaso. Las referencias de línea estables significan menos errores de "edité la línea equivocada", lo cual es fiabilidad y seguridad a la vez. - Prefiere las herramientas tipadas sobre
shellen las instrucciones de tu agente. Los avisos de uso indebido empujan en esta dirección; refuérzalo. Cada archivo leído a través deviewen lugar decates uno que respeta las reglas de ignore. - Controla las operaciones destructivas en la capa de permisos de tu runtime de agente. Octofs expone las herramientas; tu runtime decide cuáles necesitan aprobación. Autoaprueba
view. Pregunta siempre parashellydeletedetext_editor. Aquí es donde el humano permanece en el bucle para los verbos que no se pueden deshacer conundo_edit. - Cuando el radio de destrucción sea real, mete todo en un contenedor. Monta solo el directorio del proyecto. Ahora la brecha de rutas absolutas está cerrada por el kernel, no por la esperanza.
El cambio de mentalidad es el punto. No estás confiando en que el modelo sea cuidadoso. Estás organizando el mundo para que la descuido tenga un radio de destrucción pequeño — una raíz acotada, lecturas filtradas, escrituras reversibles, un shell no interactivo y un directorio de trabajo explícito.
Preguntas frecuentes
¿Octofs impide que un agente ejecute rm -rf?
No directamente — shell aún ejecuta comandos arbitrarios, incluyendo rm. Lo que cambia Octofs es que la edición y lectura de archivos ya no necesitan el shell, así que se generan muchos menos comandos destructivos de shell en primer lugar; y la raíz de trabajo más el recorrido consciente de gitignore hacen que las operaciones naturales del agente se mantengan dentro del proyecto. Para una garantía estricta de que rm -rf no pueda alcanzar nada importante, ejecuta Octofs dentro de un contenedor con solo el proyecto montado.
¿La raíz de trabajo es un sandbox?
Es un default acotado, no una jaula. Las rutas relativas se resuelven contra la raíz; las rutas absolutas se usan tal cual, sin rechazo de ... Trata --path como "dónde trabaja el agente", no "a qué está confinado el agente". Para confinamiento, usa un sandbox a nivel de OS.
¿Cómo evita que los secretos filtren al contexto?
El listado de directorios y la búsqueda de contenido son conscientes de .gitignore y omiten dotfiles por defecto. Los archivos que ya excluiste de git — .env, archivos de claves, cualquier cosa en .gitignore — no aparecen en listados ni búsquedas, así que el agente no puede leerlos en el contexto. La defensa es tan buena como tu .gitignore, así que curátelo antes de apuntar un agente al repo.
¿Qué pasa con un archivo a medio escribir si el proceso se bloquea durante la edición?
No puede pasar. Las escrituras van a un archivo temporal en el mismo directorio y se renombran sobre el destino de forma atómica. Si el proceso muere, el original está intacto. Los permisos se preservan a lo largo de la escritura.
¿Por qué seis herramientas en lugar de simplemente exponer el sistema de archivos?
Un conjunto finito y nombrado de operaciones es la característica de seguridad. El modelo no puede buscar un verbo "borra-árbol" que nunca se definió, y obtienes un único lugar — el límite de la herramienta — para hacer cumplir escrituras atómicas, deshacer, detección de conflictos y reglas de ignore. Herramientas más pocas y más estrechas también son más fáciles de seleccionar correctamente para el modelo.
¿Funciona fuera de Octomind?
Sí. Octofs habla MCP sobre stdio o Streamable HTTP y funciona con cualquier cliente MCP — Claude Desktop, Cursor, Windsurf, Zed — además del runtime de agente de Octomind.
El agente que casi borró ese directorio de prueba aún se ejecuta en nuestra configuración todos los días. El modelo no es más cuidadoso de lo que era. Simplemente tiene mucho menos que romper.
Lo que cambió es el mundo que lo rodea: lee a través de view, edita a través de text_editor, trabaja dentro de un --path acotado a un repo, y cuando las apuestas son reales, todo se sienta dentro de un contenedor que no puede ver nada más.
Dale a un agente acceso a archivos. Solo no le des los tuyos.
— Don
Octofs es código abierto bajo Apache-2.0 — un único binario en Rust, versión actual 0.5.0. El alcance, el recorrido consciente de gitignore, las escrituras atómicas y el shell no interactivo descritos aquí están todos en el código fuente; la advertencia sobre rutas absolutas también. Si necesitas una protección que no está ahí, abre un issue.



