Fundamentos de seguridad
"Row-level security policy for table objects" al subir
"New row violates row-level security policy for table objects" significa que tu subida no tiene regla insert. Hacer público el bucket no añade ninguna.

En resumen
- "New row violates row-level security policy for table objects" significa que tu subida llegó a Supabase Storage y ninguna regla sobre storage.objects la dejó entrar. El archivo no se guardó, y el resto del bucket sigue intacto.
- Storage guarda sus reglas en una sola tabla para todos los buckets de tu proyecto, y por eso el mensaje nombra una tabla que tú nunca creaste.
- Hacer público el bucket no lo resuelve. Supabase comprueba las subidas igualmente, y público solo decide quién puede abrir un archivo cuya dirección ya tiene.
- Una subida necesita una regla insert sobre storage.objects. Si tu código guarda con upsert, también necesita select y update.
Tu app sube un archivo. Funcionaba en la vista previa de tu builder, o funcionaba la semana pasada, y ahora cada intento vuelve con una frase sobre row-level security:
new row violates row-level security policy for table "objects"
Casi toda esa frase te sonará si ya pasaste por esto en una tabla tuya. Lo
distinto es la última palabra, porque objects no es una tabla que tú hayas
creado.
Esta es la parte que guía tras guía cuenta mal: la primera solución que vas a encontrar es hacer público el bucket, y no hace nada por las subidas. La propia documentación de Supabase lo dice en una línea. Ese interruptor deja la subida rechazada y abre los archivos que ya estaban dentro.
Qué significa "new row violates row-level security policy for table objects"
Tu subida llegó a Supabase Storage, Storage preguntó a las reglas puestas sobre
una tabla llamada storage.objects si ese archivo podía entrar, no encontró
ninguna que dijera que sí, y la rechazó.
La fila del mensaje existe de verdad. Storage guarda una fila en
storage.objects por cada archivo que tiene, con el nombre del archivo, el
bucket en el que está y quién lo puso ahí. Escribir un archivo es escribir esa
fila, así que las reglas de esa tabla deciden si la subida llega a ocurrir. No
se perdió nada: no se guardó ningún archivo, y el resto del bucket está
exactamente como hace un minuto.
La redacción viene de Postgres, la base de datos que hay debajo de Supabase, y
por eso se lee como maquinaria. Lleva el código 42501, el mismo que
un guardado rechazado en una de tus propias tablas.
Por qué el mensaje nombra "objects" y no tu bucket
Porque Storage guarda las reglas de todos los buckets de tu proyecto en esa única tabla.
Un bucket ordena archivos. No tiene reglas propias, y no hay ningún editor de
policies colgando de él. storage.objects es donde viven las reglas, para todos
tus buckets a la vez, y bucket_id es una columna suya. Así que una regla que
dice "solo en el bucket avatars" se escribe como una condición sobre esa
columna.
Por eso tampoco ayudó la regla de tabla que escribiste la semana pasada. Una
regla sobre profiles es una regla sobre filas de profiles, y un archivo es
una fila de storage.objects.
¿Tengo que hacer público el bucket para poder subir?
No. Detrás de un bucket hay dos preguntas distintas, y el interruptor solo responde a una. Quién puede sacar un archivo lo decide público. Quién puede meter uno es el tema de tu error, y la documentación de Supabase es explícita en que el ajuste no llega hasta ahí. De la página que describe las dos clases de bucket, leída el 11 de octubre de 2026:
El control de acceso se sigue aplicando a otros tipos de operación, incluidos subir, borrar, mover y copiar.
Lo que público sí hace está dicho con la misma claridad en esa página: cualquiera que tenga la dirección de un archivo puede abrirlo sin iniciar sesión. El ajuste no es nada más que eso.
Así que cambiarlo te deja en el único estado que nadie quiere. La subida sigue rechazada, porque subir nunca fue lo que el ajuste controlaba, y los archivos que ya estaban en el bucket los puede abrir ahora cualquiera que tenga sus direcciones. Lo que de verdad te cuesta un bucket público es una pregunta distinta de este error, y vale la pena leerla antes de tocar el interruptor.
Qué comprueba de verdad un bucket cuando subes
Una regla, y se llama insert.
La página de control de acceso de Supabase deja claro el valor por defecto: sin
policies, Storage no permite ninguna subida a un bucket, y tú permites
operaciones de una en una escribiéndolas sobre storage.objects. Después nombra
la que necesitas:
Por ejemplo, la única policy de RLS necesaria para subir objetos es conceder el permiso
INSERTsobre la tablastorage.objects.
Hay una segunda mitad, y es el motivo más común por el que una regla insert no quita el error:
Para permitir sobrescribir archivos mediante la funcionalidad
upserttendrás que conceder además los permisosSELECTyUPDATE.
Si tu subida pasa upsert: true, que pide a Storage reemplazar un archivo del
mismo nombre cuando ya hay uno, entonces una regla insert sola no basta.
Reemplazar un archivo son tres preguntas en vez de una: puedes añadir esta fila,
puedes ver la que está, y puedes cambiarla.
| Lo que tu app le pide a Storage | Lo que necesita sobre storage.objects |
|---|---|
| subir un archivo nuevo | una regla insert |
reemplazar un archivo del mismo nombre (upsert) | insert, más select y update |
| abrir un archivo de un bucket privado | una regla select |
| listar lo que hay en un bucket | una regla select |
| borrar un archivo | una regla delete |
No tienes que escribir las cinco. Escribe las que tu app hace de verdad, que para la mayoría de las subidas es la primera fila y a veces la segunda.
La regla que deja subir a tu app, y solo a tu app
Nombra el bucket, y di para quién vale la regla.
El punto de partida de la documentación limita una subida a un bucket y a visitantes con sesión iniciada:
create policy "Allow authenticated uploads"
on storage.objects
for insert
to authenticated
with check (bucket_id = 'my_bucket_id');
to authenticated es la parte que conviene no saltarse. Significa que la regla
se aplica a visitantes con sesión iniciada; quítala y la regla se aplica a todo
el mundo, desconocidos incluidos.
Para cualquier cosa que pertenezca a una persona concreta, la versión que publica Supabase pone los archivos de cada persona en una carpeta con su nombre:
create policy "Allow authenticated uploads"
on storage.objects
for insert
to authenticated
with check (
bucket_id = 'my_bucket_id' and
(storage.foldername(name))[1] = (select auth.jwt()->>'sub')
);
storage.foldername(name) parte la ruta de un archivo guardado en sus carpetas,
así que [1] es la primera. auth.jwt()->>'sub' es el id de quien tiene la
sesión iniciada y hace la petición. Leídas juntas, las dos líneas dicen que
puedes poner un archivo en la carpeta con tu nombre, en ese bucket, y en ningún
otro sitio.
Los dos son ejemplos de Supabase y no nuestros, leídos el 11 de octubre de 2026,
con my_bucket_id donde ellos lo dejaron para que veas qué parte es el nombre
de tu propio bucket.
Si prefieres ver el resultado desde fuera, nuestro escaneo gratuito le pregunta a tu proyecto en vivo cuáles de tus buckets le entregarán su contenido a un desconocido. Tarda unos 20 segundos y no necesita cuenta: escanea tu app.
Lo que una regla de lectura entrega sin que se lo pidan
Listar un bucket y descargar de él son el mismo privilegio, así que una regla escrita para que tus descargas funcionen hace también que el contenido del bucket se pueda listar.
La página de funciones de ayuda de Supabase lo dice directamente: un solo
privilegio de SQL como SELECT lo usan varias acciones de Storage. La página de
control de acceso avisa después del caso concreto, bajo un ejemplo que abre a
todo el mundo un bucket de avatares:
El filtro
allow_any_operation()es crítico aquí, porque sin él los usuarios podrían listar el contenido del bucket.
Ese es el hueco que medimos desde fuera. Hemos escaneado 8.435 apps en vivo que nombran un proyecto de Supabase. La comprobación de buckets obtuvo respuesta en 4.703 de ellas, y 792 de esas respondieron a nuestra petición de listado con los nombres de lo que tenían dentro, sin nadie con la sesión iniciada. Eso es alrededor de una de cada seis de las apps a las que pudimos preguntar.
Muchas veces basta con un nombre, porque un bucket que lista le ahorra a un
desconocido el trabajo de adivinar nombres de archivo.
invoice-2026-03-hannah.pdf dice de quién es el archivo antes de que nadie lo
abra.
El arreglo que Supabase documenta es decir para qué acción de Storage vale la regla:
create policy "Allow users to list their own objects"
on storage.objects
for select
to authenticated
using (
storage.allow_only_operation('object.list')
and owner_id = (select auth.uid()::text)
);
storage.allow_only_operation y su hermana storage.allow_any_operation son la
forma en que una regla select se estrecha a una de las acciones que comparten
el privilegio. Sin una de las dos, una regla que escribiste para que tu app
pudiera enseñar una imagen es también una regla que leerá en voz alta todo lo
que hay en el bucket.
Si un bucket que se puede listar es un problema para tu app depende de lo que haya dentro, y esa es una pregunta que el resultado del escaneo no puede responder por ti.
Cuándo público es la respuesta correcta
Cuando los archivos están pensados para que los vea cualquiera que los pida, que es una categoría real y de la que Supabase da sus propios ejemplos.
Los casos de uso que Supabase cita para un bucket público son fotos de perfil, medios públicos y contenido de entradas de blog. Son archivos que tu app enseña a un visitante sin sesión iniciada, y servirlos desde un bucket público es además más rápido, porque las dos clases de bucket se cachean de forma distinta.
Para la otra clase, la documentación nombra dos maneras de sacar un archivo de un bucket privado: una descarga que lleva el token de la persona con sesión iniciada, o un enlace firmado que funciona un tiempo limitado. Las dos dejan la decisión en tus reglas en vez de en quien encontró la dirección.
Cómo mantener bien el bucket después de hoy
Una vez correctas las reglas, se mueven dos cosas: alguien cambia el interruptor mientras persigue un fallo sin relación, y un archivo desaparece.
Reeve Monitor vuelve a pasar las nueve comprobaciones externas por ti:
- las nueve comprobaciones cada hora, en hasta tres apps, incluida la del listado de buckets
- si la app responde, cada 60 segundos
- un mensaje cuando un resultado cambia, para que un bucket que se abrió anoche no espere a que mires
- un informe mensual de lo que vio
Reeve Care guarda una copia de lo que hay dentro:
- una copia cifrada de tu base de datos de Supabase cada noche, guardada donde tu proyecto no llega
- cada copia verificada antes de contar, contando las filas de cada tabla
- también tus archivos subidos, en cuanto conectes un acceso de Storage
- una restauración en un clic cuando la necesites
- todo lo que hace Monitor
Los dos están en la página de precios, que a veces está por debajo de la cifra de lista de aquí y nunca por encima.
Qué hacer hoy
Qué hacer
- Lee el mensaje como un rechazo. El archivo no se guardó, el bucket está igual, y no hay nada que recuperar.
- Añade una regla
insertsobrestorage.objectsque nombre tu bucket enbucket_idy lleve la cláusulaTOque querías. - Si tu subida pasa
upsert: true, añade tambiénselectyupdate, o la regla insert sola no quitará el error. - Deja el interruptor de público donde está mientras haces esto. Sobre las subidas no tiene nada que decir, y sobre quién puede abrir lo que ya está guardado sí que lo tiene.
- Lee cada regla
selectsobrestorage.objectsy pregúntate qué permite además de la descarga para la que la escribiste. El listado comparte ese privilegio.
Empieza por el bucket del que vino el error, y mira después los otros buckets del mismo proyecto, porque una regla escrita amplia una vez suele haberse pegado dos. La checklist de seguridad en 10 minutos cubre qué más suele quedarse abierto en una app recién lanzada, y la guía de seguridad de Supabase repasa el resto de lo que puede alcanzar un desconocido.
Preguntas frecuentes
¿Por qué mi subida a Supabase falla con un error de row-level security?
Porque Supabase Storage preguntó a las reglas puestas sobre una tabla llamada storage.objects si tu archivo podía entrar, y no encontró ninguna que dijera que sí. Storage escribe una fila en esa tabla por cada archivo que guarda, y Row Level Security se aplica a esa fila igual que a una fila de cualquier otra tabla. No se guardó ningún archivo y nada de lo que ya estaba en el bucket cambió. El mensaje lleva el código de Postgres 42501, el mismo que cuando se rechaza un guardado en una tabla tuya.
¿Tengo que hacer público mi bucket para poder subir?
No, y hacerlo público no ayuda. Supabase documenta que el control de acceso se sigue aplicando a subir, borrar, mover y copiar, esté el bucket como esté. Público cambia una sola cosa: si alguien que tiene la dirección de un archivo puede abrirlo sin iniciar sesión. Así que el interruptor deja tu subida rechazada y hace que los archivos que ya estaban dentro los pueda abrir cualquiera que tenga su dirección.
¿Qué policies necesita un bucket?
Una subida necesita una regla insert sobre storage.objects. Si tu código reemplaza archivos del mismo nombre, que es lo que hace upsert, Supabase dice que también necesitas select y update. Abrir un archivo en un bucket privado necesita una regla select, y listar lo que hay en el bucket también, porque esas dos acciones de Storage corren sobre el mismo privilegio de SQL. Borrar necesita una regla delete. No hay obligación de escribir las cuatro, solo las que tu app hace de verdad.
¿Es peligroso un bucket público?
Depende por completo de lo que haya dentro. Público es el ajuste correcto para fotos de perfil, logos y cualquier otra cosa que tu app enseñe a un visitante que no ha iniciado sesión, que es justo lo que Supabase lista como sus propios casos de uso. Es el ajuste equivocado para facturas, exportaciones o cualquier cosa que pertenezca a una persona concreta, y para eso quieres un bucket privado con un enlace firmado. La pregunta nunca fue si público es malo.
¿Cómo compruebo si mi bucket se puede listar?
Pregúntaselo desde fuera, sin ninguna cuenta iniciada, exactamente como haría un desconocido. El listado lo concede una regla select y no el interruptor de público, así que ni el interruptor del panel ni un vistazo a tu lista de policies responden a eso. Nuestro escaneo gratuito lo hace sobre tu proyecto en vivo y te dice cuáles de tus buckets respondieron con los nombres de lo que tenían dentro.