[{"data":1,"prerenderedAt":425},["ShallowReactive",2],{"blog-es-supabase-rls-on-but-table-still-public":3},{"id":4,"title":5,"body":6,"category":387,"cover":388,"coverAlt":388,"description":389,"draft":390,"extension":391,"faq":392,"image":405,"keywords":406,"meta":412,"navigation":413,"ogTitle":414,"path":415,"published":416,"seo":417,"stem":418,"tldr":419,"updated":423,"__hash__":424},"blog_es\u002Fblog\u002Fsupabase-rls-on-but-table-still-public.md","Supabase Row Level Security está activado. Tu tabla sigue pública.",{"type":7,"value":8,"toc":376},"minimark",[9,13,21,26,29,32,35,39,42,45,56,77,80,89,93,104,195,201,204,212,216,223,230,233,239,249,253,256,259,298,301,306,310,313,316,327,330,334,363],[10,11,12],"p",{},"Activaste Row Level Security porque algo te lo dijo: el advisor de Supabase, una\nchecklist, el resultado de un escaneo, alguien en un Discord. Ahora el\ninterruptor está verde en tu panel. Y aun así te dicen que tu tabla sigue\nsiendo legible por desconocidos.",[10,14,15,16,20],{},"Aquí está la parte que guía tras guía cuenta mal: ",[17,18,19],"strong",{},"el interruptor no protege\nnada."," Decide que tus reglas se comprueben. Las reglas son otra cosa, las\ntienes que escribir tú, y la regla más rápida de escribir (la que hace que una\napp rota vuelva a funcionar) deja entrar a todo el mundo.",[22,23,25],"h2",{"id":24},"row-level-security-está-activado-en-supabase-cómo-sigue-pública-mi-tabla","Row Level Security está activado en Supabase. ¿Cómo sigue pública mi tabla?",[10,27,28],{},"Porque activarlo y decidir quién entra son dos pasos distintos, y solo el\nprimero es un interruptor.",[10,30,31],{},"Piensa en el interruptor como en poner a alguien en la puerta. Accionarlo no\ndecide quién pasa. Decide que ahora alguien comprueba una lista. Tus políticas\nson esa lista. Una lista vacía deja fuera a todos; una lista que dice «todos» no\ndeja fuera a nadie. Ambas cosas son Row Level Security activado, y tu panel\nmuestra el mismo verde en los dos casos.",[10,33,34],{},"Por eso el ajuste por sí solo responde muy poco, y por eso nuestro escáner nunca\nle pregunta a Supabase si está activado. Le pregunta a la tabla. Envía la\npetición que enviaría un desconocido, con la clave pública que viaja dentro de\ntu app, y mira si vuelve una respuesta. Pide un recuento en lugar de las filas,\nasí que descubre que la puerta se abrió sin leer nada de lo que hay detrás.",[22,36,38],{"id":37},"la-política-que-arregló-tu-app-es-probablemente-el-problema","La política que arregló tu app es probablemente el problema",[10,40,41],{},"En el momento en que activas Row Level Security tu app deja de mostrar datos, y\nlo que hiciste después para que volviera a funcionar es lo que merece una\nmirada.",[10,43,44],{},"Esa secuencia es completamente normal, y es donde esto se tuerce. Con el ajuste\nactivado y ninguna política escrita, Postgres (el motor de base de datos sobre\nel que corre Supabase) rechaza por defecto todas las peticiones, así que tus\nlistas vuelven vacías y tus pantallas se quedan en\nblanco. Algo tiene que entrar en la lista. Si le pediste a Cursor o a Lovable\nque lo arreglara, o pegaste el primer fragmento que hizo desaparecer el error,\nlo que tienes ahora probablemente se parece a esto:",[46,47,52],"pre",{"className":48,"code":50,"language":51},[49],"language-text","CREATE POLICY \"Enable read access for all users\"\n  ON public.profiles\n  FOR SELECT\n  USING (true);\n","text",[53,54,50],"code",{"__ignoreMap":55},"",[10,57,58,61,62,65,66,69,70,73,74,76],{},[53,59,60],{},"USING (true)"," es la condición que una fila tiene que cumplir para que la base\nde datos la entregue. Todas las filas cumplen ",[53,63,64],{},"true",". Ahí dentro hay un segundo\ndetalle fácil de pasar por alto: sin cláusula ",[53,67,68],{},"TO",", una política se aplica a\n",[53,71,72],{},"public",", y ",[53,75,72],{}," incluye por igual a los visitantes con sesión iniciada y a\nlos desconocidos.",[10,78,79],{},"Así que la app vuelve a funcionar, nada da error, y la tabla es exactamente tan\nlegible como lo era antes de empezar.",[10,81,82,83,88],{},"Eso arregla la lectura, y solo la lectura. Si tu app además guarda en esta\ntabla, lo siguiente que te encuentras es\n",[84,85,87],"a",{"href":86},"\u002Fblog\u002Fnew-row-violates-row-level-security-policy","new row violates row-level security policy",", que es el mismo ajuste\nrechazando una escritura, y ninguna policy de lectura lo quita.",[22,90,92],{"id":91},"los-cuatro-estados-en-los-que-puede-estar-una-tabla","Los cuatro estados en los que puede estar una tabla",[10,94,95,96,99,100,103],{},"Dos de ellos son seguros y dos no, y el interruptor no te dice cuáles. «Clave\npublicable» abajo es la que sí pertenece a tu app: ",[53,97,98],{},"sb_publishable_…"," en los\nproyectos nuevos de Supabase, ",[53,101,102],{},"anon"," en los antiguos.",[105,106,107,126],"table",{},[108,109,110],"thead",{},[111,112,113,117,120,123],"tr",{},[114,115,116],"th",{},"Row Level Security",[114,118,119],{},"La política",[114,121,122],{},"Tu app",[114,124,125],{},"Un desconocido con tu clave publicable de Supabase",[127,128,129,148,165,179],"tbody",{},[111,130,131,135,138,141],{},[132,133,134],"td",{},"Apagado",[132,136,137],{},"da igual, no se consulta",[132,139,140],{},"Funciona",[132,142,143],{},[144,145,147],"key-verdict",{"type":146},"danger","Lee todas las filas",[111,149,150,153,156,159],{},[132,151,152],{},"Encendido",[132,154,155],{},"ninguna escrita",[132,157,158],{},"Rota",[132,160,161],{},[144,162,164],{"type":163},"safe","No lee nada",[111,166,167,169,173,175],{},[132,168,152],{},[132,170,171],{},[53,172,60],{},[132,174,140],{},[132,176,177],{},[144,178,147],{"type":146},[111,180,181,183,188,190],{},[132,182,152],{},[132,184,185],{},[53,186,187],{},"USING (auth.uid() = user_id)",[132,189,140],{},[132,191,192],{},[144,193,194],{"type":163},"Lee solo las suyas",[196,197],"diagram",{"alt":198,"caption":199,"src":200},"Cuatro tablas una al lado de otra. En la primera el interruptor está apagado y las seis filas son legibles. En la segunda el interruptor está encendido con la caja de política vacía y ninguna fila es legible. En la tercera el interruptor está encendido con una política que dice true, y las seis filas vuelven a ser legibles. En la cuarta el interruptor está encendido con una política que compara el dueño de la fila, y solo dos filas son legibles.","La marca bajo cada columna no sigue al interruptor de arriba. Dos de estos están encendidos y uno de ellos lo entrega todo.","\u002Fblog\u002Fsupabase-rls-on-but-table-still-public\u002Frls-four-states-1600x760.png",[10,202,203],{},"Las dos columnas centrales son el par que pilla a la gente. Mismo ajuste, misma\ninsignia verde, resultados opuestos, y la diferencia es una palabra dentro de\nuna regla que casi nadie abre.",[10,205,206,207,211],{},"Si prefieres no leer cada política tú mismo, nuestro escaneo gratuito le hace a\ntu base de datos en vivo la misma pregunta que un desconocido y te dice qué\ntablas respondieron. Tarda unos 20 segundos y no necesita cuenta:\n",[84,208,210],{"href":209},"\u002F#scan","escanea tu app",".",[22,213,215],{"id":214},"authenticated-no-es-lo-mismo-que-tuyo","«Authenticated» no es lo mismo que «tuyo»",[10,217,218,219,222],{},"Una política que permite ",[53,220,221],{},"authenticated"," permite a todo el que tenga cuenta, lo\nque (si tu app tiene un formulario de registro abierto) es todo el que esté\ndispuesto a rellenarlo.",[10,224,225,226,229],{},"Esta es la versión más sutil del mismo error, y sobrevive a muchas revisiones\nporque parece cuidadosa. ",[53,227,228],{},"TO authenticated USING (true)"," se lee como una\nrestricción, y lo es: excluye a quien nunca se registró. Lo que no hace es\nimpedir que uno de tus clientes lea las filas de otro cliente, que suele ser lo\nque querías decir con «privado».",[10,231,232],{},"La regla que sí lo hace nombra a quién pertenece la fila:",[46,234,237],{"className":235,"code":236,"language":51},[49],"CREATE POLICY \"Users read their own rows\"\n  ON public.orders\n  FOR SELECT\n  TO authenticated\n  USING (auth.uid() = user_id);\n",[53,238,236],{"__ignoreMap":55},[10,240,241,244,245,248],{},[53,242,243],{},"auth.uid()"," es quien está preguntando. ",[53,246,247],{},"user_id"," es la columna de la fila que\ndice de quién es. La fila vuelve cuando esas dos coinciden, y se queda donde\nestá cuando no.",[22,250,252],{"id":251},"cómo-revisar-tus-propias-tablas-en-dos-minutos","Cómo revisar tus propias tablas en dos minutos",[10,254,255],{},"Abre Supabase, ve a Authentication → Policies y lee la expresión que hay dentro\nde cada política en lugar de la insignia que hay junto a cada tabla.",[10,257,258],{},"Tres cosas que buscar:",[260,261,262,271,279],"ul",{},[263,264,265,270],"li",{},[17,266,267,268,211],{},"Una política cuya condición sea ",[53,269,64],{}," Decide, tabla por tabla, si\nestarías cómodo con esos datos en una página abierta. Para una lista de\nartículos publicados, sí. Para cualquier cosa con una persona dentro, no.",[263,272,273,278],{},[17,274,275,276,211],{},"Una política sin cláusula ",[53,277,68],{}," Se aplica a todo el mundo, con sesión o\nsin ella, aunque el resto de la regla parezca muy concreto.",[263,280,281,284,285,288,289,292,293,297],{},[17,282,283],{},"Una tabla con el ajuste activado, sin políticas, y una app que aun así\nfunciona."," Esa combinación significa que algo está llegando a tus datos por\notra vía, y la explicación habitual es una clave secreta (",[53,286,287],{},"sb_secret_…",", o\n",[53,290,291],{},"service_role"," en un proyecto antiguo), que ignora todas las políticas que has\nescrito.\n",[84,294,296],{"href":295},"\u002Fblog\u002Fwhich-api-keys-are-safe-in-your-frontend","Qué claves de API son seguras en tu frontend","\ncuenta cómo distinguirla de la segura.",[10,299,300],{},"La otra comprobación a la que se recurre es abrir la app en una ventana privada\nsin iniciar sesión, y merece la pena hacerla, pero conviene saber qué demuestra.\nTu app decide qué dibuja. Tu base de datos decide qué entrega. Son decisiones\ndistintas, y un desconocido que se salta tus pantallas se lleva la segunda.",[196,302],{"alt":303,"caption":304,"src":305},"Dos caminos hacia las mismas filas. A través de las pantallas de la app vuelven dos de seis filas. Directamente a la dirección de la base de datos vuelven las seis.","Las pantallas de tu app no son la valla. La misma tabla puede mostrar dos filas a través de tu app y entregar las seis a una petición que nunca la abrió.","\u002Fblog\u002Fsupabase-rls-on-but-table-still-public\u002Fapp-view-vs-database-1600x620.png",[22,307,309],{"id":308},"qué-aspecto-tiene-cuando-te-está-pasando","Qué aspecto tiene cuando te está pasando",[10,311,312],{},"Ninguno. Esa es la forma de este problema, y por eso se queda ahí durante meses.",[10,314,315],{},"No aparece ningún error en tu builder. Nada va más lento, ninguna pantalla se\nrompe, no llega ningún correo. Tu app se comporta exactamente igual que el día\nque la lanzaste, porque desde su lado no ha cambiado nada: siempre tuvo permiso\npara leer esas filas. Lo que cambió es que todos los demás también lo tienen,\nusando la clave que viaja en el código que tu sitio envía a cada visitante.",[10,317,318,319,322,323,326],{},"Cuando aflora, aflora de lado. Una clienta pregunta cómo supo alguien algo que\nsolo sabía tu app. Una lista de direcciones que nunca publicaste aparece en\nalgún sitio. Y si la regla generosa cubre también la escritura (",[53,320,321],{},"FOR ALL"," en\nlugar de ",[53,324,325],{},"FOR SELECT","), entonces cualquiera puede además cambiar y borrar\nfilas, que es la versión que la gente descubre como una tabla que de pronto está\nvacía.",[10,328,329],{},"Las reglas también se desplazan. Una migración, un cambio de esquema, otro\narreglo de madrugada que necesitaba que los datos cargaran: cualquiera de ellos\npuede aflojar una política sin decirlo, y por eso esto merece una segunda mirada\nmás adelante y no solo una ahora. Vigilar eso es parte de lo que hace Reeve\nCare, aunque un recordatorio en tu calendario cumple la misma función.",[22,331,333],{"id":332},"qué-hacer-esta-semana","Qué hacer esta semana",[335,336,337],"key-takeaways",{},[260,338,339,342,345,354,360],{},[263,340,341],{},"Abre Authentication → Policies en Supabase y lee la condición de cada política, tabla por tabla. La insignia de la tabla no es la respuesta.",[263,343,344],{},"Para cada tabla con personas dentro (usuarios, perfiles, pedidos, mensajes), comprueba que la regla nombre al dueño de la fila en lugar de permitir a todos.",[263,346,347,348,350,351,353],{},"Sustituye cualquier ",[53,349,60],{}," en esas tablas por una regla que compare ",[53,352,243],{}," con la columna del dueño, y confirma después que tu app sigue cargando.",[263,355,356,357,359],{},"Añade la cláusula ",[53,358,68],{}," que querías poner. Una política sin ella se aplica a los desconocidos igual que a los visitantes con sesión.",[263,361,362],{},"Si una tabla tiene el ajuste activado, ninguna política y tu app sigue mostrando sus datos, averigua qué lo está sorteando antes de tocar nada más.",[10,364,365,366,370,371,375],{},"Empieza por la tabla que más te avergonzaría si fuera una página abierta, y\narregla esa hoy. La ",[84,367,369],{"href":368},"\u002Fchecklist","checklist de seguridad de 10 minutos"," cubre\nesto junto a lo demás que conviene mirar en una app recién lanzada, y la\n",[84,372,374],{"href":373},"\u002Fis-your-supabase-app-safe","guía de seguridad de Supabase"," repasa qué más se\nsuele dejar abierto.",{"title":55,"searchDepth":377,"depth":377,"links":378},3,[379,381,382,383,384,385,386],{"id":24,"depth":380,"text":25},2,{"id":37,"depth":380,"text":38},{"id":91,"depth":380,"text":92},{"id":214,"depth":380,"text":215},{"id":251,"depth":380,"text":252},{"id":308,"depth":380,"text":309},{"id":332,"depth":380,"text":333},"Fundamentos de seguridad",null,"Activar Supabase Row Level Security no protege una tabla. Lo hacen tus políticas, y la que arregló tu app rota puede dejar entrar a cualquiera.",false,"md",[393,396,399,402],{"q":394,"a":395},"Activé Row Level Security y mi app dejó de mostrar datos. ¿He roto algo?","No, eso es el ajuste funcionando. Con Row Level Security activado y ninguna política escrita, Postgres (el motor de base de datos que hay debajo de Supabase) rechaza por defecto todas las peticiones, incluidas las de tu propia app. La solución es añadir una política que describa quién debe ver qué filas. El error que hay que evitar es añadir una que permita a todo el mundo, porque esa es la versión que hace funcionar la app y deja la tabla abierta.",{"q":397,"a":398},"¿USING (true) es alguna vez la política correcta?","Sí, para datos que son públicos de verdad. Una tabla de artículos publicados, un catálogo de productos, una lista de locales en un mapa: están pensados para que cualquiera los lea, y una política que lo permite es correcta. Deja de serlo en el momento en que la tabla tiene personas dentro. Pregúntate si estarías cómodo publicando el contenido de esa tabla en una página abierta, y deja que la respuesta decida la política.",{"q":400,"a":401},"¿Row Level Security me protege si se filtró mi clave secreta?","No. Una clave secreta de Supabase (sb_secret_ en proyectos nuevos, service_role en los antiguos) se salta Row Level Security por completo, que es para lo que existe. Todas las políticas que escribiste se ignoran, en todas las tablas. Si esa clave está en tu frontend, tus políticas no están haciendo nada por ti, y rotar la clave es lo primero, antes que cualquier trabajo sobre las reglas.",{"q":403,"a":404},"¿Necesito Row Level Security si mi app ya tiene pantalla de inicio de sesión?","Sí. Tu pantalla de acceso controla tu app, y tu app no es la única forma de llegar a tu base de datos. Supabase da a cada proyecto una dirección web que responde a peticiones directamente, y la clave para hablar con ella está en el código que tu app envía a cada visitante. Las políticas son la parte que se aplica venga la petición por la puerta que venga.","\u002Fblog\u002Fsupabase-rls-on-but-table-still-public\u002Fcard-800x500.png",[407,408,409,410,411],"supabase rls","row level security politica","tabla supabase publica","rls no funciona","seguridad supabase",{},true,"Supabase RLS está activado. Tu tabla sigue siendo pública.","\u002Fblog\u002Fsupabase-rls-on-but-table-still-public","2026-08-10",{"title":5,"description":389},"blog\u002Fsupabase-rls-on-but-table-still-public",[420,421,422],"En Supabase, el interruptor y las reglas son dos cosas distintas. Activado sin regla no deja pasar a nadie; activado con la regla equivocada no detiene a nadie.","La regla que hace funcionar de nuevo una app rota suele ser la que permite cualquier petición, venga de quien venga.","Lee la política, no el interruptor. Una sola palabra decide si un desconocido puede leer tu tabla.","2026-08-12","CNQhJf8aF5SdAGilvmLpmn5l1P4idEfavjcPP4doHEE",1787826048206]