Seguridad¶
Novedades en v1.6.0
- La API key del sandbox: en Odoo 14.0 a 19.0,
scripts/deploy_local.pyhace que Odoo cree una API key para eladmindel sandbox y la guarda, sin imprimirla, en el.envdel sandbox. Ver Infraestructura local. - Las revisiones de copias y de opciones (
--check-copiesy--check-options) no corren ningún programa, no contactan nada y nunca imprimen un valor sensible, contenga lo que contenga la entrada. Ver Credenciales. - El límite conocido 10 se cierra, salvo un caso: en Odoo 10 a 12, una clave de contexto con el nombre de un campo del modelo se lee como un valor. Ver Política de permisos.
Novedades en v1.5.1
- Un hook de inicio de sesión que no lee ninguna opción del plugin ni
credencial:
hooks/session_start.pyreemplaza ahooks/check_options.py. Ver Credenciales. - El servidor solo lee las credenciales que le pasan las opciones del
plugin: ya no lee
ODOO_HTTP_BEARER. - Diagnóstico local de solo metadatos, revisado contra una lista permitida. Ver Privacidad.
- Qué reportan las revisiones del directorio de plugins, y dónde verificar cada hallazgo.
- Un límite conocido nuevo de la política: un contexto pasado por posición en Odoo 10 a 12 (ver Política de permisos).
Novedades en v1.5.0
- Política de permisos encima de los derechos del usuario
de Odoo, con el perfil
standardpor defecto. Ver Política de permisos abajo. - Registro de auditoría local, encadenado por hash, sin valores ni secretos.
- Sesiones por identidad completa: la clave de sesión incluye un HMAC del secreto por proceso, y se quitó el camino multi-tenant por cabeceras.
odoo_connectionsya no devuelve eluser:pass@de la URL.- El hook de opciones revisa también las dos opciones de la política.
Credenciales¶
- Como plugin, las credenciales son opciones del plugin (
/plugin→ odoo-tools → Configure options). La API key y la contraseña son opcionessensitive: se guardan en el almacén seguro de credenciales del sistema operativo, nunca ensettings.jsonni en tu perfil de shell, y solo se pasan al proceso local del servidor MCP, que solo las envía a la URL de Odoo que configuraste. No exportesODOO_*en tu shell para el plugin. - El plugin tiene dos hooks
SessionStart, y ninguno contacta nada:- el primero corre
python3 --versiony muestra un aviso solo cuandopython3no se puede ejecutar (el servidor MCP no podría arrancar); - el segundo,
hooks/session_start.py(nuevo en v1.5.1, reemplaza ahooks/check_options.py), arranca solo cuandopython3es 3.9 o superior y no lee ninguna opción del plugin ni credencial. Lee soloCLAUDE_PLUGIN_DATAyCLAUDE_CODE_ENTRYPOINT(el nombre de la app de Claude que inició la sesión), el origen y el id de sesión del evento, el final del diagnóstico del servidor (diagnostics/server.jsonly su archivo rotadoserver.jsonl.1), si existe la marca del modo contenedor, sus marcas de tiempo diarias y la versión del plugin: nada fuera de las carpetas del plugin. Cuando los últimos 14 días no registran ni una conexión exitosa a Odoo ni un arranque del servidor con todos los datos de conexión, dice dónde corren las herramientas de Odoo (no en Cowork ni en Claude chat), como máximo una vez al día al iniciar sesión, y se lo explica a Claude con una línea de diagnóstico para el doctor (en la primera sesión tras instalar o actualizar el plugin, y donde no tiene carpeta de datos, solo a Claude); cuando el último arranque del servidor quedó bloqueado por la política de permisos, lo dice con su código E. Escribe una línea de metadatos por sesión endiagnostics/hook.jsonl(nunca a través de un enlace simbólico), guarda dos archivos de marca de tiempo para los avisos diarios y nunca detiene la sesión (siempre sale con 0). Con el modo contenedor de/odoo-tools:deploy-localno dice nada.
- el primero corre
- Claude Code exporta cada opción guardada del plugin, incluidas las
sensibles, al entorno de cada proceso de hook del plugin como
CLAUDE_PLUGIN_OPTION_*. Los hooks de este plugin no leen ninguna (lo compruebaserver/tests/test_product_rules.py). - Las revisiones de copias y de opciones (
run_stdio.py --check-copiesy--check-options, que la ruta guiada y/odoo-tools:odoo-doctorcorren en Claude Code) leen en su entrada estándar el JSON que imprime un comando de Claude Code (claude plugin list --jsonoclaude plugin configure <id> --json): no corren ningún programa, no abren ningún archivo de Claude Code, no contactan nada y nunca imprimen un valor sensible, contenga lo que contenga la entrada. De la URL de Odoo, la revisión de opciones solo repite el esquema, el host y el puerto, nunca una ruta, una consulta ni un fragmento, que pueden llevar el token de un enlace de portal, y rechaza una URL con cualquier@como usuario o contraseña, sin mostrar ninguna parte de ella. Las dos leen UTF-8, también desde una tubería de Windows. Ver Dos copias de odoo-tools. - En modo standalone (Docker,
python -m odoo_mcp), el servidor lee las credenciales del entorno (ODOO_URL,ODOO_DB,ODOO_LOGIN,ODOO_API_KEY/ODOO_PASSWORD). El servidor nunca toma una URL ni un secreto de Odoo de una petición del cliente: el camino multi-tenant por cabeceras se quitó en la v1.5.0, yODOO_MULTITENANTse lee pero queda reservada. El servidor no lee ninguna otra credencial:ODOO_HTTP_BEARER, que leía de su entorno pero nunca usaba, se quitó en la v1.5.1. - El servidor MCP no persiste secretos en disco ni los escribe en logs. Las
sesiones se identifican por
url|db|loginmás un HMAC del secreto calculado por proceso, así que la misma URL, base y login con otro secreto nunca reutiliza una sesión autenticada; el secreto mismo nunca forma parte de una clave ni de una línea de log. - El objeto
Settingsredacta las credenciales enrepr()/str()para que un log accidental no las filtre (garantizado portests/test_security_redaction.py). - Prefiere API keys (Odoo ≥ 14) sobre contraseñas. Usa usuarios de Odoo de mínimo privilegio y activa Read-only mode en las opciones del plugin para exploración o demos.
- Qué datos se procesan y a qué servicios se contacta: ver Privacidad.
La CLI de respaldo sí escribe un archivo con credenciales
A diferencia del servidor MCP, la CLI de respaldo
(/odoo-tools:odoo-setup-cli) guarda tus credenciales en un archivo .env
local en ~/.claude/tools/odoo-cli. La configuración lo crea legible solo
por tu usuario y con marcadores de posición: tú pegas el secreto en tu propio
editor; Claude nunca te lo pide en el chat. Si usas esa superficie: conserva
esos permisos restrictivos (equivalentes a chmod 600), no lo subas a
control de versiones, y elimínalo cuando dejes de usar la CLI. Rota la API key
con la misma cadencia que aplicarías a cualquier otro secreto guardado en disco.
Usuario dedicado para el agente
Crea un usuario de Odoo específico para el agente (por ejemplo
ai-bot@tuempresa.com) con solo los permisos que necesita. Genera la API key
desde ese usuario y rótala periódicamente. Así acotas el radio de daño si la
key se filtra.
Qué pasa con los datos de tu empresa¶
Conviene distinguir dos rutas de datos:
- Del servidor MCP a Odoo (directo). El servidor corre en tu infraestructura y habla directamente con tu instancia Odoo por JSON-2 / JSON-RPC / XML-RPC. Transgenia no actúa de intermediario: no hacemos de proxy, no almacenamos ni vemos tus datos ni tus credenciales.
- Del agente de IA al proveedor del modelo. Cuando operas Odoo a través de un agente (Claude Code u otro cliente MCP), los resultados de cada herramienta (los registros de Odoo que consultas) se devuelven al cliente y se envían al proveedor del modelo (por ejemplo Anthropic, en el caso de Claude) para que el modelo los procese. Esto es inherente a cualquier flujo con agentes de IA; no es algo que agregue este plugin.
Implicaciones prácticas: tú decides qué consultas y con qué usuario de Odoo (mínimo privilegio). Revisa las políticas de tratamiento de datos de tu proveedor de modelo y evita traer campos sensibles que el agente no necesita. Para datos regulados, consulta solo lo imprescindible y considera anonimizar en origen.
Cadena de suministro¶
- Cero dependencias en tiempo de ejecución. Desde la v1.2.0, el servidor MCP
usa solo la biblioteca estándar de Python (el protocolo MCP y los transportes
JSON-2, JSON-RPC y XML-RPC están implementados sobre ella). El plugin lo lanza con
python3 ${CLAUDE_PLUGIN_ROOT}/server/run_stdio.py: sin lanzador de paquetes (uv,uvx,npx), sin paso de instalación y sin descargas al arrancar. El código que corre es exactamente el código legible incluido en la carpeta del plugin. run_stdio.pypone primero ensys.pathel código incluido, de modo que otro paqueteodoo_mcpinstalado en el equipo no puede sustituir el código revisado.- Los extras opcionales (
cache,metrics,otel) solo se usan si tú los instalas (modo standalone); el plugin no instala ninguno. - La CLI de respaldo opcional es la única pieza que instala paquetes: corre
npm installal configurarla con/odoo-tools:odoo-setup-cli.
Qué reportan las revisiones del directorio de plugins¶
El directorio de plugins de Claude lista estos hallazgos para este plugin. Cada uno, con su nombre en inglés como lo muestra el directorio, y dónde verificarlo:
- Uses a credential from the user's machine
(
.claude-plugin/plugin.json,server/src/odoo_mcp/config.py). El servidor MCP lee dos credenciales, la API key y la contraseña de Odoo, del entorno que.claude-plugin/plugin.jsonllena desde las opcionessensitiveodoo_api_keyyodoo_password. Solo las envía a la URL de Odoo que configuraste, el servicio al que pertenecen: los tres transportes deserver/src/odoo_mcp/transport/se conectan solo a esa URL y rechazan las redirecciones.server/tests/test_product_rules.pyfalla si el servidor lee cualquier otra credencial de su entorno. scripts/deploy_local.pygenera contraseñas aleatorias (secrets.token_urlsafe) para el sandbox Docker local de/odoo-tools:deploy-local, las guarda en un.envsolo para tu usuario en la carpeta del sandbox y las usa solo con los contenedores de ese sandbox en127.0.0.1. No son tus credenciales. En Odoo 14.0 y posteriores también hace que el Odoo del sandbox cree una API key para su usuario administrador (odoo shellen un contenedor de un solo uso del sandbox, con el propiores.users.apikeysde Odoo), comprueba que la key inicia sesión y la guarda en el mismo.envcomoODOO_API_KEY, sin imprimirla nunca. Tú la copias en la opción Odoo API key del plugin; Claude nunca la lee.- Pruebas (
server/tests/): usan credenciales falsas como testigo para demostrar que ninguna línea de log, de auditoría o de diagnóstico, ningún reporte de soporte y ninguna salida de hook contienen una credencial. El plugin nunca las corre. - Uses hooks. Los dos hooks
SessionStartde Credenciales:hooks/session_start.pylee solo la carpeta de datos del plugin yCLAUDE_CODE_ENTRYPOINT, nunca una opción del plugin ni una credencial (lo compruebaserver/tests/test_product_rules.py), y siempre sale con 0; el otro solo correpython3 --version. - Runs a local program for an MCP server.
python3 ${CLAUDE_PLUGIN_ROOT}/server/run_stdio.py: solo la biblioteca estándar, sin instalar ni descargar nada (ver Cadena de suministro). - Contains a download-and-run command. Solo dos comandos descargan y corren
código en tu equipo, y solo cuando tú los invocas y confirmas:
/odoo-tools:deploy-localdescarga y corre imágenes de Docker (copias sin cambios de las imágenes oficiales de PostgreSQL y Odoo, por etiqueta de serie, y la imagen del servidor MCP en la versión del plugin), y/odoo-tools:odoo-setup-clicorrenpm installpara la CLI opcional. El servidor MCP y los hooks no descargan ni instalan nada.
Chequeos automáticos (CI)¶
El flujo .github/workflows/security.yml corre en cada push/PR:
- gitleaks: escanea historia y diffs en busca de secretos commiteados. Se
compila con
go installen una etiqueta fija (v8.18.4, con cada módulo verificado contra la base de checksums de Go), sin descargar y ejecutar binarios concurl. - leak-guard: busca marcadores internos o de tenant prohibidos (datos de clientes, hosts de infraestructura, identificadores fiscales, credenciales hardcodeadas) y hace fallar el build si aparecen. El repositorio es vendor-neutral: solo contiene el tooling genérico más la marca/autoría pública y la oferta de servicios. Los marcadores específicos de la organización se leen de un secreto del repositorio en lugar de estar escritos en el flujo (la carpeta del plugin es la raíz del repositorio, así que el flujo llega a cada instalación), y sus coincidencias se reportan solo por nombre de archivo.
.gitignore: excluye.env,*.env, entornos virtuales y salida de build para que las credenciales locales no se commiteen por accidente.
Transporte¶
- Usa URLs de Odoo con HTTPS. JSON-2, JSON-RPC y XML-RPC corren sobre la URL
que proporciones; una URL
http://se envía sin cifrar. - JSON-2 (Odoo saas~18.4 / 19.0+) envía la API key como
Authorization: beareren cada petición, nunca en una redirección (las redirecciones se rechazan), no envía cookies y nunca muestra en un resultado de herramienta la traza del servidor de Odoo (el campodebugde un error). Solo acepta API keys, así que una configuración con contraseña se queda en los endpoints clásicos. - Todos los transportes verifican el certificado TLS (incluido el nombre del
host) con el almacén de confianza del sistema (respetan
SSL_CERT_FILE; si el paquete opcionalcertifiestá instalado, también usan su lista de CA). No hay opción para desactivar la verificación. Todos aplicanODOO_TIMEOUT(120 s por defecto; opción del plugin Request timeout). - JSON-RPC no sigue redirecciones HTTP (un POST redirigido se convertiría en un GET sin cuerpo); configura la URL final de tu Odoo.
- Autenticación básica delante de Odoo:
ODOO_URLpuede llevaruser:pass@; JSON-RPC la envía como cabeceraAuthorizationy ningún error ni log ni resultado de herramienta repite la contraseña (desde la v1.5.0,odoo_connectionsquita eluser:pass@de la URL que reporta). JSON-2 necesita esa misma cabecera para su API key, así que una URL así se queda en los endpoints clásicos. Odoo URL no es una opciónsensitive: esa contraseña se guarda en la configuración del plugin, no en el almacén seguro de credenciales. - El servidor registra logs solo en stderr; stdout está reservado al canal MCP.
Registro de auditoría¶
Desde la v1.5.0, cada llamada a herramienta, rechazos incluidos, deja una línea
JSON: la herramienta, los nombres de modelo y operación que tocó
(op:modelo[:método]), la capa y la regla de un rechazo, conteos, la duración, el
nombre de la clase de excepción de un error y el digest de la política.
Nunca guarda valores de argumentos, dominios, valores de campos, ids,
contenido de archivos, la URL, tokens, llaves, contraseñas ni textos de
mensajes de error; un nombre que no pasa la revisión de nombres de la política se
escribe como ?. La base de datos y el login de Odoo aparecen solo en la línea
start que se escribe al arrancar el servidor.
- El plugin escribe archivos diarios (
odoo-tools-audit-AAAA-MM-DD.jsonl) en su carpeta de datos (ODOO_AUDIT_DIR), solo para tu usuario (carpeta0700, archivos0600, nunca a través de un enlace simbólico), y los conserva 90 días (ODOO_AUDIT_RETENTION_DAYS;0los conserva). - Sin carpeta, las líneas van al logger
odoo_mcp.auditen stderr;ODOO_AUDIT_DIR=offno escribe ninguna. - Cada línea lleva un hash de la anterior, y
python3 server/run_stdio.py --verify-audit ARCHIVOnombra la primera línea editada, reordenada o insertada, o una última línea incompleta. Con un solo archivo no puede detectar líneas completas quitadas del final de un día ni un archivo de un día borrado (el prefijo que sobrevive sigue encadenado). En local esto es evidencia, no prevención: quien puede escribir la carpeta puede reescribir los archivos. - Un fallo al escribir nunca rompe una llamada a herramienta.
Detalle y formato en Registro de auditoría.
Política de permisos¶
En el modo local (stdio), la política de permisos limita lo que Claude puede hacer a través de las herramientas MCP de odoo-tools. Protege contra errores y contra instrucciones escondidas en los datos que Claude lee. No limita a la persona que tiene la API key, y no es un sandbox para un agente con acceso sin restricciones a la shell del mismo equipo. La frontera que resiste frente a todos es el propio usuario de Odoo: dale al agente un usuario de Odoo dedicado y de mínimo privilegio (no un administrador) y una API key con caducidad. En un futuro modo remoto, donde el servidor guarde la credencial de Odoo, la misma política se volverá una aplicación real de permisos.
- Qué es. Un techo encima de los derechos de acceso, las reglas de registro y
los grupos de campos de Odoo, fijado por las opciones Permission profile
(
readonly,standardpor defecto,builder,full) y un Permission policy file opcional, leído una vez al arrancar desde una ruta absoluta explícita (nunca desde una ubicación por defecto). Nunca concede lo que Odoo rechaza y nunca es más débil que Read-only mode.fullconserva el comportamiento de la 1.4. Referencia: Política de permisos. - Qué cierra
standard. Los secretos guardados (parámetros del sistema, credenciales de servidores de correo, LDAP y proveedores de pago, tokens de IAP y de sincronización bancaria, certificados y llaves CFDI, la cola de correo saliente) no se pueden leer; usuarios, grupos, reglas de acceso, módulos, cuentas bancarias, fusiones de contactos, historial del chatter y secuencias fiscales quedan en solo lectura; los modelos que envían correo, SMS, cartas o enlaces para compartir, y los métodos de salida (message_postsalvo notas internas,message_subscribe,send_mail, etc.), quedan cerrados; las acciones de servidor, automatizaciones, vistas y otros registrosir.*quedan en solo lectura; los borrados se rechazan; los campos con aspecto de credencial se retiran de las lecturas y se rechazan en los argumentos.fullsigue rechazando crear, cambiar o revocar API keys y desinstalar módulos y, para hacerlo cumplir, una llamada cuyos argumentos o modelos relacionados la política no puede revisar. - Dos capas. Los accesos declarados de cada herramienta se deciden con sus argumentos antes de que exista una sesión (capa A: nada llega a Odoo). Luego cada llamada a Odoo que hace la herramienta pasa por una sesión vigilada (capa B) que la revisa otra vez con sus valores y rutas (comandos x2many, rutas de dominio y de exportación, el padre de un adjunto, el correo de contacto de un usuario) antes de enviarla, y detiene como un error cualquier llamada que la herramienta no declaró. Una herramienta sin mapeo de permisos no se puede registrar, listar ni llamar.
- Una política inválida bloquea el servidor: solo funciona
odoo_version, que explica el error sin contactar a Odoo. - Cinturón de seguridad, no sandbox. En stdio, un agente con una shell
sin restricciones puede leer el entorno del servidor, editar el archivo de
política o las opciones del plugin, o llamar a Odoo directamente. Las reglas
permissions.denyde Claude Code sobre la carpeta de la política, las confirmaciones de Bash y la línea de instrucciones (un rechazo de la política es definitivo) lo hacen más difícil, no imposible.odoo_versionadvierte cuando el usuario de Odoo es administrador, porque entonces Odoo no acota nada. - Límites conocidos (detallados en
Límites conocidos): los efectos dentro de los
métodos de negocio (un correo de confirmación, las automatizaciones existentes)
son de Odoo; las cadenas que devuelven los métodos (una URL de portal con un
token) no se redactan; los modelos puntero distintos de
ir.attachmentimportan solo cuando un archivo rechaza lecturas de negocio; los campos relacionados con nombres inofensivos pueden llevar datos retenidos; los enlaces many2one no se revisan como toques; el contenido publicado es un canal de salida por su efecto; los conteos y agregados permiten inferir;builderequivale afullen confidencialidad, porque las automatizaciones corren código en el servidor; en Odoo 10 a 12, una clave de un objeto pasado por posición a un método de negocio fuera de la tabla de la política que es el nombre de un campo del modelo se lee como un valor, así que no se somete a la lista de claves de contexto permitidas.
Modo solo lectura¶
- Con Read-only mode (
ODOO_READONLY=1) el servidor ya no lista las herramientas que no son de solo lectura (odoo_create,odoo_write,odoo_unlink,odoo_execute,odoo_translate_set, las dos herramientas Studio-style y las dos de importación: tambiénodoo_import_preview, porque su prueba revertida puede consumir números de secuencia y disparar webhooks), y las rechaza con un error claro antes de tocar Odoo cuando se llaman por su nombre. Las lecturas, exportaciones,odoo_telemetry_previewy las herramientas Online de solo lectura siguen funcionando. Ningún perfil ni archivo de política puede devolver una escritura mientras está activo. - La CLI de respaldo no tiene un equivalente, ni una política de permisos: su
comando
writesiempre intenta la llamada. Usa ahí un usuario de Odoo de mínimo privilegio. odoo_online_profilelista las API keys del usuario solo por nombre, alcance y fechas: Odoo no guarda el material de la key en un campo legible, y el plugin nunca crea, rota ni revoca keys.odoo_record_documentsdevuelve un archivo solo si es adjunto del registro pedido y mide como máximomax_bytes(máximo 5 MiB).
Infraestructura local (/odoo-tools:deploy-local)¶
- Solo corre cuando lo invocas y confirmas; ningún hook ejecuta Docker.
- Odoo se publica solo en
127.0.0.1; no queda expuesto a tu red. - Las contraseñas se generan localmente con
secretsde Python, se guardan en~/.odoo-tools/sandbox/.env(0600, carpeta0700) y nunca se imprimen; Claude no lee ese archivo. - En Odoo 14.0 y posteriores, la API key que Odoo crea para el
admindel sandbox se guarda en ese mismo archivo comoODOO_API_KEYy tampoco se imprime, ni siquiera en un mensaje de error. Tú la copias en la opción Odoo API key del plugin; nunca la pegues en el chat. Odoo la lista como "odoo-tools sandbox" en Preferencias > Account Security; si la quitas ahí, el siguientesandbox upcrea otra. - Las imágenes vienen de
ghcr.io/transgenia(copias sin cambios de las oficiales, actualizadas cada semana) con respaldo a Docker Hub. - La imagen
ghcr.io/transgenia/odoo-mcp-toolscorre como usuario sin privilegios y recibe las credenciales por--env-file, nunca en la línea de comandos.
Reportar una vulnerabilidad¶
Escribe a dev@transgenia.org con detalles y pasos de reproducción. Por favor no abras issues públicos para reportes de seguridad. Buscamos acusar recibo en un plazo de 5 días hábiles.