RAG multi-tenant seguro: cómo filtrar permisos antes de recuperar contexto

Un filtro de metadata no es seguridad si lo compone el navegador o se aplica después del vector search. En un RAG multi-tenant, la identidad autenticada debe decidir qué chunks existen para cada consulta antes de que el modelo los vea.

Compartir
RAG multi-tenant seguro: cómo filtrar permisos antes de recuperar contexto

La keyword principal es `RAG multi-tenant`; la intención es implementar un chat de conocimiento que no entregue a un usuario chunks, citas o respuestas basadas en documentos de otro tenant, equipo o nivel de acceso.

El orden correcto es identidad verificada → política → filtro de retrieval → ranking → contexto → modelo. Si recuperas top-50 global, filtras top-5 al final y solo entonces construyes el prompt, ya has expuesto datos a una capa que puede registrar trazas, caché, métricas o errores.

Flujo de RAG multi-tenant: identidad autenticada, política, filtro previo al índice vectorial, contexto permitido, respuesta con citas y auditoría; un documento no autorizado queda denegado
La frontera de permisos está antes del índice. El modelo no decide qué documento puede leer una persona.

Checklist

Deriva el filtro en backend

El frontend puede enviar la pregunta, no el ámbito. Tras validar sesión o token, el backend obtiene tenant, grupos, rol y atributos de sensibilidad de una fuente controlada. Luego crea un filtro cerrado: tenant exacto AND clasificación permitida AND grupo incluido AND documento no revocado.

No aceptes un JSON arbitrario tipo `filters` del navegador y lo combines con un `AND tenant_id = ...` optimista. El atacante puede pedir otro tenant, añadir un OR, provocar un fallback sin filtro o explotar una ruta de búsqueda distinta que olvidaste proteger. El contrato del retriever debería recibir un objeto de identidad, no un filtro libre.

La misma política debe proteger sugerencias, búsqueda por título, previsualizaciones, descargas, caches semánticas, herramientas de agente y links de cita. Proteger solo `retrieve()` deja una API lateral con la que inferir nombres o contenido de documentos restringidos.

Ejemplo con Postgres, pgvector y RLS

¿Te está sirviendo? Hay una dosis cada semana

Te resumo herramientas de IA para devs, agentes, MCP, seguridad y workflows en un email de 5 minutos. En español y sin ruido.

Suscribirme gratis

Postgres permite que la base aplique Row Level Security aunque alguien añada una ruta de aplicación defectuosa. Usa una cuenta de aplicación que no sea owner de la tabla, activa RLS y fuerza RLS para que el owner tampoco la eluda accidentalmente. La variable de sesión se fija dentro de una transacción, desde claims que el servidor validó.

Puntos a revisar

Lo que conviene comprobar

schema.sql
CREATE TABLE rag_chunks (id uuid PRIMARY KEY, tenant_id uuid NOT NULL, classification text NOT NULL, content text NOT NULL, embedding vector(1536), revoked_at timestamptz); ALTER TABLE rag_chunks ENABLE ROW LEVEL SECURITY; ALTER TABLE rag_chunks FORCE ROW LEVEL SECURITY; CREATE POLICY tenant_can_read_chunks ON rag_chunks FOR SELECT USING (tenant_id = current_setting('app.tenant_id', true)::uuid AND revoked_at IS NULL); SELECT set_config('app.tenant_id', :verified_tenant_id, true); SELECT id, content FROM rag_chunks WHERE classification = ANY(:allowed_classifications) ORDER BY embedding <=> :query_embedding LIMIT 12;

Esto no reemplaza una política de grupos más rica, pero ilustra la propiedad importante: el `tenant_id` no sale de la pregunta ni del navegador, y el filtro se ejecuta en la misma consulta que calcula vecinos. Comprueba además que el pool limpia configuración de sesión al devolver la conexión; una identidad heredada es otra fuga entre requests.

Índice compartido, colección por tenant o aislamiento físico

Un índice compartido con filtros es eficiente cuando los tenants son numerosos, el motor ejecuta el filtro antes de devolver candidatos y puedes medir el coste de ACL de alta cardinalidad. Es la opción normal para SaaS, pero requiere una disciplina fuerte de ingesta, filtros obligatorios y tests negativos.

Una colección o namespace por tenant reduce el radio de blast y simplifica algunas consultas, a costa de operaciones, shards pequeños y migraciones. Úsalo cuando cada tenant tenga volumen suficiente o un modelo de retención distinto. Para clientes regulados o muy sensibles, una cuenta, índice o clave de cifrado dedicada puede ser una decisión de producto válida.

No elijas según la facilidad de la demo. Elige según dónde puedes demostrar aislamiento: permisos por chunk, backup y restauración, borrado, logs, caché, soporte operativo y capacidad de revocar acceso sin re-embeddar todo el corpus.

