KoderKoder.ai
PreciosEmpresasEducaciónPara inversores
Iniciar sesiónComenzar

Producto

PreciosEmpresasPara inversores

Recursos

ContáctanosSoporteEducaciónBlog

Legal

Política de privacidadTérminos de usoSeguridadPolítica de uso aceptableReportar abuso

Social

LinkedInTwitter
Koder.ai
Idioma

© 2026 Koder.ai. Todos los derechos reservados.

Inicio›Blog›Seguridad a nivel de fila (RLS) de PostgreSQL para SaaS: políticas que funcionan
14 oct 2025·8 min

Seguridad a nivel de fila (RLS) de PostgreSQL para SaaS: políticas que funcionan

La seguridad a nivel de fila (RLS) de PostgreSQL ayuda a aplicar el aislamiento por tenant en la base de datos. Aprende cuándo usarla, cómo escribir políticas y qué evitar.

Seguridad a nivel de fila (RLS) de PostgreSQL para SaaS: políticas que funcionan

El problema real que RLS intenta resolver en apps SaaS

En una app SaaS, el bug de seguridad más peligroso es el que aparece cuando escalas. Empiezas con una regla simple como “los usuarios solo pueden ver los datos de su tenant”, luego publicas rápido un nuevo endpoint, añades una consulta para reporting o introduces un join que silenciosamente omite la comprobación.

La autorización solo en la app falla bajo presión porque las reglas acaban dispersas. Un controlador comprueba tenant_id, otro la membresía, un job en background se olvida y una ruta de “export admin” se queda “temporal” durante meses. Incluso equipos cuidadosos se olvidan de un punto.

La seguridad a nivel de fila (RLS) de PostgreSQL resuelve un problema específico: hace que la base de datos aplique qué filas son visibles para una petición dada. El modelo mental es simple: cada SELECT, UPDATE y DELETE se filtra automáticamente por las políticas, como cada petición se filtra por el middleware de autenticación.

La parte de “filas” importa. RLS no protege todo:

  • No oculta columnas específicas por sí sola (usa vistas o privilegios de columna).
  • No vuelve seguras funciones inseguras (una función aún puede filtrar datos si corre con privilegios elevados).
  • No valida reglas de negocio (por ejemplo, “solo los propietarios pueden cambiar ajustes de facturación”).

Un ejemplo concreto: añades un endpoint que lista proyectos con un join a facturas para un dashboard. Con autorización solo en la app, es fácil filtrar projects por tenant y olvidar filtrar invoices, o unir por una clave que cruza tenants. Con RLS, ambas tablas pueden hacer cumplir el aislamiento por tenant, así la consulta falla de forma segura en vez de filtrar datos.

La compensación es real. Escribes menos código de autorización repetido y reduces los lugares donde puede haber fugas. Pero también asumes trabajo nuevo: debes diseñar políticas con cuidado, probarlas pronto y aceptar que una política puede bloquear una consulta que esperabas que funcionara.

Cuándo RLS simplifica la autorización (y cuándo añade dolor)

RLS puede parecer trabajo extra hasta que tu app supera unas pocas rutas. Si tienes límites de tenant estrictos y muchas vías de consulta (pantallas de listado, búsqueda, exports, herramientas admin), poner la regla en la base de datos significa que no tienes que recordar añadir el mismo filtro en todas partes.

RLS encaja bien cuando la regla es aburrida y universal: “un usuario solo puede ver filas de su tenant” o “un usuario solo puede ver proyectos de los que es miembro”. En esos casos, las políticas reducen errores porque cada SELECT, UPDATE y DELETE pasa por la misma puerta, incluso cuando se añade una consulta después.

También ayuda en apps con carga de lectura donde la lógica de filtrado se mantiene consistente. Si tu API tiene 15 maneras diferentes de cargar facturas (por estado, por fecha, por cliente, por búsqueda), RLS te permite dejar de reimplementar el filtrado por tenant en cada consulta y centrarte en la funcionalidad.

RLS añade dolor cuando las reglas no son por fila. Reglas por campo como “puedes ver el salario pero no el bonus” o “enmascara esta columna a menos que seas RR.HH.” a menudo se convierten en SQL incómodo y excepciones difíciles de mantener.

