[{"data":1,"prerenderedAt":810},["ShallowReactive",2],{"blog-es-supabase-security-checker":3,"blog-index-es":570},{"id":4,"title":5,"body":6,"category":522,"cover":523,"coverAlt":524,"description":525,"draft":526,"extension":527,"faq":528,"image":544,"keywords":545,"meta":558,"navigation":559,"ogTitle":560,"path":561,"published":562,"seo":563,"stem":564,"tldr":565,"updated":562,"__hash__":569},"blog_es\u002Fblog\u002Fsupabase-security-checker.md","Supabase security checker: haz tú mismo las cinco comprobaciones",{"type":7,"value":8,"toc":510},"minimark",[9,13,21,24,29,32,35,38,47,51,54,66,89,112,115,118,124,128,131,141,148,159,162,226,231,237,260,268,272,275,281,287,290,301,322,330,334,337,344,350,357,363,373,377,380,386,389,396,400,403,409,428,434,444,447,451,482,487,495,502],[10,11,12],"p",{},"Buscaste un Supabase security checker y te salieron nueve. Uno quiere tu\nrepositorio. Otro quiere una extensión de navegador. Otro quiere acceso de\nlectura a toda tu cuenta de Supabase, que se parece bastante a aquello que te\npreocupaba como para hacerte dudar.",[10,14,15,16,20],{},"Esto es lo que ninguna de esas páginas de producto dice en voz alta. ",[17,18,19],"strong",{},"Son cinco\ncomprobaciones, y puedes hacer todas tú mismo",", sin cuenta, sin instalar nada y\nsin ninguna de las credenciales que sería peligroso entregar. Diez minutos en\nuna terminal cubren el mismo terreno que las herramientas.",[10,22,23],{},"Las cinco de abajo son las que nuestro propio escáner ejecuta contra un proyecto\nde Supabase, escritas como comandos. Si construiste con Lovable, Bolt, v0,\nCursor, Replit, Windsurf o Base44 y tu app guarda cualquier cosa, lo que hay\ndetrás es muy probablemente Supabase, y estas son las preguntas que le haría un\ndesconocido.",[25,26,28],"h2",{"id":27},"qué-comprueba-realmente-un-supabase-security-checker","¿Qué comprueba realmente un Supabase security checker?",[10,30,31],{},"Tu app publicada. Los ajustes dentro de tu proyecto son trabajo de otra\nherramienta, y Supabase ya trae esa herramienta.",[10,33,34],{},"El Security Advisor de tu panel lee el catálogo del propio proyecto: qué tablas\ntienen Row Level Security desactivado, qué funciones llevan un search path laxo,\nqué extensiones viven en el esquema público. Está leyendo lo que configuraste.",[10,36,37],{},"Un checker lee lo que tu app le entrega a un desconocido. El bundle de\nJavaScript que descargan tus visitantes, la respuesta que da una tabla a una\npetición sin sesión, el contenido de un bucket de almacenamiento, las cabeceras\nde tus páginas. De tu configuración no ve nada, y no le hace falta, porque está\nmirando la consecuencia.",[10,39,40,41,46],{},"Las dos mitades se separan más a menudo de lo que sugieren los nombres. Una\ntabla puede pasar el Advisor con el interruptor puesto y una política escrita, y\naun así entregar sus filas a cualquiera, porque la política que se escribió deja\nentrar a todo el mundo. Eso merece un artículo aparte si es nuevo para ti:\n",[42,43,45],"a",{"href":44},"\u002Fblog\u002Fsupabase-rls-on-but-table-still-public","el interruptor no es la protección",".",[25,48,50],{"id":49},"comprobación-1-qué-clave-de-supabase-entrega-tu-app","Comprobación 1: ¿qué clave de Supabase entrega tu app?",[10,52,53],{},"Abre tu app en un navegador, pulsa F12 y mira una sola petición.",[10,55,56,57,61,62,65],{},"Ve a la pestaña Red, recarga la página y escribe ",[58,59,60],"code",{},"supabase"," en el filtro. Tu app\nhará al menos una petición a una dirección que acaba en ",[58,63,64],{},".supabase.co",". Haz clic\nen ella. Ahí dentro están las dos cosas que necesitas para el resto del\nartículo:",[67,68,69,80],"ul",{},[70,71,72,75,76,79],"li",{},[17,73,74],{},"La dirección de la petición"," empieza con la URL de tu proyecto, algo como\n",[58,77,78],{},"https:\u002F\u002Fabcdefghij.supabase.co",". Las comprobaciones 2 y 3 apuntan ahí.",[70,81,82,88],{},[17,83,84,85],{},"La cabecera de petición ",[58,86,87],{},"apikey"," lleva la clave que tu app entrega a cada\nvisitante.",[10,90,91,92,95,96,99,100,103,104,107,108,46],{},"Copia las dos a algún sitio y luego lee la clave. Una que empieza por\n",[58,93,94],{},"sb_publishable_"," pertenece a tu app y no hay nada que arreglar. Una que empieza\npor ",[58,97,98],{},"sb_secret_"," no pertenece nunca, y encontrarla termina la lista antes de\ntiempo: rótala hoy, antes que cualquier otra cosa de esta página. Los proyectos\nantiguos llevan un par sin prefijo legible, ",[58,101,102],{},"anon"," y ",[58,105,106],{},"service_role",", y\ndistinguirlas significa\n",[42,109,111],{"href":110},"\u002Fblog\u002Fwhich-api-keys-are-safe-in-your-frontend","leer el rol dentro de la clave",[10,113,114],{},"Ese último caso es raro. De las 30.998 apps en vivo que escaneamos en agosto de\n2026, una clave secreta de Supabase en el navegador apareció tres veces.",[10,116,117],{},"Mientras tengas abierta la pestaña Red, anota los nombres de tabla que veas en\nesas direcciones. La siguiente comprobación los necesita.",[119,120],"diagram",{"alt":121,"caption":122,"src":123},"Un panel de bundle en el centro que contiene una llave y una dirección. Una flecha sale hacia la izquierda hasta una ventana de navegador, marcada con los números 4 y 5. Otra sale hacia la derecha hasta una base de datos y un bucket de almacenamiento etiquetado .supabase.co, marcada con 2 y 3.","Todo lo que viene apunta a una de dos direcciones: la de tu app y la de tu proyecto. La comprobación 1 es la que te da la segunda.","\u002Fblog\u002Fsupabase-security-checker\u002Ftwo-addresses-1600x680.png",[25,125,127],{"id":126},"comprobación-2-responden-tus-tablas-a-un-desconocido","Comprobación 2: ¿responden tus tablas a un desconocido?",[10,129,130],{},"Pregúntale a la base de datos cuántas filas le daría a alguien sin sesión, y lee\nel número en la respuesta.",[132,133,139],"pre",{"className":134,"code":136,"language":137,"meta":138},[135],"language-bash","curl -s -i \\\n  -H \"apikey: TU_CLAVE_PUBLICABLE\" \\\n  -H \"Prefer: count=exact\" \\\n  -H \"Range: 0-0\" \\\n  \"https:\u002F\u002FTU_PROYECTO.supabase.co\u002Frest\u002Fv1\u002Fprofiles?select=count\" \\\n  | grep -i content-range\n","bash","",[58,140,136],{"__ignoreMap":138},[10,142,143,144,147],{},"Pon la clave y la dirección del proyecto de la comprobación 1, y tu propio\nnombre de tabla donde dice ",[58,145,146],{},"profiles",". curl viene instalado en macOS, en Linux y\nen Windows actual, así que no hay nada que descargar antes.",[10,149,150,151,154,155,158],{},"Esa petición no trae ninguna fila. ",[58,152,153],{},"select=count"," pide un recuento,\n",[58,156,157],{},"Range: 0-0"," pide ninguna de las filas en sí, y toda la respuesta llega en una\nsola cabecera. Es la misma petición que hace nuestro escáner, y por eso puede\nejecutarse contra la app en vivo de alguien sin tocar sus datos.",[10,160,161],{},"Pueden volver cuatro cosas, y una de ellas es un hallazgo:",[163,164,165,178],"table",{},[166,167,168],"thead",{},[169,170,171,175],"tr",{},[172,173,174],"th",{},"Lo que se imprime",[172,176,177],{},"Lo que significa",[179,180,181,192,202,216],"tbody",{},[169,182,183,189],{},[184,185,186],"td",{},[58,187,188],{},"content-range: *\u002F0",[184,190,191],{},"Nada legible sin sesión. Esa tabla está haciendo su trabajo.",[169,193,194,199],{},[184,195,196],{},[58,197,198],{},"content-range: 0-0\u002F128",[184,200,201],{},"128 filas al alcance de cualquiera que tenga la clave que viaja en tu página.",[169,203,204,213],{},[184,205,206,209,210],{},[58,207,208],{},"401"," o ",[58,211,212],{},"403",[184,214,215],{},"La clave fue rechazada antes de llegar a la tabla. Sin respuesta, no limpio.",[169,217,218,223],{},[184,219,220],{},[58,221,222],{},"404",[184,224,225],{},"No hay tabla con ese nombre. O lo adivinaste, o se escribe de otra forma.",[119,227],{"alt":228,"caption":229,"src":230},"Dos paneles de respuesta uno al lado del otro. Ambos muestran la misma línea de estado 200. El de la izquierda enmarca una cabecera content-range que acaba en barra y cero, y recibe una marca verde; el de la derecha enmarca la misma cabecera acabada en 128, y recibe una cruz roja.","Las dos peticiones tuvieron éxito. Una cabecera es toda la diferencia entre una tabla que hace su trabajo y una que entrega filas a quien las pida.","\u002Fblog\u002Fsupabase-security-checker\u002Fthe-one-header-1600x700.png",[10,232,233,234,236],{},"El rechazo es la fila con la que hay que tener cuidado. Un ",[58,235,208],{}," dice que la\npetición se detuvo antes de llegar a la tabla, lo que no te dice nada sobre si\nla tabla está protegida, y de las cuatro es la más fácil de redondear a una\nmarca verde.",[10,238,239,240,243,244,246,247,243,250,243,253,243,256,259],{},"Ejecuta el comando para cada tabla que tu app nombró en la comprobación 1, y\ndespués para las corrientes que suele tener una app: ",[58,241,242],{},"users",", ",[58,245,146],{},",\n",[58,248,249],{},"customers",[58,251,252],{},"orders",[58,254,255],{},"messages",[58,257,258],{},"invoices",". En esas seis viven los datos de\notras personas.",[10,261,262,263,267],{},"Es la comprobación que más encuentra. De las 3.680 apps con Supabase donde\npudimos completarla, 2.096 tenían al menos una tabla que respondía a una\npetición sin sesión, y en 394 de ellas la tabla abierta llevaba nombre de\npersonas. La\n",[42,264,266],{"href":265},"\u002Fblog\u002Fcan-anyone-read-your-supabase-database","medición completa y de qué es una proporción","\nestá en un artículo aparte.",[25,269,271],{"id":270},"comprobación-3-lista-un-bucket-sus-propios-archivos","Comprobación 3: ¿lista un bucket sus propios archivos?",[10,273,274],{},"Pídele a un bucket su contenido y mira si vuelve algo.",[132,276,279],{"className":277,"code":278,"language":137,"meta":138},[135],"curl -s -X POST \\\n  -H \"apikey: TU_CLAVE_PUBLICABLE\" \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -d '{\"prefix\":\"\",\"limit\":1}' \\\n  \"https:\u002F\u002FTU_PROYECTO.supabase.co\u002Fstorage\u002Fv1\u002Fobject\u002Flist\u002Favatars\"\n",[58,280,278],{"__ignoreMap":138},[10,282,283,286],{},[17,284,285],{},"Envía el body."," Un POST a esa dirección sin nada dentro responde \"Body cannot\nbe empty\" para cualquier bucket de cualquier proyecto, esté configurado como\nesté. Lo sabemos porque nuestra propia comprobación de buckets se publicó así y\npasó meses informando alegremente de que nada era listable.",[10,288,289],{},"Dos formas de respuesta:",[67,291,292,298],{},[70,293,294,297],{},[58,295,296],{},"[]"," significa que el bucket está cerrado, o que no hay ningún bucket con ese\nnombre. Desde fuera no puedes saber cuál de las dos, así que una lista vacía\nno es prueba de nada.",[70,299,300],{},"Un array con un archivo dentro significa que un desconocido puede pedirle a tu\nproyecto un directorio de ese bucket y recibirlo.",[10,302,303,304,243,307,243,310,243,313,243,316,243,319,46],{},"Prueba los nombres de bucket que usó tu app, y después los habituales:\n",[58,305,306],{},"avatars",[58,308,309],{},"public",[58,311,312],{},"uploads",[58,314,315],{},"files",[58,317,318],{},"images",[58,320,321],{},"documents",[10,323,324,325,329],{},"Listable y público son dos ajustes distintos guardados en dos sitios distintos,\ny ",[42,326,328],{"href":327},"\u002Fblog\u002Fsupabase-storage-bucket-public","la diferencia decide cuánto importa un bucket público",".\nEl listado es el que hay que cerrar, porque le ahorra a un desconocido el\ntrabajo de adivinar un nombre de archivo. De las 27.269 apps donde pudimos\npreguntar, 792 respondieron con una lista.",[25,331,333],{"id":332},"comprobación-4-se-publica-tu-código-fuente-junto-a-tu-app","Comprobación 4: ¿se publica tu código fuente junto a tu app?",[10,335,336],{},"Lee la última línea de tu bundle de JavaScript.",[10,338,339,340,343],{},"De vuelta en la pestaña Red de la comprobación 1, busca el archivo ",[58,341,342],{},".js"," más\ngrande que cargó tu app y copia su dirección. Después:",[132,345,348],{"className":346,"code":347,"language":137,"meta":138},[135],"curl -s \"https:\u002F\u002Ftu-app.example\u002Fassets\u002Findex-abc123.js\" | tail -c 120\n",[58,349,347],{"__ignoreMap":138},[10,351,352,353,356],{},"Una última línea que diga ",[58,354,355],{},"\u002F\u002F# sourceMappingURL=index-abc123.js.map"," significa\nque tu build escribió un source map y le dijo a los navegadores dónde está. Si\nademás lo publicó lo aclara una petición más:",[132,358,361],{"className":359,"code":360,"language":137,"meta":138},[135],"curl -s -o \u002Fdev\u002Fnull -w \"%{http_code}\\n\" \\\n  \"https:\u002F\u002Ftu-app.example\u002Fassets\u002Findex-abc123.js.map\"\n",[58,362,360],{"__ignoreMap":138},[10,364,365,368,369,46],{},[58,366,367],{},"200"," significa que cualquiera puede descargarlo. Un source map convierte el\nJavaScript comprimido de vuelta en tus archivos originales, con tu estructura de\ncarpetas, tus comentarios y tu lógica intactos. Lo que expone es tu código, y\ncualquier clave que revele ya estaba en el bundle a su lado, que es de lo que\niba la comprobación 1. 3.885 de las 30.987 apps que pudimos revisar publican el\nsuyo, y en algunos builders eso es un ajuste por defecto de la plataforma y no\nuna decisión de nadie:\n",[42,370,372],{"href":371},"\u002Fblog\u002Fsource-maps-exposed-in-production","qué muestra un source map publicado",[25,374,376],{"id":375},"comprobación-5-qué-envía-tu-hosting-con-cada-página","Comprobación 5: qué envía tu hosting con cada página",[10,378,379],{},"Una petición a la dirección de tu propia app, leyendo lo que volvió con ella.",[132,381,384],{"className":382,"code":383,"language":137,"meta":138},[135],"curl -s -i \"https:\u002F\u002Ftu-app.example\" | grep -i -E \\\n  \"content-security-policy|strict-transport-security|x-frame-options|x-content-type-options|referrer-policy\"\n",[58,385,383],{"__ignoreMap":138},[10,387,388],{},"Cinco cabeceras, y las que no se imprimen son las que te faltan. Le dicen a un\nnavegador que rechace la página si se carga dentro del sitio de otro, que la\npróxima vez insista en una conexión cifrada, y que deje de adivinar qué tipo de\narchivo acaba de recibir.",[10,390,391,392,46],{},"Casi todas las apps suspenden esta y casi nadie puede hacer nada al respecto. A\n30.756 de las 30.981 apps que pudimos revisar les faltaba al menos una, porque\nlas envía la plataforma de hosting y quien tiene su app en un subdominio de un\nbuilder no tiene dónde cambiarlas. Lo contamos y lo limitamos a medio:\n",[42,393,395],{"href":394},"\u002Fblog\u002Fmissing-security-headers","qué predicen y qué no las cabeceras que faltan",[25,397,399],{"id":398},"cómo-leer-lo-que-encontraste","Cómo leer lo que encontraste",[10,401,402],{},"Por gravedad, que no es el orden en que las ejecutaste.",[10,404,405,408],{},[17,406,407],{},"Una clave secreta en el bundle va primero."," Se salta todas las reglas que\nescribiste en todas las tablas, así que rótala antes de mirar cualquier otra\ncosa de aquí.",[10,410,411,414,415,243,417,243,419,103,421,423,424,427],{},[17,412,413],{},"Una tabla llena de personas que responde a la comprobación 2 va después."," En\n",[58,416,242],{},[58,418,146],{},[58,420,252],{},[58,422,255],{}," están los datos de otra gente, y\nahora mismo se puede llegar a ellos. Una tabla ",[58,425,426],{},"products"," que responde igual\npuede estar perfectamente bien, y tú eres la única persona capaz de distinguir\nlas dos.",[10,429,430,433],{},[17,431,432],{},"Luego un bucket listable, luego los maps y las cabeceras",", que suelen ser los\nvalores por defecto de tu plataforma y no algo que elegiste.",[10,435,436,437,440,441,443],{},"Una cosa que llevarte de las cinco. ",[17,438,439],{},"Una comprobación que no obtuvo respuesta\nno ha aprobado."," Un ",[58,442,208],{},", una petición que agotó el tiempo, una tabla cuyo\nnombre adivinaste mal: cada una de esas es una pregunta que sigue abierta.\nNuestro escáner escribe \"No se pudo comprobar\" en esas líneas y deja la nota\ncomo está, y hacerlo a mano significa llevar esa lista tú.",[10,445,446],{},"Todo esto describe además la app que estaba en vivo en el momento en que\npreguntaste. Row Level Security se desactiva para que cargue una página, un\nbucket se abre para una subida, una clave se pega para que una función salga\nesta noche.",[25,448,450],{"id":449},"qué-hacer-esta-semana","Qué hacer esta semana",[452,453,454],"key-takeaways",{},[67,455,456,470,473,476,479],{},[70,457,458,459,243,461,243,463,243,465,103,467,469],{},"Ejecuta la comprobación 2 contra cada tabla que nombra tu app, y después contra ",[58,460,242],{},[58,462,146],{},[58,464,249],{},[58,466,252],{},[58,468,255],{},". Ahí están los hallazgos.",[70,471,472],{},"Si la comprobación 1 sacó una clave secreta, rótala primero. Borrarla de tu código deja el valor antiguo funcionando para quien ya lo tenga.",[70,474,475],{},"Anota cada sonda que recibió un rechazo o ninguna respuesta. Esas quedan sin responder, y una pregunta sin responder no es un resultado limpio.",[70,477,478],{},"Deja en paz las tablas que deben ser públicas. Una lista de productos que responde a un desconocido es tu app funcionando bien.",[70,480,481],{},"Vuelve a ejecutar las cinco después de un deploy y después de que alguien cambie una regla de la base de datos. Ninguna de las dos cosas se anuncia sola.",[119,483],{"alt":484,"caption":485,"src":486},"Un informe de Reeve. Un recuadro ámbar grande con la letra C, el número 85 sobre 100 debajo, y nueve filas a la derecha, cada una con un icono de comprobación y una barra. Siete filas llevan una marca verde, la fila de almacenamiento lleva una cruz roja, y la fila de la base de datos lleva un hueco discontinuo vacío en lugar de marca.","Las mismas cinco comprobaciones, y cuatro más, devueltas como informe. La puntuación es 85 y la nota es C, porque un hallazgo alto limita la letra diga lo que diga la aritmética. La fila discontinua es la base de datos, que nunca respondió.","\u002Fblog\u002Fsupabase-security-checker\u002Fgrade-score-and-checks-1600x760.png",[10,488,489,490,494],{},"Nuestro ",[42,491,493],{"href":492},"\u002Fsecurity-scanner","escáner de seguridad gratuito"," ejecuta estas cinco y\ncuatro más contra cualquier URL en vivo en unos veinte segundos, sin cuenta y\nsin que entregues credenciales. Lee, nunca escribe, y donde no consigue\nrespuesta lo dice en la línea.",[10,496,497,498,46],{},"Las cinco puedes hacerlas tú hoy. Lo que ninguna pasada suelta puede decirte es\nsi las respuestas siguen siendo las mismas el mes que viene, y para eso\nconstruimos Reeve Care: repite estas comprobaciones según un calendario, te\nescribe cuando una respuesta empeora, y guarda copias verificadas de tu base de\ndatos de Supabase, además de los archivos que suben tus usuarios en cuanto\nconectas una credencial de almacenamiento.\n",[42,499,501],{"href":500},"\u002F#pricing","Qué vigila y cuánto cuesta",[10,503,504,505,509],{},"Si una terminal no es donde quieres estar, la guía en lenguaje claro para\n",[42,506,508],{"href":507},"\u002Fis-your-supabase-app-safe","apps con Supabase"," explica para qué sirve cada uno\nde estos ajustes y dónde encontrarlo en el panel.",{"title":138,"searchDepth":511,"depth":511,"links":512},3,[513,515,516,517,518,519,520,521],{"id":27,"depth":514,"text":28},2,{"id":49,"depth":514,"text":50},{"id":126,"depth":514,"text":127},{"id":270,"depth":514,"text":271},{"id":332,"depth":514,"text":333},{"id":375,"depth":514,"text":376},{"id":398,"depth":514,"text":399},{"id":449,"depth":514,"text":450},"Fundamentos de seguridad","\u002Fblog\u002Fsupabase-security-checker\u002Fcover-1200x630.png","Una ventana de terminal vista de frente, con cinco comandos cortos escritos bajo un prompt verde y un cursor esperando en la sexta línea.","Un Supabase security checker lee tu app publicada en lugar de la configuración de tu proyecto. Estas son las cinco comprobaciones y cómo hacer cada una.",false,"md",[529,532,535,538,541],{"q":530,"a":531},"¿El Security Advisor de Supabase lo detecta todo?","Detecta todo lo de su propia mitad. El Advisor lee la configuración de tu proyecto e informa de tablas con Row Level Security desactivado, funciones con un search path laxo y ajustes parecidos que controlas desde el panel. Lo que no puede ver es tu app publicada: qué clave acabó en el JavaScript que descargan tus visitantes, si una política que escribiste detiene de verdad una petición anónima, o qué cabeceras envía tu hosting. Ejecuta el Advisor y las cinco comprobaciones de este artículo, porque miran cosas distintas.",{"q":533,"a":534},"¿Es seguro ejecutar estas comprobaciones contra mi propio proyecto?","Sí. Todos los comandos de aquí leen y ninguno escribe. La comprobación de tablas le pide a tu base de datos un recuento de filas y lee el número de una cabecera de respuesta, así que nunca se descarga una fila. La de buckets pide un listado y se detiene ahí, sin descargar ningún archivo. Las dos últimas son una petición de página corriente, del tipo que tus visitantes hacen todo el día. Ejecutarlas contra un proyecto que no es tuyo es otra cuestión, y la respuesta es pedir permiso primero.",{"q":536,"a":537},"Mi anon key está en mi bundle. ¿Es un problema?","No. La anon key en proyectos antiguos, y sb_publishable_ en los nuevos, está pensada para estar en el código que descargan tus visitantes. Nombra tu proyecto y por sí sola no concede nada; Row Level Security es lo que decide qué filas recibe una petición. La clave que nunca debe estar ahí es la secreta, llamada service_role en proyectos antiguos y sb_secret_ en los nuevos, porque se salta todas las reglas que escribiste.",{"q":539,"a":540},"¿Qué significa content-range: *\u002F0?","Es tu base de datos diciendo que a este solicitante le entregará cero filas de esa tabla. El asterisco significa que la respuesta no contiene fila alguna, y el número después de la barra es el recuento que el solicitante puede ver. Así que *\u002F0 es la respuesta que quieres de una tabla con datos personales, y 0-0\u002F128 significa que hay 128 filas al alcance de cualquiera que tenga la clave que viaja en tu app.",{"q":542,"a":543},"¿Necesito mi service_role key para probar esto?","No, y un checker que te la pida está pidiendo lo que no debe. Todas las comprobaciones de aquí usan la clave publicable que ya está en tu app, porque esa es la clave que tendría un desconocido. Probar con una clave secreta te dice qué alcanza un administrador, que nunca estuvo en duda. No pegues una clave service_role ni sb_secret_ en ningún escáner, incluido el nuestro: nada de lo que hacemos la necesita.","\u002Fblog\u002Fsupabase-security-checker\u002Fcard-800x500.png",[546,547,548,549,550,551,552,553,554,555,556,557],"supabase security checker","comprobar seguridad supabase","supabase rls checker","escáner de seguridad supabase","auditoría de seguridad supabase","probar row level security supabase","supabase security advisor","tablas expuestas supabase","mi base de datos supabase es pública","anon key supabase expuesta","comprobar bucket público supabase","escáner de fugas supabase",{},true,"Supabase security checker: las cinco comprobaciones","\u002Fblog\u002Fsupabase-security-checker","2026-09-06",{"title":5,"description":525},"blog\u002Fsupabase-security-checker",[566,567,568],"Un Supabase security checker lee tu app en vivo igual que la lee un desconocido: el bundle que entrega, las respuestas de tus tablas, tus buckets de almacenamiento, tus cabeceras.","El Security Advisor de Supabase lee la otra mitad, que es la configuración de tu proyecto. Ninguno de los dos ve lo que ve el otro.","Cinco comprobaciones cubren la vista desde fuera. Todas se ejecutan desde una terminal, no piden cuenta y juntas llevan unos diez minutos.","7HeDn4UBoc3ucsxbSjFpVlzf3dFlO5R53206wLd6L60",[571,578,584,590,596,602,608,614,620,626,632,638,644,650,656,662,668,674,675,681,686,692,698,704,710,716,722,728,734,740,746,752,757,763,769,774,778,784,790,795,800,806],{"path":572,"title":573,"description":574,"published":575,"category":576,"image":577,"draft":526},"\u002Fblog\u002Fsupabase-backup-auth-users","Por qué tu dump de Supabase no tiene usuarios dentro","Ejecuta supabase db dump por sí solo y obtienes la forma de tu base de datos y ninguna de sus filas, sin el esquema auth donde viven tus usuarios.","2026-09-23","Backups","\u002Fblog\u002Fsupabase-backup-auth-users\u002Fcard-800x500.png",{"path":579,"title":580,"description":581,"published":582,"category":522,"image":583,"draft":526},"\u002Fblog\u002Fdomain-and-certificate-expiry","Dominio caducado, web caída: qué pasa a partir de ahora","Tu dominio caducó y tu web está caída. Aquí tienes el reloj que acaba de arrancar, por qué el certificado es el menor problema, y cómo comprobar ambos.","2026-09-22","\u002Fblog\u002Fdomain-and-certificate-expiry\u002Fcard-800x500.png",{"path":585,"title":586,"description":587,"published":588,"category":522,"image":589,"draft":526},"\u002Fblog\u002Fis-lovable-safe","¿Es seguro Lovable? Lo que vimos en 18.554 aplicaciones Lovable","¿Es seguro Lovable? Pasamos 18.554 aplicaciones Lovable por nueve comprobaciones. La plataforma fue la más limpia de cinco. Los hallazgos estaban dentro.","2026-09-21","\u002Fblog\u002Fis-lovable-safe\u002Fcard-800x500.png",{"path":591,"title":592,"description":593,"published":594,"category":522,"image":595,"draft":526},"\u002Fblog\u002Fvibe-coded-app-security-checklist","La lista de seguridad del vibe coding, en nueve comprobaciones","Una lista de seguridad del vibe coding con nueve puntos, cada uno verificable en tu app desde fuera por cualquiera, y cada uno con una prueba de una línea.","2026-09-20","\u002Fblog\u002Fvibe-coded-app-security-checklist\u002Fcard-800x500.png",{"path":597,"title":598,"description":599,"published":600,"category":522,"image":601,"draft":526},"\u002Fblog\u002Fstripe-secret-key-in-frontend","Una clave secreta de Stripe en tu frontend puede mover dinero","Una clave secreta de Stripe expuesta en tu frontend puede reembolsar, cobrar y leer cada ficha de cliente que tienes. Tu clave pk_live_ sí debe estar ahí.","2026-09-19","\u002Fblog\u002Fstripe-secret-key-in-frontend\u002Fcard-800x500.png",{"path":603,"title":604,"description":605,"published":606,"category":522,"image":607,"draft":526},"\u002Fblog\u002Fvite-and-next-public-env-vars","Variables de entorno de Vite expuestas: el prefijo dice publica esto","Variables de entorno de Vite expuestas en tu app: hicieron lo que pedía el prefijo. VITE_ y NEXT_PUBLIC_ significan publica esto, y la IA nunca supo el coste.","2026-09-18","\u002Fblog\u002Fvite-and-next-public-env-vars\u002Fcard-800x500.png",{"path":609,"title":610,"description":611,"published":612,"category":522,"image":613,"draft":526},"\u002Fblog\u002Fbase44-source-maps","Seguridad en Base44: qué marca un escaneo y qué te toca arreglar a ti","Seguridad en Base44 en 5.438 apps escaneadas: tres hallazgos en casi todas son de la plataforma, el source map es la insignia de Base44 y lo tuyo es poco.","2026-09-17","\u002Fblog\u002Fbase44-source-maps\u002Fcard-800x500.png",{"path":615,"title":616,"description":617,"published":618,"category":522,"image":619,"draft":526},"\u002Fblog\u002Fis-cursor-ai-safe","¿Es seguro Cursor AI? El editor, el código y la app que publicaste","¿Es seguro Cursor AI? Tres preguntas en una búsqueda: qué guarda Cursor, en qué falla el código que escribe y si la app que publicaste está abierta.","2026-09-16","\u002Fblog\u002Fis-cursor-ai-safe\u002Fcard-800x500.png",{"path":621,"title":622,"description":623,"published":624,"category":522,"image":625,"draft":526},"\u002Fblog\u002Fis-replit-safe","¿Es seguro Replit? Lo que vimos en 3.042 aplicaciones Replit en vivo","¿Es seguro Replit? Pasamos 3.042 aplicaciones Replit en vivo por nueve comprobaciones externas. Los hallazgos estaban en la aplicación publicada, no en el host.","2026-09-15","\u002Fblog\u002Fis-replit-safe\u002Fcard-800x500.png",{"path":627,"title":628,"description":629,"published":630,"category":576,"image":631,"draft":526},"\u002Fblog\u002Fsupabase-storage-backup","Supabase storage backup: tu copia de la base no tiene archivos","Un Supabase storage backup es una tarea aparte. Las copias de la base guardan la lista de tus archivos y ninguno de ellos, así que cada upload queda roto.","2026-09-14","\u002Fblog\u002Fsupabase-storage-backup\u002Fcard-800x500.png",{"path":633,"title":634,"description":635,"published":636,"category":576,"image":637,"draft":526},"\u002Fblog\u002Fsupabase-point-in-time-recovery","Point-in-time recovery de Supabase: precio y límites","El point-in-time recovery de Supabase rebobina tu base de datos a cualquier segundo de la última semana. Cuesta $100 al mes sobre Pro y solo cubre la base.","2026-09-13","\u002Fblog\u002Fsupabase-point-in-time-recovery\u002Fcard-800x500.png",{"path":639,"title":640,"description":641,"published":642,"category":576,"image":643,"draft":526},"\u002Fblog\u002Fsupabase-project-paused-recover","¿Proyecto de Supabase pausado? Tus datos siguen ahí","Supabase pausó tu proyecto tras una semana sin actividad. No se ha borrado nada, Restaurar está junto al nombre y tienes un año antes de que eso cambie.","2026-09-12","\u002Fblog\u002Fsupabase-project-paused-recover\u002Fcard-800x500.png",{"path":645,"title":646,"description":647,"published":648,"category":522,"image":649,"draft":526},"\u002Fblog\u002Fsafest-ai-app-builder","¿Qué builder de apps con IA es más seguro? 30.998 apps escaneadas","¿Qué builder de apps con IA es más seguro? Escaneamos 30.998 apps activas de Lovable, Base44, Replit, v0 y Bolt. El builder no es lo que decide tu nota.","2026-09-11","\u002Fblog\u002Fsafest-ai-app-builder\u002Fcard-800x500.png",{"path":651,"title":652,"description":653,"published":654,"category":522,"image":655,"draft":526},"\u002Fblog\u002Fenable-rls-on-every-supabase-table","Activa Row Level Security en cada tabla de Supabase y compruébalo","Activar Row Level Security en Supabase sin política cierra la tabla del todo. Una política sin el interruptor no hace nada. Aquí tienes el SQL y la prueba.","2026-09-10","\u002Fblog\u002Fenable-rls-on-every-supabase-table\u002Fcard-800x500.png",{"path":657,"title":658,"description":659,"published":660,"category":522,"image":661,"draft":526},"\u002Fblog\u002Fsupabase-rls-disabled-in-public","Supabase \"RLS disabled in public\": lo que el aviso no ve","Supabase marca \"RLS disabled in public\" como error. No dice nada de la política de lectura que deja tu tabla igual de abierta a cualquiera.","2026-09-09","\u002Fblog\u002Fsupabase-rls-disabled-in-public\u002Fcard-800x500.png",{"path":663,"title":664,"description":665,"published":666,"category":522,"image":667,"draft":526},"\u002Fblog\u002Frotate-supabase-service-role-key","Cómo rotar una clave service_role de Supabase filtrada","Supabase dice que primero cierres la fuga. Otras guías dicen que rotes ya. Cuál es lo correcto depende de dónde se filtró tu clave service_role.","2026-09-08","\u002Fblog\u002Frotate-supabase-service-role-key\u002Fcard-800x500.png",{"path":669,"title":670,"description":671,"published":672,"category":522,"image":673,"draft":526},"\u002Fblog\u002Fvibe-coding-security-scanners-compared","Escáneres de seguridad vibe coding comparados, el nuestro incluido","El mejor escáner de seguridad para vibe coding depende de tres preguntas que ninguna lista de funciones responde. Diez herramientas, con precios.","2026-09-07","\u002Fblog\u002Fvibe-coding-security-scanners-compared\u002Fcard-800x500.png",{"path":561,"title":5,"description":525,"published":562,"category":522,"image":544,"draft":526},{"path":676,"title":677,"description":678,"published":679,"category":522,"image":680,"draft":526},"\u002Fblog\u002Fvibe-coding-security-scanner","Escáner de seguridad para vibe coding: qué se le escapa","Un escáner de seguridad para vibe coding lee tu app en vivo desde fuera. Qué cubre eso, las cuatro cosas que no puede ver y cómo leer el resultado.","2026-09-05","\u002Fblog\u002Fvibe-coding-security-scanner\u002Fcard-800x500.png",{"path":394,"title":682,"description":683,"published":684,"category":522,"image":685,"draft":526},"Faltan cabeceras de seguridad: cuándo importa de verdad","Que falten cabeceras de seguridad es el hallazgo que más imprime nuestro escáner. Contra qué protegen y cuándo son la línea menos urgente de tu informe.","2026-09-04","\u002Fblog\u002Fmissing-security-headers\u002Fcard-800x500.png",{"path":687,"title":688,"description":689,"published":690,"category":522,"image":691,"draft":526},"\u002Fblog\u002Fopenai-api-key-exposed-in-frontend","Tu clave de API de OpenAI está expuesta en tu frontend. Rótala.","Una clave de API de OpenAI expuesta en tu frontend no se puede atar a un dominio. Rótala hoy, mueve la llamada a tu servidor y limita el gasto.","2026-09-03","\u002Fblog\u002Fopenai-api-key-exposed-in-frontend\u002Fcard-800x500.png",{"path":693,"title":694,"description":695,"published":696,"category":522,"image":697,"draft":526},"\u002Fblog\u002Fwhat-secrets-leak-from-vibe-coded-apps","Clave de API expuesta en el frontend: qué llevan 30.998 apps","Una clave de API expuesta en tu frontend suele ser una clave de Google Maps. Escaneamos 30.998 apps vibe-coded y contamos qué secretos se filtran de verdad.","2026-09-02","\u002Fblog\u002Fwhat-secrets-leak-from-vibe-coded-apps\u002Fcard-800x500.png",{"path":699,"title":700,"description":701,"published":702,"category":576,"image":703,"draft":526},"\u002Fblog\u002Fsupabase-backup-tools-compared","Herramientas de backup de Supabase comparadas, la nuestra incluida","Cuatro tipos de herramienta de backup de Supabase, qué copia cada una y cuándo una GitHub Action gratuita le gana a pagar a cualquiera, nosotros incluidos.","2026-09-01","\u002Fblog\u002Fsupabase-backup-tools-compared\u002Fcard-800x500.png",{"path":705,"title":706,"description":707,"published":708,"category":522,"image":709,"draft":526},"\u002Fblog\u002Freplit-secrets-explained","Cómo usar secrets en Replit, y qué se publica de todos modos","Cómo usar secrets en Replit: añadir uno, leerlo y arreglar las dos causas del undefined. Y las claves que la herramienta Secrets no puede mantener privadas.","2026-08-31","\u002Fblog\u002Freplit-secrets-explained\u002Fcard-800x500.png",{"path":711,"title":712,"description":713,"published":714,"category":576,"image":715,"draft":526},"\u002Fblog\u002Fback-up-supabase-free-tier","Backups del plan gratuito de Supabase: cómo hacer uno sin terminal","No hay backups en el plan gratuito de Supabase: la copia tienes que hacerla tú. Cómo sacarla desde el panel y qué deja fuera la vía CSV.","2026-08-30","\u002Fblog\u002Fback-up-supabase-free-tier\u002Fcard-800x500.png",{"path":717,"title":718,"description":719,"published":720,"category":522,"image":721,"draft":526},"\u002Fblog\u002Fis-supabase-secure","¿Supabase es seguro? Sí. Tu proyecto es otra pregunta","¿Supabase es seguro? La plataforma está auditada, cifrada y sometida a pentests. Su propia documentación dice dónde termina eso y empiezan tus ajustes.","2026-08-29","\u002Fblog\u002Fis-supabase-secure\u002Fcard-800x500.png",{"path":723,"title":724,"description":725,"published":726,"category":522,"image":727,"draft":526},"\u002Fblog\u002Fwhere-to-find-supabase-api-keys","Dónde están tus claves API de Supabase: anon, service_role y URL","La URL del proyecto, la clave anon y la clave service_role están en una sola página del panel. Aquí tienes dónde está, y cuál de los cuatro va en tu app.","2026-08-28","\u002Fblog\u002Fwhere-to-find-supabase-api-keys\u002Fcard-800x500.png",{"path":729,"title":730,"description":731,"published":732,"category":522,"image":733,"draft":526},"\u002Fblog\u002Fnew-row-violates-row-level-security-policy","New row violates row-level security policy en Supabase. ¿Y ahora?","\"New row violates row-level security policy\" significa que Supabase rechazó una escritura. El arreglo rápido vuelve a abrir la tabla para cualquiera.","2026-08-27","\u002Fblog\u002Fnew-row-violates-row-level-security-policy\u002Fcard-800x500.png",{"path":735,"title":736,"description":737,"published":738,"category":522,"image":739,"draft":526},"\u002Fblog\u002Fcors-wildcard-security-risk","¿Es un comodín CORS un riesgo de seguridad? Casi nunca.","¿Es un comodín CORS un riesgo de seguridad? Casi siempre es el ajuste por defecto de tu builder y no entrega nada que tu servidor no diera ya a cualquiera.","2026-08-26","\u002Fblog\u002Fcors-wildcard-security-risk\u002Fcard-800x500.png",{"path":741,"title":742,"description":743,"published":744,"category":576,"image":745,"draft":526},"\u002Fblog\u002Fsupabase-branching-is-not-a-backup","Supabase branching no es un backup. Solo va hacia adelante.","Por qué el branching de Supabase no es un backup: una rama arranca sin tus datos y un merge solo mueve el esquema. Para qué sirve y qué usar en su lugar.","2026-08-25","\u002Fblog\u002Fsupabase-branching-is-not-a-backup\u002Fcard-800x500.png",{"path":747,"title":748,"description":749,"published":750,"category":576,"image":751,"draft":526},"\u002Fblog\u002Fhow-to-restore-a-supabase-backup","Cómo restaurar un backup de Supabase, y qué se rompe después","Cómo restaurar un backup de Supabase desde el panel o desde un archivo de volcado, qué reemplaza la restauración y por qué tu app puede seguir rota al terminar.","2026-08-24","\u002Fblog\u002Fhow-to-restore-a-supabase-backup\u002Fcard-800x500.png",{"path":327,"title":753,"description":754,"published":755,"category":522,"image":756,"draft":526},"Tu bucket de Supabase Storage es público. ¿Es un problema?","Un bucket público de Supabase Storage significa que quien tenga la URL de un archivo puede abrirlo. No significa que alguien pueda listar lo que hay dentro.","2026-08-23","\u002Fblog\u002Fsupabase-storage-bucket-public\u002Fcard-800x500.png",{"path":758,"title":759,"description":760,"published":761,"category":522,"image":762,"draft":526},"\u002Fblog\u002Fgoogle-api-key-exposed-in-frontend","¿Es un problema una clave de API de Google en tu frontend?","Una clave de API de Google en tu frontend es la clave que más encuentra nuestro escáner, y casi siempre no pasa nada. Un ajuste gratuito decide cuál es cuál.","2026-08-22","\u002Fblog\u002Fgoogle-api-key-exposed-in-frontend\u002Fcard-800x500.png",{"path":764,"title":765,"description":766,"published":767,"category":576,"image":768,"draft":526},"\u002Fblog\u002Fai-agent-deleted-my-database","Un agente de IA borró mis datos de Supabase. ¿Qué puedo recuperar?","Un agente de IA borró datos de tu base. Lo que puedes recuperar ya estaba decidido antes de que se ejecutara, y los próximos minutos deciden el resto.","2026-08-21","\u002Fblog\u002Fai-agent-deleted-my-database\u002Fcard-800x500.png",{"path":265,"title":770,"description":771,"published":772,"category":522,"image":773,"draft":526},"¿Puede cualquiera leer tu base de datos Supabase? 3.680 apps","¿Puede cualquiera leer tu base de datos Supabase sin iniciar sesión? Escaneamos 30.998 apps activas hechas con builders de IA y medimos cuántas dicen que sí.","2026-08-18","\u002Fblog\u002Fcan-anyone-read-your-supabase-database\u002Fcard-800x500.png",{"path":371,"title":775,"description":776,"published":772,"category":522,"image":777,"draft":526},"Source maps expuestos: tu app está publicando su código original","Un source map expuesto deja que cualquiera lea el código original de tu app, comentarios incluidos. La comprobación de 30 segundos y lo que de verdad importa.","\u002Fblog\u002Fsource-maps-exposed-in-production\u002Fcard-800x500.png",{"path":779,"title":780,"description":781,"published":782,"category":522,"image":783,"draft":526},"\u002Fblog\u002Fsupabase-new-api-keys","Las nuevas claves de API de Supabase: ¿cuál va en tu app?","Supabase sustituyó anon y service_role por claves publicables y secretas. ¿Cuál pertenece a tu app y cuál no debe estar nunca?","2026-08-12","\u002Fblog\u002Fsupabase-new-api-keys\u002Fcard-800x500.png",{"path":785,"title":786,"description":787,"published":788,"category":576,"image":789,"draft":526},"\u002Fblog\u002Fdoes-supabase-back-up-my-database","¿Supabase respalda mi base de datos? Depende de tu plan.","¿Supabase respalda tu base de datos? A diario en los planes de pago, y nada en el gratuito. Cómo saber cuál tienes y qué es lo que esa copia no cubre.","2026-08-11","\u002Fblog\u002Fdoes-supabase-back-up-my-database\u002Fcard-800x500.png",{"path":44,"title":791,"description":792,"published":793,"category":522,"image":794,"draft":526},"Supabase Row Level Security está activado. Tu tabla sigue pública.","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.","2026-08-10","\u002Fblog\u002Fsupabase-rls-on-but-table-still-public\u002Fcard-800x500.png",{"path":796,"title":797,"description":798,"published":793,"category":576,"image":799,"draft":526},"\u002Fblog\u002Fversion-history-is-not-a-backup","El historial de versiones no es un backup. No recupera una tabla.","Lovable y Bolt guardan un historial de versiones de tu código. Tu base de datos es un servicio aparte, y volver atrás no te devuelve tus datos.","\u002Fblog\u002Fversion-history-is-not-a-backup\u002Fcard-800x500.png",{"path":801,"title":802,"description":803,"published":804,"category":576,"image":805,"draft":526},"\u002Fblog\u002Fthree-ways-to-back-up-a-supabase-database","Tres formas de respaldar una base Supabase, y qué falta en cada una","El panel, pg_dump y un servicio gestionado. Qué guarda de verdad cada uno, qué deja fuera sin avisar y cuál sobrevive a perder la cuenta.","2026-08-09","\u002Fblog\u002Fthree-ways-to-back-up-a-supabase-database\u002Fcard-800x500.png",{"path":110,"title":807,"description":808,"published":804,"category":522,"image":809,"draft":526},"Qué claves de API son seguras en el navegador y cuáles no","Tu clave anon de Supabase está pensada para ser pública. Tu clave service_role no, y se salta todas las reglas que pongas. Cómo distinguirlas.","\u002Fblog\u002Fwhich-api-keys-are-safe-in-your-frontend\u002Fcard-800x500.png",1790150954103]