Aislamiento entre clientes: las tres formas de hacerlo y lo que cuesta cada una
El fallo de seguridad más frecuente en un SaaS multi-tenant tiene poco que ver con la inyección SQL. El identificador de la organización viaja en un parámetro que el usuario controla y el servidor no comprueba si le pertenece. Se llama IDOR, se arregla comprobando pertenencia en cada consulta y aparece en casi todas las auditorías de productos jóvenes.
Las tres estrategias
Antes de hablar de fallos, la decisión de partida. Hay tres formas de separar los datos de tus clientes y se eligen por criterios distintos.
| Estrategia | Aislamiento | Coste de operar | Cuándo |
|---|---|---|---|
Una base, tenant_id en cada tabla | Lógico | Bajo | Por defecto. De decenas a miles de clientes |
| Un esquema por cliente | Medio | Medio-alto | Exigencia contractual de separación o clientes muy desiguales |
| Una base por cliente | Fuerte | Alto | Sector regulado o pocos clientes muy grandes |
La primera es la correcta para casi todo el mundo y la que usa Vecinly. Las otras dos se eligen cuando alguien las exige por escrito. Parecer más seguras no es motivo.
Lo que cuesta cada una, en concreto
Con tenant_id, el coste está en la disciplina: cada consulta tiene que filtrar y basta que
una no lo haga para que un cliente vea datos de otro. El precio es vigilancia permanente.
Con un esquema por cliente, el coste está en las migraciones. Cambiar una columna deja de ser una operación y pasa a ser cuarenta, con cuarenta oportunidades de que una falle a medias. A los dos años hay esquemas desincronizados y encontrarlos es un trabajo de tarde.
Con una base por cliente, el coste está en todo lo demás: cuarenta copias de seguridad, cuarenta actualizaciones de versión, cuarenta juegos de credenciales, cuarenta objetivos de monitorización. Se paga en tiempo de operación cada semana, para siempre.
Nadie cuenta esta parte cuando recomienda «una base por cliente, que es más seguro».
Con 40 clientes, la diferencia práctica no está en la seguridad: está en si una migración es una operación o son cuarenta.
Por qué la comprobación en la interfaz no cuenta
El intento ingenuo y el que más a menudo llega a producción dentro de un prototipo:
La interfaz solo muestra al usuario las organizaciones a las que pertenece. El desplegable está bien construido. El menú es correcto. Y el endpoint que devuelve los datos acepta cualquier identificador que le llegue.
Cambiar un número en la barra de direcciones no requiere ninguna habilidad. Es lo primero que prueba cualquiera con curiosidad y lo primero que prueba una auditoría.
La regla es corta: la interfaz decide qué se enseña, el servidor decide qué se puede. Y «el servidor» significa la propia consulta. Una comprobación al principio de la función es algo que alguien olvidará añadir en el endpoint número treinta.
El patrón: que el filtro no dependa de acordarse
La forma de resolverlo de verdad es que el aislamiento deje de estar en manos de quien escribe cada consulta. Dos capas y hacen falta las dos.
Capa 1 · La pertenencia se resuelve en el servidor
El identificador de organización se deriva de la sesión y de la pertenencia comprobada. El parámetro que manda el cliente se ignora. Si un usuario pertenece a dos organizaciones, la elección vive en la sesión y la URL no tiene nada que decir.
Suena obvio escrito así. Deja de serlo cuando el endpoint lo escribe otra persona seis meses después.
Capa 2 · La política vive en la base de datos
En Postgres, eso es RLS (row level security): una política por tabla que filtra las filas
según una variable de sesión. Una consulta a la que se le olvide el WHERE deja de devolver datos
ajenos y pasa a devolver cero filas.
Es defensa en profundidad de la buena. El error se sigue cometiendo y no tiene consecuencias.
La interfaz decide qué se enseña. El servidor decide qué se puede, y la base de datos lo vuelve a comprobar.
Y tiene su coste. La política se evalúa por fila, así que hay que indexar pensando en ella. Un índice que funcionaba bien sin RLS puede dejar de usarse cuando la política entra en juego y el plan de ejecución cambia sin que nadie toque la consulta. Si activas RLS, revisa los planes de las consultas calientes después de activarla.
Lo que sigue fallando aunque hagas las dos capas
Con RLS activa y pertenencia comprobada, quedan estos:
- Los trabajos en segundo plano. Corren sin sesión de usuario, así que suelen ejecutarse con un rol que se salta las políticas. Es la puerta de atrás más común.
- Los informes y exportaciones. Casi siempre se escriben con consultas propias más optimizadas y a menudo con conexión distinta.
- La caché. Guardar un resultado sin el identificador de organización en la clave sirve datos de un cliente a otro y encima de forma intermitente, que es lo peor para diagnosticarlo.
- Los identificadores secuenciales. No es un fallo de aislamiento, pero regala información:
con
factura/1042cualquiera sabe cuántas facturas has emitido. Identificadores opacos. - Los ficheros. Las políticas de la base de datos no cubren el almacenamiento de objetos. Una URL de descarga adivinable salta todo lo anterior.
- El correo. Un aviso que se envía al destinatario equivocado es una brecha, aunque la base de datos esté perfecta.
El cliente ruidoso
Un problema de aislamiento que no es de seguridad y que aparece más tarde: un solo cliente con un volumen anómalo degrada a todos los demás.
Suele llegar sin avisar. Un cliente sube diez mil registros de golpe o programa una integración que consulta cada treinta segundos. De pronto el producto va lento para los otros treinta y nueve, que no han hecho nada.
Se maneja con medidas que van por orden de esfuerzo: primero límites de tasa por organización, después colas separadas para el trabajo pesado y, al final, un presupuesto de consulta que corte lo que se pase. Ninguna hace falta el primer día. Todas hacen falta antes de lo que se cree.
Cuándo esto es sobreingeniería
Si tu producto tiene un cliente, todavía no tienes un problema multi-tenant. Montar RLS, límites por organización y colas separadas para un producto con tres usuarios internos es trabajo que no compra nada.
El umbral práctico: cuando dos organizaciones distintas que no se conocen entre sí van a tener datos en el mismo sistema. A partir de ahí, la capa 1 es obligatoria desde el primer día, porque reescribir la pertenencia después toca todos los endpoints. La capa 2 puede esperar a que haya algo que proteger.
Lo que no puede esperar nunca es la decisión de estrategia. Cambiar de tenant_id a esquema por
cliente con datos dentro es una migración larga y con riesgo real. Se elige al principio y se
elige la primera salvo que alguien te obligue por contrato a otra cosa.
Vecinly es un SaaS multi-tenant en producción, con roles finos, cobros recurrentes y datos de comunidades que no pueden verse entre sí. Está construido sobre Scaleway (Francia) y se puede ver funcionando en su ficha de caso. Si tienes un prototipo que va a recibir su primer cliente de pago, SaaS a medida explica cómo se lleva a producción.