Tampoco encaja bien para reporting pesado que realmente necesita acceso amplio. Los equipos suelen crear roles de bypass para “solo este job”, y ahí es donde se acumulan los errores.

Antes de comprometerte, decide si quieres que la base de datos sea la puerta final. Si la respuesta es sí, planifica disciplina: prueba el comportamiento de la base de datos (no solo las respuestas de la API), trata las migraciones como cambios de seguridad, evita bypasses rápidos, decide cómo se autentican los jobs en background y mantén las políticas pequeñas y repetibles.

Si usas herramientas que generan backends, pueden acelerar la entrega, pero no quitan la necesidad de roles claros, tests y un modelo de tenant simple. (Por ejemplo, Koder.ai usa Go y PostgreSQL para backends generados, y aún quieres diseñar RLS deliberadamente en vez de “esparcirlo después”.)

Conceptos de modelo de datos que hacen manejables las políticas RLS

RLS es más sencillo cuando tu esquema ya dice claramente quién posee qué. Si empiezas con un modelo difuso y tratas de “arreglarlo con políticas”, normalmente obtienes queries lentas y bugs confusos.

Pon una clave de tenant donde corresponda

Elige una clave de tenant (como org_id) y úsala de forma consistente. La mayoría de las tablas propiedad del tenant deberían tenerla, incluso si además referencian otra tabla que la tenga. Esto evita joins dentro de las políticas y mantiene simples las comprobaciones USING.

Una regla práctica: si una fila debería desaparecer cuando un cliente cancela, probablemente necesita org_id.

Modela membresías de forma explícita

Las políticas RLS suelen responder a una pregunta: “¿Es este usuario miembro de esta org, y qué puede hacer?” Eso es difícil de inferir desde columnas ad hoc.

Mantén las tablas centrales pequeñas y aburridas:

  • users (una fila por persona)
  • orgs (una fila por tenant)
  • org_memberships (user_id, org_id, role, status)
  • opcional: project_memberships para acceso por proyecto

Con eso, tus políticas pueden comprobar la membresía con una sola búsqueda indexada.

Separa datos de referencia compartidos de datos propiedad del tenant

No todo necesita org_id. Tablas de referencia como países, categorías de producto o tipos de plan suelen ser compartidas entre tenants. Hazlas de solo lectura para la mayoría de roles y no las ates a una org.

Los datos propiedad del tenant (proyectos, facturas, tickets) deberían evitar traer detalles de tenant a través de tablas compartidas. Mantén las tablas compartidas mínimas y estables.

FKs, cascadas e indexación

Las foreign keys siguen funcionando con RLS, pero los deletes pueden sorprender si el rol que borra no puede “ver” las filas dependientes. Planifica cascadas con cuidado y prueba flujos reales de borrado.

Indexa las columnas que filtran tus políticas, especialmente org_id y las claves de membresía. Una política que se lee como WHERE org_id = ... no debería convertirse en un escaneo de tabla completa cuando la tabla llega a millones de filas.

Cómo funcionan las políticas RLS, sin las partes aterradoras

RLS es un interruptor por tabla. Una vez habilitado, PostgreSQL deja de confiar en que tu código recuerde el filtro de tenant. Cada SELECT, UPDATE y DELETE se filtra por políticas, y cada INSERT y UPDATE se valida por políticas.

El mayor cambio mental: con RLS activado, consultas que antes devolvían datos pueden empezar a devolver cero filas sin errores. Eso es PostgreSQL aplicando control de acceso.

Qué hacen realmente las políticas

Las políticas son reglas pequeñas adjuntas a una tabla. Usan dos comprobaciones:

  • USING es el filtro de lectura. Si una fila no cumple USING, es invisible para SELECT y no puede ser objetivo de UPDATE o DELETE.
  • WITH CHECK es la puerta de escritura. Decide qué filas nuevas o modificadas están permitidas para INSERT o UPDATE.

Un patrón común en SaaS: USING asegura que solo ves filas de tu tenant, y WITH CHECK asegura que no puedes insertar una fila en el tenant de otro adivinando un tenant_id.

PERMISSIVE vs RESTRICTIVE, en una frase

Cuando añades más políticas después, esto importa:

  • PERMISSIVE (por defecto): una fila se permite si cualquier política la permite.
  • RESTRICTIVE: una fila se permite solo si todas las políticas restrictivas lo permiten (además del comportamiento permisivo).