Reranking ocurre después del filtro. Si necesitas diversidad por documento o un mínimo de citas, aplícalo dentro del conjunto permitido. La mejora de relevancia nunca puede reintroducir un candidato que la política excluyó.

Pruebas de aislamiento que deben bloquear un despliegue

Crea dos tenants con documentos deliberadamente similares y una tercera identidad sin acceso. Comprueba que cada consulta, variante lingüística y prompt injection solo devuelve citas del tenant correcto. Incluye documentos de ACL vacía, grupos revocados, clasificación caducada y errores del proveedor de identidad; el comportamiento seguro es denegar o devolver cero contexto.

Prueba todas las superficies: retrieval directo, API de chat, streaming, historial, cache, búsqueda de archivos, enlaces citados, herramientas MCP y tareas en background. Haz también una prueba de conexión reutilizada para confirmar que no conserva `app.tenant_id` ni un cache key de otro usuario.

Registra request ID, política/version de ACL, número de candidatos antes y después del filtro, ids de chunks permitidos hasheados y decisión final. No guardes el prompt y los chunks íntegros por defecto: una traza de seguridad que se convierte en repositorio de datos sensibles no es observabilidad responsable.

Preguntas frecuentes

¿Un metadata filter hace seguro mi RAG?

Solo si la aplicación construye el filtro desde identidad verificada, el motor lo aplica antes de recuperar candidatos y todas las rutas usan la misma política. Un filtro enviado por el cliente no es un control de acceso.

¿Debo usar un índice por tenant?

No siempre. Un índice compartido con pre-filtros y pruebas de aislamiento puede escalar bien. Separa colecciones o infraestructura cuando el aislamiento, la retención, el volumen o el riesgo lo justifiquen y puedas operarlo mejor.

¿Puedo filtrar después de buscar los vectores?

No para seguridad. Filtrar después puede exponer candidatos a caches, logs o un modelo, y además puede inducir fallbacks inseguros. Filtra antes de ranking y contexto.

¿RLS sustituye las ACL del índice vectorial?

RLS es una defensa excelente cuando retrieval pasa por tu base, pero no protege automáticamente un vector DB o servicio gestionado externo. Usa la frontera que realmente ejecuta la consulta y añade defensas independientes cuando puedas.

¿Qué pasa si no hay documentos autorizados?

Devuelve una negativa clara o pide permisos. Nunca amplíes la búsqueda global ni inventes contexto para hacer la respuesta más útil.

¿Prompt injection puede saltarse la ACL?

No debería, porque la ACL se decide antes de entregar contexto al modelo. Aun así, trata el contenido recuperado como no confiable y protege las herramientas que el agente pueda llamar después.

Cómo implementar un primer RAG multi-tenant seguro

  1. Modelar acceso. Define tenant, usuarios, grupos, clasificación, procedencia y qué significa una revocación antes de elegir el vector store.
  2. Etiquetar en ingesta. Asigna metadata de tenant y ACL a cada chunk en una fuente controlada, registra versión y rechaza documentos sin propietario conocido.
  3. Autenticar en backend. Valida token o sesión y deriva claims en servidor; nunca aceptes el tenant o filtro de permisos desde el navegador.
  4. Construir el pre-filtro. Genera una política cerrada de tenant, grupos, clasificación y revocación que el retriever aplique antes de vector o keyword search.
  5. Añadir defensa de datos. Activa RLS o el control nativo del índice y usa una credencial de runtime que no pueda saltarse la política.
  6. Controlar frescura. Sincroniza cambios de permisos, define un SLA y bloquea recuperación de una revocación urgente mientras reindexas.
  7. Evaluar aislamiento. Crea corpus gemelos entre tenants y casos negativos para cada API, caché, herramienta y conexión reutilizada.
  8. Observar sin filtrar. Registra decisiones y contadores redactados, alerta por filtros ausentes y conserva evidencia suficiente para investigar sin copiar el contexto sensible.

Fuentes y referencias

También te puede interesar

Búsqueda híbrida RAG: BM25, vectores y rerankingEvaluación RAG en producción: métricas y datasetsMemoria de agentes de IA: privacidad y borradoPrompt injection en agentes de IA: defensas y evalsOpenTelemetry GenAI: observar agentes sin filtrar datos

Recibe una lectura semanal de herramientas IA para devs

Cada semana te resumo herramientas de IA para devs, agentes, MCP, seguridad y workflows en un email de 5 minutos. En español y sin ruido.

Suscribirme gratis

Lo mejor de la IA para desarrolladores, cada martes

Newsletter en español, gratis. Las herramientas, modelos y trucos de IA para devs que de verdad importan — sin ruido.