Si planeas apilar reglas como coincidencia de tenant más comprobaciones de rol más membresía de proyecto, las políticas restrictivas pueden dejar la intención más clara, pero también facilitan que te quedes fuera si olvidas una condición.

Cómo sabe Postgres quién es el usuario

RLS necesita un valor fiable de “quién llama”. Opciones comunes:

  • Una variable de sesión por petición (por ejemplo, app.user_id y app.tenant_id).
  • Claims de JWT mapeadas a settings de sesión por tu capa API.
  • Cambio de rol (SET ROLE ... por petición), que puede funcionar pero añade overhead operacional.

Elige un enfoque y aplícalo en todas partes. Mezclar fuentes de identidad entre servicios es un camino rápido a bugs confusos.

Nombrar políticas para poder debuggear luego

Usa una convención predecible para que los dumps de esquema y logs sigan siendo legibles. Por ejemplo: {tabla}__{acción}__{regla}, como projects__select__tenant_match.

Paso a paso: añade RLS a una tabla y verifica que funciona

Prueba RLS en staging cuanto antes
Despliega tu app y verifica el comportamiento de RLS en un entorno similar a producción.
Desplegar app

Si eres nuevo en RLS, empieza con una tabla y una pequeña prueba. El objetivo no es cobertura perfecta. El objetivo es que la base de datos se niegue a permitir acceso entre tenants incluso si hay un bug en la app.

Una tabla pequeña para practicar

Asume una tabla simple projects. Primero, añade tenant_id de una forma que no rompa escrituras.

ALTER TABLE projects ADD COLUMN tenant_id uuid;

-- Backfill existing rows (example: everyone belongs to a default tenant)
UPDATE projects SET tenant_id = '11111111-1111-1111-1111-111111111111'::uuid
WHERE tenant_id IS NULL;

ALTER TABLE projects ALTER COLUMN tenant_id SET NOT NULL;

A continuación, separa la propiedad del acceso. Un patrón común es: un rol posee las tablas (app_owner), otro rol lo usa la API (app_user). El rol de la API no debe ser el owner de la tabla, o puede eludir las políticas.

ALTER TABLE projects OWNER TO app_owner;
REVOKE ALL ON projects FROM PUBLIC;
GRANT SELECT, INSERT, UPDATE, DELETE ON projects TO app_user;

Ahora decide cómo la petición le dice a Postgres qué tenant está sirviendo. Un enfoque simple es un setting de alcance de petición. Tu app lo establece justo después de abrir una transacción.

-- inside the same transaction as the request
SELECT set_config('app.current_tenant', '22222222-2222-2222-2222-222222222222', true);

Habilita RLS y comienza con acceso de lectura.

ALTER TABLE projects ENABLE ROW LEVEL SECURITY;

CREATE POLICY projects_tenant_select
ON projects
FOR SELECT
TO app_user
USING (tenant_id = current_setting('app.current_tenant')::uuid);

Verifícalo probando dos tenants distintos y comprobando que el conteo de filas cambia.

Añade reglas de escritura (WITH CHECK)

Las políticas de lectura no protegen escrituras. Añade WITH CHECK para que inserts y updates no puedan colar filas en el tenant equivocado.

CREATE POLICY projects_tenant_write
ON projects
FOR INSERT, UPDATE
TO app_user
WITH CHECK (tenant_id = current_setting('app.current_tenant')::uuid);

Una forma rápida de verificar comportamiento (incluidos fallos) es mantener un pequeño script SQL que puedas volver a ejecutar después de cada migración:

  • BEGIN; SET LOCAL ROLE app_user;
  • SELECT set_config('app.current_tenant', '\u003ctenant A\u003e', true); SELECT count(*) FROM projects;
  • INSERT INTO projects(id, tenant_id, name) VALUES (gen_random_uuid(), '\u003ctenant B\u003e', 'bad'); (debe fallar)
  • UPDATE projects SET tenant_id = '\u003ctenant B\u003e' WHERE ...; (debe fallar)
  • ROLLBACK;

Si puedes ejecutar ese script y obtener los mismos resultados cada vez, tienes una línea base fiable antes de expandir RLS a otras tablas.

Patrones de política que reutilizarás en la mayoría de apps SaaS

La mayoría de equipos adoptan RLS cuando se cansan de repetir los mismos chequeos de autorización en cada consulta. La buena noticia es que las formas de política que necesitas suelen ser consistentes.

Filas de propietario vs filas de membresía

Algunas tablas son naturalmente propiedad de un usuario (notas, tokens API). Otras pertenecen a un tenant donde el acceso depende de la membresía. Trata estos como patrones distintos.

Para datos solo del propietario, las políticas suelen comprobar created_by = app_user_id(). Para datos de tenant, las políticas suelen comprobar si el usuario tiene una fila de membresía para la org.

Una forma práctica de mantener legibles las políticas es centralizar la identidad en pequeños helpers SQL y reutilizarlos:

-- Example helpers
create function app_user_id() returns uuid
language sql stable as $$
  select current_setting('app.user_id', true)::uuid
$$;

create function app_is_admin() returns boolean
language sql stable as $$
  select current_setting('app.is_admin', true) = 'true'
$$;

Separa reglas de lectura de reglas de escritura

Las lecturas suelen ser más amplias que las escrituras. Por ejemplo, cualquier miembro de la org puede SELECT proyectos, pero solo los editores pueden UPDATE, y solo los propietarios pueden DELETE.

Manténlo explícito: una política para SELECT (membresía), una para INSERT/UPDATE con WITH CHECK (rol) y una para DELETE (a menudo más estricta que update).

Override de admin sin desactivar RLS

Evita “apagar RLS para admins”. En su lugar, añade una salida de emergencia dentro de las políticas, como app_is_admin(), para que no concedas accidentalmente acceso completo a un rol de servicio compartido.

Borrados lógicos y flags de estado

Si usas deleted_at o status, inclúyelos en la política de SELECT (deleted_at is null). De lo contrario, alguien puede “resucitar” filas cambiando flags que la app asumía definitivos.

UPSERT: mantén WITH CHECK compatible

INSERT ... ON CONFLICT DO UPDATE debe cumplir WITH CHECK para la fila después de la escritura. Si tu política requiere created_by = app_user_id(), asegúrate de que tu upsert establezca created_by en el insert y no lo sobreescriba en el update.

Si generas código backend, estos patrones valen la pena convertirlos en plantillas internas para que nuevas tablas empiecen con valores seguros en lugar de una hoja en blanco.

Fallos comunes de RLS que hacen el debugging doloroso

RLS es genial hasta que un pequeño detalle hace que parezca que PostgreSQL está “ocultando o mostrando datos al azar”. Los errores siguientes hacen perder más tiempo.

Errores que causan fugas silenciosas entre tenants

La primera trampa es olvidar WITH CHECK en insert y update. USING controla lo que puedes ver, no lo que puedes crear. Sin WITH CHECK, un bug en la app puede escribir una fila en el tenant equivocado y quizá no lo notes porque ese mismo usuario no puede leerla.

Otra fuga común es el “join filtrado mal”. Filtras correctamente projects, luego haces join a invoices, notes o files que no están protegidos igual. La solución es estricta pero directa: cada tabla que pueda revelar datos de tenant necesita su propia política, y las vistas no deberían depender de que solo una tabla sea segura.

Patrones de fallo comunes que aparecen temprano:

  • Existe una política de lectura, pero falta WITH CHECK para escritura.
  • Una condición de política usa un join a otra tabla que no está protegida.
  • El acceso se aplica en una vista, pero la tabla subyacente sigue abierta.
  • Confías en que “la app siempre pone tenant_id”, y un job en background se olvida.
  • Testeas con un rol superuser, así nunca ves el comportamiento real.

Errores que hacen el comportamiento confuso

Políticas que referencian la misma tabla (directamente o a través de una vista) pueden crear sorpresas de recursión. Una política puede comprobar membresía consultando una vista que lee la tabla protegida otra vez, lo que lleva a errores, consultas lentas o una política que nunca coincide.

La configuración de roles es otra fuente de confusión. Owners de tablas y roles elevados pueden evitar RLS, así que tus tests pasan mientras los usuarios reales fallan (o al revés). Prueba siempre con el mismo rol de bajo privilegio que usa tu app.

Ten cuidado con funciones SECURITY DEFINER. Corren con los privilegios del owner de la función, así que un helper como current_tenant_id() puede estar bien, pero una función “de conveniencia” que lea datos puede leer accidentalmente entre tenants a menos que la diseñes para respetar RLS.

También establece un search_path seguro dentro de funciones security definer. Si no lo haces, la función puede usar un objeto con el mismo nombre y tu lógica de política puede apuntar silenciosamente a lo incorrecto según el estado de la sesión.

Depurar RLS: formas prácticas de ver qué está pasando

Aplica RLS a datos SaaS comunes
Construye un CRM basado en tenants y aplica los mismos patrones RLS en todas las tablas.
Iniciar un CRM

Los bugs de RLS suelen ser falta de contexto, no “SQL malo”. Una política puede ser correcta en papel y aún fallar porque el rol de sesión es diferente de lo que piensas, o porque la petición nunca fijó los valores de tenant y usuario que la política espera.

Una forma fiable de reproducir un informe de producción es replicar la misma configuración de sesión localmente y ejecutar la consulta exacta. Eso usualmente significa:

  • SET ROLE app_user; (o el rol real de la API)
  • SELECT set_config('app.tenant_id', 't_123', true); y SELECT set_config('app.user_id', 'u_456', true);
  • Ejecutar el mismo SQL que corrió tu app (incluyendo parámetros)
  • Confirmar lo que Postgres ve: SELECT current_user, current_setting('app.tenant_id', true), current_setting('app.user_id', true);

Cuando no estés seguro de qué política se aplica, consulta el catálogo en lugar de adivinar. pg_policies muestra cada política, el comando y las expresiones USING y WITH CHECK. Combínalo con pg_class para confirmar que RLS está habilitado en la tabla y no se está evitando.

Los problemas de rendimiento pueden parecer problemas de autorización. Una política que hace join a una tabla de membresía o llama a una función puede ser correcta pero lenta cuando la tabla crece. Usa EXPLAIN (ANALYZE, BUFFERS) en la consulta reproducida y busca escaneos secuenciales, nested loops inesperados o filtros aplicados tarde. La falta de índices en (tenant_id, user_id) y tablas de membresía son causas comunes.

También ayuda registrar tres valores por petición en la capa app: el tenant ID, el user ID y el rol de base de datos usado para la petición. Cuando esos no coinciden con lo que piensas que estableciste, RLS se comportará “mal” porque las entradas son incorrectas.

Para tests, conserva algunos tenants seed y haz las fallas explícitas. Una pequeña suite suele incluir: “Tenant A no puede leer Tenant B”, “usuario sin membresía no puede ver el proyecto”, “owner puede actualizar, viewer no”, “insert bloqueado a menos que tenant_id coincida con el contexto” y “override admin solo aplica donde debe”.

Checklist rápido antes de pasar a producción con RLS

Trata RLS como un cinturón de seguridad, no como un toggle de feature. Pequeños fallos se convierten en “todo el mundo ve los datos de todos” o “todo devuelve cero filas”.

Modelo de datos y forma de las políticas

Asegúrate de que el diseño de tablas y las reglas de política encajen con tu modelo de tenant.

  • Cada tabla propiedad de tenant debería tener una clave de tenant clara (usualmente tenant_id). Si no la tiene, escribe por qué (por ejemplo, tablas de referencia globales).
  • Habilita RLS en todas las tablas propiedad de tenant, no solo en las “principales”. Si algunos caminos nunca deben eludirlo, considera FORCE ROW LEVEL SECURITY en esas tablas.
  • Separa reglas de lectura y escritura. Lecturas usan USING. Las escrituras deben incluir WITH CHECK para que inserts/updates no puedan mover una fila a otro tenant.
  • Mantén predicados de política amigables para índices. Si las políticas filtran por tenant_id o hacen joins a tablas de membresía, añade los índices correspondientes.

Un escenario de sanity simple: un usuario del tenant A puede leer sus propias facturas, puede insertar una factura solo para el tenant A y no puede actualizar una factura para cambiar tenant_id.

Roles, rendimiento y tests

RLS solo es tan fuerte como los roles que usa tu app.

  • Confirma que la app nunca se conecta como superuser, owner de tablas o cualquier rol con bypassrls.
  • Ejecuta algunas consultas reales con volumen de datos similar a producción y revisa planes de consulta.
  • Añade tests negativos automatizados que demuestren que el acceso entre tenants falla.

Ejemplo: app multi-tenant de projects con acceso por membresía

Empieza con un backend centrado en Postgres
Obtén una API Go limpia y un esquema PostgreSQL que puedas proteger con RLS.
Generar backend

Imagina una app B2B donde compañías (orgs) tienen proyectos, y los proyectos tienen tareas. Los usuarios pueden pertenecer a múltiples orgs y pueden ser miembros de algunos proyectos y no de otros. Esto encaja bien con RLS porque la base de datos puede hacer cumplir el aislamiento por tenant incluso si un endpoint olvida un filtro.

Un modelo simple es: orgs, users, org_memberships (org_id, user_id, role), projects (id, org_id), project_memberships (project_id, user_id), tasks (id, project_id, org_id, ...). Ese org_id en tasks es intencional. Mantiene las políticas simples y reduce sorpresas en joins.

Una fuga clásica ocurre cuando tasks solo tiene project_id y tu política comprueba acceso mediante un join a projects. Un error (una política permisiva en projects, un join que elimina una condición o una vista que cambia contexto) puede exponer tareas de otra org.

Un camino de migración más seguro evita romper tráfico en producción:

  • Publica primero cambios de esquema (añade org_id a tasks, añade tablas de membresía).
  • Backfill tasks.org_id desde projects.org_id, luego añade NOT NULL.
  • Añade políticas pero mantén RLS deshabilitado mientras pruebas en staging.
  • Habilita RLS, luego forzalo, y solo entonces quita los filtros en la app.

El acceso de soporte suele manejarse mejor con un rol narrow break-glass, no desactivando RLS. Sepáralo de las cuentas normales de soporte y haz explícito cuándo se usa.

Documenta las reglas para que las políticas no se desvíen: qué variables de sesión deben setearse (user_id, org_id), qué tablas deben llevar org_id, qué significa “miembro” y unos pocos ejemplos SQL que deberían devolver 0 filas si se ejecutan con la org equivocada.

Siguientes pasos: desplegar RLS con seguridad y mantenerlo manejable

RLS es más fácil de mantener cuando lo tratas como un cambio de producto. Desplázalo en trozos pequeños, prueba el comportamiento con tests y guarda un registro claro del porqué de cada política.

Un plan de despliegue que suele funcionar:

  • Empieza con una tabla que tenga propiedad de tenant clara (por ejemplo, projects) y ciérrala.
  • Añade tests que cubran lecturas y escrituras permitidas y bloqueadas para unos pocos roles (owner, member, outsider).
  • Expande por lotes (un área de funcionalidad a la vez) que puedas debuggear en una sola sesión.
  • Monitoriza errores de permiso durante la implantación y despliega en una ventana de bajo riesgo.

Tras la primera tabla estable, haz que los cambios de política sean deliberados. Añade un paso de revisión de políticas a las migraciones e incluye una nota corta sobre la intención (quién debe acceder a qué y por qué) más el test correspondiente. Esto evita “añadir otro OR” que poco a poco se convierta en un agujero.

Si te mueves rápido, herramientas como Koder.ai (koder.ai) pueden ayudarte a generar un punto de partida Go + PostgreSQL por chat, y luego puedes añadir políticas RLS y tests con la misma disciplina que tendría un backend hecho a mano.

Finalmente, mantén redes de seguridad durante el despliegue. Toma snapshots antes de migraciones de políticas, practica rollbacks hasta que sea aburrido y conserva un pequeño camino break-glass para soporte que no desactive RLS en todo el sistema.

Preguntas frecuentes

¿Qué problema de seguridad resuelve RLS en una app SaaS?

RLS hace que PostgreSQL aplique qué filas son visibles o escribibles para una petición, de modo que el aislamiento por tenant no dependa de que cada endpoint recuerde el WHERE tenant_id = .... La ventaja principal es reducir los errores de “se olvidó un chequeo” cuando la aplicación crece y las consultas se multiplican.

¿Cuándo merece la pena RLS a pesar de la complejidad extra?

Vale la pena cuando las reglas de acceso son consistentes y basadas en filas, como el aislamiento por tenant o acceso por membresía, y cuando hay muchas vías para consultar datos (búsquedas, exports, pantallas de administración, jobs). Normalmente no compensa si las reglas son por campo, altamente excepcionales o están dominadas por grandes informes que necesitan lecturas entre tenants.

¿De qué NO me protege RLS?

Usa RLS para visibilidad de filas y control básico de escritura; para lo demás emplea otras herramientas. La privacidad de columnas típicamente necesita vistas y privilegios por columna, y las reglas de negocio complejas (por ejemplo, propiedad de facturación o flujos de aprobación) siguen perteneciendo a la lógica de la aplicación o a constraints de base de datos bien diseñados.

¿Cuál es la forma más segura de empezar a usar RLS si soy nuevo?

Crea un rol de bajo privilegio para la API (no el owner de las tablas), habilita RLS y añade una política SELECT y una política INSERT/UPDATE con WITH CHECK. Usa un valor de sesión por petición (por ejemplo app.current_tenant) y verifica que cambiarlo altera las filas que puedes ver y escribir.

¿Cómo debe mi app decirle a Postgres qué tenant y usuario hace la petición?

Una opción común es una variable de sesión por petición, establecida al inicio de la transacción, como app.tenant_id y app.user_id. La clave es consistencia: todos los caminos de código (requests web, jobs, scripts) deben fijar los mismos valores que esperan las políticas, o verás comportamientos confusos de “cero filas”.

¿Cuál es la diferencia entre USING y WITH CHECK en una política RLS?

USING controla el filtro de lectura: qué filas existentes son visibles y pueden ser objetivo de SELECT, UPDATE y DELETE. WITH CHECK controla qué filas nuevas o modificadas están permitidas en INSERT y , de modo que evita “escribir en otro tenant” aunque la app pase un incorrecto.

¿Por qué insisten en “no olvides WITH CHECK”?

Porque si solo añades USING, un endpoint con bug aún puede insertar o actualizar filas en otro tenant y quizá no lo notes porque ese mismo usuario no puede leer esa fila equivocada. Empareja siempre las reglas de lectura por tenant con una regla WITH CHECK para escrituras, así no se crean datos malos desde el principio.

¿Cómo debería estructurar mi esquema para que las políticas RLS sean simples y rápidas?

Evita joins dentro de las políticas poniendo la clave de tenant (por ejemplo org_id) directamente en las tablas propiedad del tenant, incluso si también referencian otra tabla que la tenga. Añade tablas de membresía explícitas (org_memberships, opcionalmente project_memberships) para que las políticas hagan una búsqueda indexada en lugar de inferencias complejas.

¿Cómo depuro “RLS me está ocultando datos” sin adivinar?

Reproduce primero el mismo contexto de sesión que usa tu app fijando el mismo rol y settings de sesión, y luego ejecuta la consulta SQL exacta. Después, confirma que RLS está habilitado e inspecciona pg_policies para ver qué expresiones USING y WITH CHECK se aplican: RLS suele fallar por falta de contexto de identidad más que por “SQL malo”.

Si genero mi backend (por ejemplo con Koder.ai), ¿aún necesito diseñar RLS con cuidado?

Sí, pero trata el código generado como punto de partida, no como un sistema de seguridad terminado. Si usas Koder.ai para generar un backend Go + PostgreSQL, aún necesitas definir tu modelo de tenants, fijar identidad en sesión de forma consistente y añadir políticas y tests deliberadamente para que las nuevas tablas no salgan sin las protecciones correctas.

Contenido
El problema real que RLS intenta resolver en apps SaaSCuándo RLS simplifica la autorización (y cuándo añade dolor)Conceptos de modelo de datos que hacen manejables las políticas RLSCómo funcionan las políticas RLS, sin las partes aterradorasPaso a paso: añade RLS a una tabla y verifica que funcionaPatrones de política que reutilizarás en la mayoría de apps SaaSFallos comunes de RLS que hacen el debugging dolorosoDepurar RLS: formas prácticas de ver qué está pasandoChecklist rápido antes de pasar a producción con RLSEjemplo: app multi-tenant de projects con acceso por membresíaSiguientes pasos: desplegar RLS con seguridad y mantenerlo manejablePreguntas frecuentes
Compartir
Koder.ai
Crea tu propia app con Koder hoy!

La mejor manera de entender el poder de Koder es verlo por ti mismo.

Empezar gratisReservar demo
UPDATE
tenant_id