[{"data":1,"prerenderedAt":1276},["ShallowReactive",2],{"blog-es-invalid-claim-missing-sub-claim":3,"blog-index-es":751},{"id":4,"title":5,"body":6,"category":709,"cover":710,"coverAlt":711,"description":712,"draft":713,"extension":714,"faq":715,"image":731,"keywords":732,"meta":739,"navigation":740,"ogTitle":741,"path":742,"published":743,"seo":744,"stem":745,"tldr":746,"updated":743,"__hash__":750},"blog_es\u002Fblog\u002Finvalid-claim-missing-sub-claim.md","Supabase \"invalid claim: missing sub claim\": no hay nadie conectado",{"type":7,"value":8,"toc":696},"minimark",[9,13,24,36,41,44,59,77,83,86,97,103,107,110,119,129,135,142,148,174,182,186,189,203,211,219,226,236,260,265,269,272,283,331,342,345,349,352,361,367,383,389,403,407,410,429,436,446,450,453,567,585,589,595,604,618,628,645,649,681],[10,11,12],"p",{},"Tu app tiene una Supabase Edge Function que hace algo por la persona que la usa,\ncomo guardar sus ajustes o llamar a OpenAI en su nombre. Mientras la probabas,\nfuncionaba. Luego el registro de la función empieza a llenarse con esta línea, y\nla app muestra un error o nada en absoluto:",[14,15,20],"pre",{"className":16,"code":18,"language":19},[17],"language-text","AuthApiError: invalid claim: missing sub claim\n","text",[21,22,18],"code",{"__ignoreMap":23},"",[10,25,26,27,30,31,35],{},"Busca \"invalid claim: missing sub claim\" y te dirán que regeneres tus claves,\nque desactives Verify JWT o que pongas la clave ",[21,28,29],{},"service_role",". Cada uno de esos\nconsejos trata el token como si estuviera roto. ",[32,33,34],"strong",{},"El token estaba bien.","\nSupabase comprobó su firma y lo aceptó. Lo que le faltaba era un nombre,\ncualquiera.",[37,38,40],"h2",{"id":39},"qué-significa-invalid-claim-missing-sub-claim","¿Qué significa \"invalid claim: missing sub claim\"?",[10,42,43],{},"A Supabase Auth le preguntaron qué usuario llama; encontró el token auténtico y\nningún usuario escrito en él.",[10,45,46,47,50,51,58],{},"Los tokens que lee Supabase Auth son JWT: tres bloques de texto separados por\npuntos, donde el bloque del medio son datos legibles. Cada elemento de esos\ndatos se llama claim. ",[21,48,49],{},"sub",", abreviatura de subject, es el claim que guarda el\nid de un usuario, y la\n",[52,53,57],"a",{"href":54,"rel":55},"https:\u002F\u002Fsupabase.com\u002Fdocs\u002Fguides\u002Fauth\u002Fjwt-fields",[56],"nofollow","referencia de JWT de Supabase","\nlo describe como el ID del usuario, un UUID.",[10,60,61,62,65,66,69,70,72,73,76],{},"El mensaje está escrito en el propio código fuente de Supabase Auth, en una\nfunción llamada ",[21,63,64],{},"maybeLoadUserOrSession"," dentro de ",[21,67,68],{},"internal\u002Fapi\u002Fauth.go",".\nCuando esa función se ejecuta, la firma del token ya se comprobó y pasó.\nEntonces busca ",[21,71,49],{},", y cuando el claim está vacío se detiene con un HTTP 403,\nel código de error ",[21,74,75],{},"bad_jwt"," y esta frase. La respuesta que recibe tu código se\nve así:",[14,78,81],{"className":79,"code":80,"language":19},[17],"{\"code\":403,\"error_code\":\"bad_jwt\",\"msg\":\"invalid claim: missing sub claim\"}\n",[21,82,80],{"__ignoreMap":23},[10,84,85],{},"Imagina una recepción a la que se le hace una sola pregunta: ¿qué visitante es\neste? Entregas una credencial. La recepción revisa primero el sello, y el sello\nes auténtico. Luego lee la línea del nombre, y la línea del nombre está vacía.\nLa recepción no tiene con qué responder, así que se niega, y la negativa señala\nla línea vacía.",[10,87,88,89,92,93,96],{},"Cualquier llamada que necesite a una persona con sesión iniciada puede recibir\nesta respuesta. ",[21,90,91],{},"getUser()"," y ",[21,94,95],{},"updateUser()"," son las dos en las que más\nprobablemente la verás, y añadir un segundo factor o vincular otro método de\ninicio de sesión pasa por la misma comprobación.",[98,99],"diagram",{"alt":100,"caption":101,"src":102},"Dos credenciales van a la misma recepción. Las dos llevan un sello auténtico y las dos pasan la comprobación del sello. La de arriba, marcada authenticated, tiene cara y nombre, y la recepción responde con una persona y 200. La de abajo, marcada anon, tiene la foto y la línea del nombre vacías y punteadas, y la recepción responde 403.","El sello se comprueba primero, y las dos credenciales pasan. La pregunta que falla es la segunda: de quién es el nombre que lleva.","\u002Fblog\u002Finvalid-claim-missing-sub-claim\u002Fthe-name-line-1600x720.png",[37,104,106],{"id":105},"una-clave-nombra-tu-app-un-token-de-sesión-nombra-a-una-persona","Una clave nombra tu app. Un token de sesión nombra a una persona.",[10,108,109],{},"La clave de API de tu proyecto es la credencial con la línea del nombre vacía, y\nse hizo así a propósito.",[10,111,112,113,118],{},"La ",[52,114,117],{"href":115,"rel":116},"https:\u002F\u002Fsupabase.com\u002Fdocs\u002Fguides\u002Fgetting-started\u002Fapi-keys",[56],"documentación de claves de API","\nlo dice en una línea: según Supabase, una clave de API no distingue entre\nusuarios, sino entre aplicaciones. La clave le dice a Supabase qué app llama.\nQuién está usando esa app es otra pregunta, y la responde un token que Supabase\nAuth emite a cada persona cuando inicia sesión.",[10,120,121,122,125,126,128],{},"La referencia de JWT de Supabase imprime los dos. La clave ",[21,123,124],{},"anon"," de un proyecto\nmás antiguo se decodifica así, y no hay ningún ",[21,127,49],{}," en ella:",[14,130,133],{"className":131,"code":132,"language":19},[17],"{\n  \"iss\": \"supabase\",\n  \"ref\": \"abcdefghijklmnopqrst\",\n  \"role\": \"anon\",\n  \"iat\": 1640991600,\n  \"exp\": 1640995200\n}\n",[21,134,132],{"__ignoreMap":23},[10,136,137,138,141],{},"El token de acceso de una persona con sesión iniciada lleva su id, el rol\n",[21,139,140],{},"authenticated"," y la sesión a la que pertenece (recortado aquí a las líneas que\nimportan):",[14,143,146],{"className":144,"code":145,"language":19},[17],"{\n  \"sub\": \"123e4567-e89b-12d3-a456-426614174000\",\n  \"role\": \"authenticated\",\n  \"session_id\": \"123e4567-e89b-12d3-a456-426614174000\",\n  \"email\": \"user@example.com\"\n}\n",[21,147,145],{"__ignoreMap":23},[10,149,150,151,153,154,157,158,160,161,92,164,167,168,173],{},"La clave ",[21,152,29],{}," es otra vez la primera forma, con\n",[21,155,156],{},"\"role\": \"service_role\""," y tampoco ningún ",[21,159,49],{},". Las claves más nuevas\n",[21,162,163],{},"sb_publishable_",[21,165,166],{},"sb_secret_"," ni siquiera son JWT, pero la\n",[52,169,172],{"href":170,"rel":171},"https:\u002F\u002Fsupabase.com\u002Fdocs\u002Fguides\u002Ftroubleshooting\u002Fauth-error-401-invalid-claim-missing-sub--AFwMR",[56],"página de soluciones de Supabase para este error","\ndice que Auth las reconoce y que tampoco llevan el claim sub. Así que cualquier\nclave de tu proyecto, antigua o nueva, pública o secreta, produce este mensaje\ncuando se le entrega a Auth como si fuera una persona. Esa misma página nombra\nel error tal cual: usar sin querer una clave de API de Supabase en lugar del\ntoken de acceso de Auth.",[10,175,176,177,181],{},"Si las claves y sus prefijos son nuevos para ti,\n",[52,178,180],{"href":179},"\u002Fblog\u002Fwhich-api-keys-are-safe-in-your-frontend","qué claves de API pueden ir en tu frontend","\nexplica cómo distinguir la pública de la secreta.",[37,183,185],{"id":184},"dónde-se-envía-la-clave-en-lugar-de-una-sesión","Dónde se envía la clave en lugar de una sesión",[10,187,188],{},"Tres caminos llevan hasta aquí, y el primero es el que toma una Edge Function.",[10,190,191,194,195,198,199,202],{},[32,192,193],{},"1. Una Edge Function llamada desde una página sin nadie con sesión iniciada.","\nCuando supabase-js tiene una sesión, pone el token de la persona en la cabecera\n",[21,196,197],{},"Authorization",". Cuando no la tiene, no tiene otra cosa que poner ahí, así que\ncon una clave antigua envía en esa cabecera la clave de tu proyecto. Su archivo\n",[21,200,201],{},"fetch.ts"," se lee exactamente así: el token de sesión si lo hay, si no, la\nclave.",[10,204,205,206,210],{},"La comprobación Verify JWT de la plataforma deja pasar esa petición. Una clave\nantigua es un JWT que firmó tu proyecto, y Supabase documenta que la\ncomprobación también acepta las claves nuevas: es el hueco que\n",[52,207,209],{"href":208},"\u002Fblog\u002Fmove-a-secret-to-an-edge-function","mover un secreto a una Edge Function","\nexplica completo. Después tu función pasa la cabecera a Auth, que es lo que\nhacen estas líneas:",[14,212,217],{"className":213,"code":215,"language":216,"meta":23},[214],"language-ts","const supabase = createClient(\n  Deno.env.get('SUPABASE_URL')!,\n  Deno.env.get('SUPABASE_ANON_KEY')!,\n  { global: { headers: { Authorization: req.headers.get('Authorization')! } } },\n)\nconst { data: { user } } = await supabase.auth.getUser()\n","ts",[21,218,215],{"__ignoreMap":23},[10,220,221,222,225],{},"Auth lee la clave, encuentra la línea del nombre vacía y responde 403. Si la\nfunción devuelve ese fallo, tu app muestra ",[21,223,224],{},"Edge Function returned a non-2xx status code",", con este mensaje esperando en el registro de la función.",[10,227,228,231,232,235],{},[32,229,230],{},"2. Una llamada hecha antes de que terminara el inicio de sesión."," La página\nde soluciones de Supabase también nombra este caso: un usuario que había\nempezado a registrarse o a iniciar sesión y no lo había completado cuando se\nllamó a ",[21,233,234],{},"getUser",". Una forma que toma es un registro que todavía espera su\ncorreo de confirmación. Aún no hay sesión, así que lo que llegó a Auth no era el\ntoken de una persona.",[10,237,238,241,242,245,246,249,250,253,254,256,257,259],{},[32,239,240],{},"3. Tu propio servidor le pasa a Auth el valor equivocado."," Una ruta que llama\na ",[21,243,244],{},"getUser(token)"," con la clave en ",[21,247,248],{},"token",", o una automatización en n8n, Make o\nPostman que llama a ",[21,251,252],{},"\u002Fauth\u002Fv1\u002Fuser"," con la clave en su cabecera\n",[21,255,197],{},". La solución que da la página de Supabase para este caso es\nasegurarse de que el token que se pasa a ",[21,258,91],{}," sea el token de acceso de\nla persona.",[98,261],{"alt":262,"caption":263,"src":264},"Una página sin nadie con sesión iniciada envía dos credenciales vacías idénticas, una marcada apikey y otra Authorization. Las dos pasan la comprobación de la plataforma con una marca y llegan a la Edge Function. La función pasa la credencial de Authorization a la recepción, que responde 403 con una cruz.","Sin nadie con sesión iniciada, la página envía la misma clave en las dos cabeceras. Cada comprobación antes de Auth la deja pasar, y solo la pregunta de Auth encuentra vacía la línea del nombre.","\u002Fblog\u002Finvalid-claim-missing-sub-claim\u002Fthe-same-key-twice-1600x760.png",[37,266,268],{"id":267},"cómo-ver-qué-recibió-tu-función","Cómo ver qué recibió tu función",[10,270,271],{},"Compara dos cabeceras de la petición en tu navegador.",[10,273,274,275,278,279,282],{},"Abre tu app publicada, pulsa F12, elige la pestaña ",[32,276,277],{},"Network"," y repite lo que\nfalló. Haz clic en la petición cuya dirección contiene ",[21,280,281],{},"\u002Ffunctions\u002Fv1\u002F"," y el\nnombre de tu función, y lee sus cabeceras de petición:",[284,285,286,303,314],"ul",{},[287,288,289,302],"li",{},[32,290,291,293,294,297,298,301],{},[21,292,197],{}," lleva ",[21,295,296],{},"Bearer"," y después la misma cadena que ",[21,299,300],{},"apikey","."," La\npágina no tenía sesión que enviar, así que envió la clave dos veces. Estás en\nel camino 1 o 2.",[287,304,305,313],{},[32,306,307,309,310,301],{},[21,308,197],{}," lleva otra cadena larga que empieza por ",[21,311,312],{},"eyJ"," Salió el\ntoken de una persona. La función o tu servidor lo perdió por el camino, así que\nestás en el camino 3.",[287,315,316,321,322,326,327,330],{},[32,317,318,319,301],{},"No hay cabecera ",[21,320,197],{}," Entonces no es este el mensaje que vas a\nver. Con Verify JWT activado, la plataforma rechaza la petición con un 401\nantes de que corra tu código, y\n",[52,323,325],{"href":324},"\u002Fblog\u002Fedge-function-returned-a-non-2xx-status-code","el artículo del error non-2xx","\nlo lee. Desactivado, Auth responde ",[21,328,329],{},"This endpoint requires a valid Bearer token",", la primera fila de la tabla de más abajo.",[10,332,333,334,337,338,301],{},"Si el mensaje está en cambio en la consola de tu navegador y dice\n",[21,335,336],{},"Auth session missing!",", ninguna petición llegó a Auth: supabase-js no encontró\nninguna sesión y se detuvo antes de enviar una, y eso lo cubre\n",[52,339,341],{"href":340},"\u002Fblog\u002Fauth-session-missing","su propio artículo",[10,343,344],{},"Lee los valores donde están. No pegues ninguno de los dos en una web que ofrezca\ndecodificarlo: tu clave es pública de todos modos, pero el token de acceso de\nuna persona permite a quien lo tenga actuar como esa persona hasta que caduque.",[37,346,348],{"id":347},"la-solución-preguntar-solo-cuando-hay-alguien-con-sesión-iniciada","La solución: preguntar solo cuando hay alguien con sesión iniciada",[10,350,351],{},"Decide qué debe hacer la función con un visitante que no ha iniciado sesión, y\nescribe esa decisión en la función.",[10,353,354,357,358,360],{},[32,355,356],{},"Si solo deben usarla personas con sesión iniciada,"," dos cambios. La página\nllama a la función solo cuando alguien ha iniciado sesión. Y la función responde\nun 401 claro cuando ",[21,359,91],{}," no encuentra ningún usuario, para que una\nllamada suelta desde una página sin sesión reciba una negativa clara en vez de\nun fallo:",[14,362,365],{"className":363,"code":364,"language":216,"meta":23},[214],"const { data: { user } } = await supabase.auth.getUser()\nif (!user) {\n  return new Response(JSON.stringify({ error: 'Sign in first' }), {\n    status: 401,\n    headers: { ...corsHeaders, 'Content-Type': 'application\u002Fjson' },\n  })\n}\n",[21,366,364],{"__ignoreMap":23},[10,368,369,370,374,375,378,379,382],{},"Mantén también las cabeceras CORS en esa respuesta, o el navegador se la oculta\na tu app y te encuentras con\n",[52,371,373],{"href":372},"\u002Fblog\u002Ffailed-to-send-a-request-to-the-edge-function","\"Failed to send a request to the Edge Function\"","\nen su lugar. Supabase también ofrece un wrapper, ",[21,376,377],{},"withSupabase({ auth: 'user' })",",\nque hace la misma comprobación antes de que corra tu handler; el\n",[52,380,381],{"href":208},"artículo de la Edge Function"," muestra\ncómo montarlo.",[10,384,385,388],{},[32,386,387],{},"Si también deben usarla visitantes sin sesión,"," pregunta a Auth solo en el\ncamino que necesita a una persona. Trata una negativa ahí como \"esto es un\nvisitante\" y sigue con lo que un visitante puede hacer, en vez de dejar que el\nerror detenga la petición.",[10,390,391,394,395,398,399,402],{},[32,392,393],{},"En tu propio servidor,"," envía el token de acceso de la persona desde el\nnavegador, como describe la página de Supabase: ponlo en una cabecera\n",[21,396,397],{},"Authorization: Bearer"," de tu petición, léelo en el servidor y llama a\n",[21,400,401],{},"supabase.auth.getUser(token)"," con ese valor. La clave de tu proyecto nunca\nentra en esa llamada.",[37,404,406],{"id":405},"dos-arreglos-que-lo-empeoran","Dos arreglos que lo empeoran",[10,408,409],{},"Los dos se sugieren para este error, y los dos te dejan peor que el propio error.",[10,411,412,415,416,418,419,421,422,424,425,301],{},[32,413,414],{},"Poner la clave secreta en su lugar."," No puede ayudar, porque ",[21,417,29],{}," y\n",[21,420,166],{}," tampoco llevan ",[21,423,49],{},", así que Auth responde igual. El daño está en\ndónde acaba la clave. Pídele a un builder que \"use la clave de servicio\" para\nsaltarse un error y puede que la ponga en la página que descargan tus\nvisitantes, donde lee y escribe cada fila de tu base de datos digan lo que digan\ntus reglas. Si eso ya pasó, ",[52,426,428],{"href":427},"\u002Fblog\u002Frotate-supabase-service-role-key","rótala primero",[10,430,431,432,301],{},"Ese desliz se ve desde fuera. Nuestro escaneo gratuito lee el JavaScript de tu\napp publicada como lo haría un extraño y nombra cada clave que encuentra,\nincluido si es una que debe estar ahí. Tarda unos 20 segundos y no necesita\ncuenta: ",[52,433,435],{"href":434},"\u002Fsecurity-scanner","escanea tu app",[10,437,438,441,442,445],{},[32,439,440],{},"Quitar la comprobación y tomar el id del usuario de la petición."," Una función\nque lee ",[21,443,444],{},"userId"," del cuerpo de la petición cree a quien lo envíe. Si además esa\nfunción escribe con la clave secreta, cualquiera que abra tu página y lea cómo se\nllama a la función puede actuar como cualquiera de tus usuarios, y nada en tus\nregistros dice que no fueron ellos. Con la comprobación en su sitio, un extraño\nque intente la misma llamada recibe este error y nada más.",[37,447,449],{"id":448},"los-otros-mensajes-de-la-misma-comprobación","Los otros mensajes de la misma comprobación",[10,451,452],{},"Las comprobaciones de Auth corren en un orden fijo y cada una tiene su propia\nfrase, así que el mensaje te dice hasta dónde llegó el token.",[454,455,456,472],"table",{},[457,458,459],"thead",{},[460,461,462,466,469],"tr",{},[463,464,465],"th",{},"Mensaje",[463,467,468],{},"Estado y código",[463,470,471],{},"Qué pasó",[473,474,475,493,508,522,537,552],"tbody",{},[460,476,477,482,488],{},[478,479,480],"td",{},[21,481,329],{},[478,483,484,485],{},"401 ",[21,486,487],{},"no_authorization",[478,489,490,491,301],{},"No llegó ninguna cabecera ",[21,492,397],{},[460,494,495,500,505],{},[478,496,497],{},[21,498,499],{},"invalid JWT: unable to parse or verify signature, …",[478,501,502,503],{},"403 ",[21,504,75],{},[478,506,507],{},"El sello está mal: un token dañado, uno de otro proyecto o una clave antigua.",[460,509,510,515,519],{},[478,511,512],{},[21,513,514],{},"invalid claim: missing sub claim",[478,516,502,517],{},[21,518,75],{},[478,520,521],{},"El sello está bien y la línea del nombre, vacía: una clave de API.",[460,523,524,529,534],{},[478,525,526],{},[21,527,528],{},"invalid claim: sub claim must be a UUID",[478,530,531,532],{},"400 ",[21,533,75],{},[478,535,536],{},"Hay un nombre, pero no tiene la forma de un id de usuario de Supabase.",[460,538,539,544,549],{},[478,540,541],{},[21,542,543],{},"User from sub claim in JWT does not exist",[478,545,502,546],{},[21,547,548],{},"user_not_found",[478,550,551],{},"El token nombra a un usuario que ya fue eliminado.",[460,553,554,559,564],{},[478,555,556],{},[21,557,558],{},"Session from session_id claim in JWT does not exist",[478,560,502,561],{},[21,562,563],{},"session_not_found",[478,565,566],{},"El token sobrevivió a su sesión, por ejemplo tras cerrar sesión en otro dispositivo.",[10,568,569,570,572,573,576,577,580,581,301],{},"Cada fila sale del mismo archivo, ",[21,571,68],{},". La versión que da tu\npropia base de datos para la fila del sello equivocado, ",[21,574,575],{},"PGRST301",", tiene\n",[52,578,341],{"href":579},"\u002Fblog\u002Fsupabase-invalid-jwt",". La fila de la sesión se lee\njunto con ",[52,582,584],{"href":583},"\u002Fblog\u002Fsupabase-users-logged-out","por qué se cierra la sesión de los usuarios de Supabase",[37,586,588],{"id":587},"que-la-comprobación-siga-ahí-tras-el-próximo-cambio","Que la comprobación siga ahí tras el próximo cambio",[10,590,591,594],{},[32,592,593],{},"Una comprobación de quién llama puede desaparecer la próxima vez que le pidas\na tu builder que haga desaparecer un error",", y las dos formas más rápidas de\nsilenciar este son las dos de arriba.",[10,596,597],{},[32,598,599,603],{},[52,600,602],{"href":601},"\u002Fwebsite-monitoring","Reeve Monitor"," vuelve a escanear tu app publicada cada hora:",[284,605,606,609,612,615],{},[287,607,608],{},"las nueve comprobaciones cada hora, en hasta tres apps",[287,610,611],{},"un correo el día en que un nuevo escaneo califica tu app peor que el anterior; una clave secreta en tu JavaScript limita por sí sola la nota a D",[287,613,614],{},"si la app está disponible, cada 60 segundos",[287,616,617],{},"un informe mensual de lo que vio",[10,619,620],{},[32,621,622,623,627],{},"Una función que se cree el id de usuario que le envían puede cambiar las filas\nde cualquiera de tus usuarios para quien la llame. ",[52,624,626],{"href":625},"\u002Fsupabase-backups","Reeve Care","\nguarda una copia de tu base de datos de Supabase de antes de que eso pase.",[284,629,630,633,636,639,642],{},[287,631,632],{},"una copia cifrada según un calendario, guardada en el almacenamiento propio de Reeve en la UE, fuera de tu cuenta de Supabase",[287,634,635],{},"cada copia verificada antes de contar, contando las filas de cada tabla",[287,637,638],{},"una restauración en un clic que guarda lo que hay ahora antes de reemplazarlo",[287,640,641],{},"también tus archivos subidos, en cuanto conectes una credencial de Storage",[287,643,644],{},"todo lo que hace Monitor",[37,646,648],{"id":647},"qué-hacer-ahora","Qué hacer ahora",[650,651,652],"key-takeaways",{},[284,653,654,663,669,675,678],{},[287,655,656,657,659,660,662],{},"Abre la petición que falla en la pestaña Network. Si ",[21,658,197],{}," lleva el mismo valor que ",[21,661,300],{},", no había nadie con sesión iniciada cuando se envió.",[287,664,665,666,668],{},"Llama a las funciones que necesitan a una persona solo después del inicio de sesión, y haz que la función responda 401 cuando ",[21,667,91],{}," no encuentre ningún usuario.",[287,670,671,672,674],{},"En tu propio servidor, pasa a ",[21,673,91],{}," el token de acceso de la persona. La clave de tu proyecto no lleva ningún usuario dentro.",[287,676,677],{},"Deja la clave secreta donde está. Tampoco nombra a nadie, y en la página lee y escribe cada fila.",[287,679,680],{},"Mantén la comprobación de quién llama. Una función que toma el id del usuario de la petición se cree a cualquiera.",[10,682,683,684,686,687,690,691,695],{},"Si tu app muestra ",[21,685,224],{}," mientras este\nmensaje está en el registro, la función está pasando la negativa, y\n",[52,688,689],{"href":324},"ese artículo"," lee la\nrespuesta desde ahí. La ",[52,692,694],{"href":693},"\u002Fchecklist","lista de seguridad de 10 minutos"," cubre lo\ndemás que una app recién lanzada suele dejar abierto.",{"title":23,"searchDepth":697,"depth":697,"links":698},3,[699,701,702,703,704,705,706,707,708],{"id":39,"depth":700,"text":40},2,{"id":105,"depth":700,"text":106},{"id":184,"depth":700,"text":185},{"id":267,"depth":700,"text":268},{"id":347,"depth":700,"text":348},{"id":405,"depth":700,"text":406},{"id":448,"depth":700,"text":449},{"id":587,"depth":700,"text":588},{"id":647,"depth":700,"text":648},"Fundamentos de seguridad","\u002Fblog\u002Finvalid-claim-missing-sub-claim\u002Fcover-1200x630.png","Una credencial colgada de un cordón, con un sello auténtico y la línea del nombre vacía, delante de dos credenciales con nombre y cara.","\"invalid claim: missing sub claim\": Supabase Auth recibió un token auténtico sin ningún usuario, la clave del proyecto en lugar de una sesión.",false,"md",[716,719,722,725,728],{"q":717,"a":718},"¿Qué significa \"invalid claim: missing sub claim\" en Supabase?","Significa que a Supabase Auth le preguntaron qué usuario llama y le entregaron un token sin ningún usuario dentro. El token en sí era auténtico y pasó la comprobación de firma. Lo que le faltaba era el claim sub, el campo que guarda el id de un usuario. Así son exactamente las claves de API de tu proyecto, y por eso la causa habitual es una clave enviada donde va el token de un usuario con sesión iniciada.",{"q":720,"a":721},"¿Es lo mismo que \"Auth session missing!\"?","No. \"Auth session missing!\" viene de supabase-js en el navegador, que no encuentra ninguna sesión y ni siquiera envía la petición. \"invalid claim: missing sub claim\" lo devuelve Supabase Auth, que recibió una petición y encontró una clave donde debía estar el token del usuario. El primero suele indicar una página sin sesión; el segundo, una función o un servidor que pasó la clave.",{"q":723,"a":724},"¿Se arregla desactivando Verify JWT?","No. Verify JWT es una comprobación que Supabase hace en una Edge Function antes de que arranque tu código, y este mensaje llega después, de Supabase Auth, cuando tu código pregunta quién llama. Desactivar la comprobación de la plataforma deja llegar a tu código también las peticiones sin ningún token, y el error sigue exactamente donde estaba.",{"q":726,"a":727},"¿Un inicio de sesión anónimo provoca este error?","No. Un inicio de sesión anónimo crea un usuario real con su propio id, así que su token lleva un claim sub e is_anonymous en true. La clave anon es otra cosa aunque se llame parecido: una clave de proyecto sin usuario dentro, y es la que produce este mensaje.",{"q":729,"a":730},"¿Por qué unas páginas dicen 401 y otras 403?","La página de soluciones de Supabase para este mensaje lleva 401 en el título. El código actual de Supabase Auth responde 403, con el código de error bad_jwt. Si tu código decide qué hacer solo por el estado, comprueba también el código de error y el mensaje.","\u002Fblog\u002Finvalid-claim-missing-sub-claim\u002Fcard-800x500.png",[733,514,734,735,736,737,738],"invalid claim missing sub claim supabase","AuthApiError invalid claim missing sub claim","bad_jwt supabase","supabase getuser 403","error getuser supabase edge function","supabase anon key getuser",{},true,"\"invalid claim: missing sub claim\" en Supabase","\u002Fblog\u002Finvalid-claim-missing-sub-claim","2026-11-04",{"title":5,"description":712},"blog\u002Finvalid-claim-missing-sub-claim",[747,748,749],"\"invalid claim: missing sub claim\" es Supabase Auth respondiendo 403 con el código bad_jwt. Le preguntaron quién llama, el token que recibió era auténtico y no nombraba a nadie.","La propia página de soluciones de Supabase nombra la causa común: una clave de API enviada donde va el token de acceso de un usuario con sesión iniciada. Una clave dice qué app llama y no lleva a ninguna persona dentro.","Llama a la función solo cuando alguien haya iniciado sesión, y haz que responda 401 cuando no haya nadie. Poner la clave secreta en su lugar no cambia nada, porque esa clave tampoco nombra a nadie.","ghfUDT-QL8GnDjkmkKL30zvRcs6zTcqpo8kIHE2P_IY",[752,758,764,770,776,781,782,787,793,799,805,812,818,824,829,835,841,847,853,859,865,871,876,882,888,893,899,905,911,917,923,929,935,941,947,953,958,964,970,976,982,988,994,1000,1006,1012,1017,1023,1029,1035,1041,1047,1053,1059,1065,1071,1077,1083,1089,1095,1101,1107,1113,1119,1124,1130,1136,1142,1148,1154,1160,1166,1172,1178,1184,1190,1196,1202,1208,1214,1220,1226,1232,1238,1243,1249,1255,1261,1266,1272],{"path":753,"title":754,"description":755,"published":756,"category":709,"image":757,"draft":713},"\u002Fblog\u002Fmax-client-connections-reached","Supabase \"max client connections reached\", explicado","Supabase \"max client connections reached\": el pooler no tiene sitio para un cliente más. El puerto de tu cadena de conexión decide el límite.","2026-11-09","\u002Fblog\u002Fmax-client-connections-reached\u002Fcard-800x500.png",{"path":759,"title":760,"description":761,"published":762,"category":709,"image":763,"draft":713},"\u002Fblog\u002Funsupported-provider-provider-is-not-enabled","Error de Supabase: \"Unsupported provider: provider is not enabled\"","\"Unsupported provider: provider is not enabled\": Google está desactivado en tu proyecto Supabase. Cómo activarlo y arreglar redirect_uri_mismatch.","2026-11-08","\u002Fblog\u002Funsupported-provider-provider-is-not-enabled\u002Fcard-800x500.png",{"path":765,"title":766,"description":767,"published":768,"category":709,"image":769,"draft":713},"\u002Fblog\u002Fviolates-foreign-key-constraint","Error de Supabase \"violates foreign key constraint\" al insertar","Supabase \"insert or update on table violates foreign key constraint\": tu fila apunta a una fila que no existe. Cuál, por qué, y cómo arreglarlo.","2026-11-07","\u002Fblog\u002Fviolates-foreign-key-constraint\u002Fcard-800x500.png",{"path":771,"title":772,"description":773,"published":774,"category":709,"image":775,"draft":713},"\u002Fblog\u002Fnull-value-violates-not-null-constraint","Error de Supabase \"null value in column violates not-null constraint\"","Supabase \"null value in column violates not-null constraint\": una columna obligatoria llegó vacía. De dónde vino el null, y cómo arreglarlo.","2026-11-06","\u002Fblog\u002Fnull-value-violates-not-null-constraint\u002Fcard-800x500.png",{"path":340,"title":777,"description":778,"published":779,"category":709,"image":780,"draft":713},"Supabase \"Auth session missing!\": adónde fue la sesión","\"Auth session missing!\" es supabase-js sin encontrar ninguna sesión iniciada y sin llegar a preguntar a Supabase. Adónde fue la sesión, y el arreglo.","2026-11-05","\u002Fblog\u002Fauth-session-missing\u002Fcard-800x500.png",{"path":742,"title":5,"description":712,"published":743,"category":709,"image":731,"draft":713},{"path":372,"title":783,"description":784,"published":785,"category":709,"image":786,"draft":713},"\"Failed to send a request to the Edge Function\": la causa real","\"Failed to send a request to the Edge Function\": el navegador no recibió una respuesta legible. Su consola dice por qué y si tu función se ejecutó.","2026-11-03","\u002Fblog\u002Ffailed-to-send-a-request-to-the-edge-function\u002Fcard-800x500.png",{"path":788,"title":789,"description":790,"published":791,"category":709,"image":792,"draft":713},"\u002Fblog\u002Fsupabase-auth-redirects-to-localhost","Por qué los enlaces de email de Supabase redirigen a localhost:3000","Si Supabase redirige a localhost:3000, la Site URL nunca se cambió o tu dirección falta en Redirect URLs. La solución, y el comodín que debes evitar.","2026-11-02","\u002Fblog\u002Fsupabase-auth-redirects-to-localhost\u002Fcard-800x500.png",{"path":794,"title":795,"description":796,"published":797,"category":709,"image":798,"draft":713},"\u002Fblog\u002Femail-link-is-invalid-or-has-expired","Supabase \"Email link is invalid or has expired\" con un enlace nuevo","Con un enlace enviado hace un minuto, \"Email link is invalid or has expired\" en Supabase suele indicar que un filtro de correo lo usó antes. Cómo arreglarlo.","2026-11-01","\u002Fblog\u002Femail-link-is-invalid-or-has-expired\u002Fcard-800x500.png",{"path":800,"title":801,"description":802,"published":803,"category":709,"image":804,"draft":713},"\u002Fblog\u002Fcanceling-statement-due-to-statement-timeout","Supabase \"canceling statement due to statement timeout\", explicado","Supabase \"canceling statement due to statement timeout\": una consulta superó el límite de su rol, 3 segundos para un visitante. Revisa antes la política RLS.","2026-10-31","\u002Fblog\u002Fcanceling-statement-due-to-statement-timeout\u002Fcard-800x500.png",{"path":806,"title":807,"description":808,"published":809,"category":810,"image":811,"draft":713},"\u002Fblog\u002Fdatabase-error-deleting-user","\"Database error deleting user\" en Supabase, y qué se va con él","\"Database error deleting user\" en Supabase: tu base de datos rechazó el borrado. La causa está en tus logs y el arreglo decide qué más se borra.","2026-10-30","Backups","\u002Fblog\u002Fdatabase-error-deleting-user\u002Fcard-800x500.png",{"path":813,"title":814,"description":815,"published":816,"category":709,"image":817,"draft":713},"\u002Fblog\u002Fstripe-webhook-no-signatures-found","\"No signatures found matching the expected signature\" en Supabase","¿Tu webhook de Stripe en una Edge Function de Supabase dice \"No signatures found matching the expected signature\"? Tiene dos causas. Sigue verificando.","2026-10-29","\u002Fblog\u002Fstripe-webhook-no-signatures-found\u002Fcard-800x500.png",{"path":819,"title":820,"description":821,"published":822,"category":709,"image":823,"draft":713},"\u002Fblog\u002Fsupabase-invalid-login","\"Invalid login credentials\" en Supabase: las tres causas","Supabase dice \"Invalid login credentials\" ante una contraseña errónea, un email desconocido y una cuenta de Google. Cómo saber cuál es.","2026-10-28","\u002Fblog\u002Fsupabase-invalid-login\u002Fcard-800x500.png",{"path":324,"title":825,"description":826,"published":827,"category":709,"image":828,"draft":713},"\"Edge Function returned a non-2xx status code\": el error real","\"Edge Function returned a non-2xx status code\" significa que tu función de Supabase respondió con un error. Aquí ves dónde leer el real.","2026-10-27","\u002Fblog\u002Fedge-function-returned-a-non-2xx-status-code\u002Fcard-800x500.png",{"path":830,"title":831,"description":832,"published":833,"category":709,"image":834,"draft":713},"\u002Fblog\u002Fcertificate-not-trusted","Certificado no confiable: tres causas aparte de la caducidad","¿Certificado no confiable aunque la fecha está bien? Nombra otra dirección, se firmó a sí mismo o a su cadena le falta un eslabón. Cómo saber cuál.","2026-10-26","\u002Fblog\u002Fcertificate-not-trusted\u002Fcard-800x500.png",{"path":836,"title":837,"description":838,"published":839,"category":709,"image":840,"draft":713},"\u002Fblog\u002Finvalid-input-syntax-for-type-uuid","Supabase \"invalid input syntax for type uuid\": lee las comillas","Supabase \"invalid input syntax for type uuid\" significa que tu app envió algo que no es un id. Lee las comillas, frena la consulta, conserva la columna.","2026-10-25","\u002Fblog\u002Finvalid-input-syntax-for-type-uuid\u002Fcard-800x500.png",{"path":842,"title":843,"description":844,"published":845,"category":709,"image":846,"draft":713},"\u002Fblog\u002Fduplicate-key-value-violates-unique-constraint","Supabase \"duplicate key value violates unique constraint\", explicado","Supabase \"duplicate key value violates unique constraint\": el valor ya está ocupado. Encuentra la columna, elige un arreglo y aparta el valor de la pantalla.","2026-10-24","\u002Fblog\u002Fduplicate-key-value-violates-unique-constraint\u002Fcard-800x500.png",{"path":848,"title":849,"description":850,"published":851,"category":709,"image":852,"draft":713},"\u002Fblog\u002Fsupabase-relation-does-not-exist","Supabase \"relation does not exist\" aunque la tabla está ahí","Supabase \"relation does not exist\" o PGRST205: ninguna tabla tiene ese nombre en ese esquema. Cuatro comprobaciones, y lo que abre exponer un esquema.","2026-10-23","\u002Fblog\u002Fsupabase-relation-does-not-exist\u002Fcard-800x500.png",{"path":854,"title":855,"description":856,"published":857,"category":709,"image":858,"draft":713},"\u002Fblog\u002Fsupabase-function-not-found","Supabase PGRST202: \"Could not find the function in the schema cache\"","Supabase \"Could not find the function in the schema cache\" muestra los nombres de argumentos que envió tu app. Compáralos con la función.","2026-10-22","\u002Fblog\u002Fsupabase-function-not-found\u002Fcard-800x500.png",{"path":860,"title":861,"description":862,"published":863,"category":709,"image":864,"draft":713},"\u002Fblog\u002Fsupabase-schema-cache-column-error","Supabase PGRST204: \"Could not find the column in the schema cache\"","Supabase \"Could not find the column in the schema cache\" suele ser un nombre que no coincide. Cuatro comprobaciones y la línea que recarga.","2026-10-21","\u002Fblog\u002Fsupabase-schema-cache-column-error\u002Fcard-800x500.png",{"path":866,"title":867,"description":868,"published":869,"category":709,"image":870,"draft":713},"\u002Fblog\u002Fsupabase-email-rate-limit-exceeded","Supabase \"Email Rate Limit Exceeded\": dos límites, un solo código","Supabase \"Email Rate Limit Exceeded\" limita cada email que envía tu proyecto: 2 por hora con el remitente integrado. Qué cuenta y cómo subirlo.","2026-10-20","\u002Fblog\u002Fsupabase-email-rate-limit-exceeded\u002Fcard-800x500.png",{"path":583,"title":872,"description":873,"published":874,"category":709,"image":875,"draft":713},"Supabase \"Invalid Refresh Token: Refresh Token Not Found\": 3 causas","Supabase \"Invalid Refresh Token: Refresh Token Not Found\" cierra la sesión de tus usuarios. Tres causas, y cómo las separa el log de Auth.","2026-10-19","\u002Fblog\u002Fsupabase-users-logged-out\u002Fcard-800x500.png",{"path":877,"title":878,"description":879,"published":880,"category":709,"image":881,"draft":713},"\u002Fblog\u002Fpermission-denied-for-schema-public","Supabase \"permission denied for schema public\": tres causas","Supabase \"permission denied for schema public\" es un grant que falta en el esquema. Qué lo quitó y la línea que lo devuelve.","2026-10-18","\u002Fblog\u002Fpermission-denied-for-schema-public\u002Fcard-800x500.png",{"path":883,"title":884,"description":885,"published":886,"category":709,"image":887,"draft":713},"\u002Fblog\u002Fsupabase-bucket-not-found","Supabase \"Bucket not found\": las tres cosas que significa","Supabase \"Bucket not found\" es una de tres cosas: un enlace público a un bucket privado, un nombre que no coincide exactamente o el proyecto equivocado.","2026-10-17","\u002Fblog\u002Fsupabase-bucket-not-found\u002Fcard-800x500.png",{"path":579,"title":889,"description":890,"published":891,"category":709,"image":892,"draft":713},"Supabase invalid JWT: ¿qué clave dejó de funcionar?","Un error Supabase invalid JWT es PGRST301: un token firmado no pasó la verificación. En un proyecto nuevo se trata de la sesión, no de la clave de API.","2026-10-16","\u002Fblog\u002Fsupabase-invalid-jwt\u002Fcard-800x500.png",{"path":894,"title":895,"description":896,"published":897,"category":709,"image":898,"draft":713},"\u002Fblog\u002Fsupabase-single-row-error","Supabase \"multiple (or no) rows returned\" casi siempre es ninguna","Supabase devuelve PGRST116, \"JSON object requested, multiple (or no) rows returned\", cuando tu consulta no encontró nada. Una fila oculta parece ausente.","2026-10-15","\u002Fblog\u002Fsupabase-single-row-error\u002Fcard-800x500.png",{"path":900,"title":901,"description":902,"published":903,"category":709,"image":904,"draft":713},"\u002Fblog\u002Fdatabase-error-saving-new-user","\"Database error saving new user\" en Supabase: dónde mirar","\"Database error saving new user\" significa que algo unido a tu registro de Supabase falló y se deshizo entero. El error real está en tus logs.","2026-10-14","\u002Fblog\u002Fdatabase-error-saving-new-user\u002Fcard-800x500.png",{"path":906,"title":907,"description":908,"published":909,"category":709,"image":910,"draft":713},"\u002Fblog\u002Fwhat-is-supabase","¿Qué es Supabase? La base de datos que eligió tu builder","Qué es Supabase: la base de datos, el login y el almacenamiento de archivos de tu app. Supabase lleva los servidores; las reglas sobre tus filas son tuyas.","2026-10-13","\u002Fblog\u002Fwhat-is-supabase\u002Fcard-800x500.png",{"path":912,"title":913,"description":914,"published":915,"category":709,"image":916,"draft":713},"\u002Fblog\u002Faws-access-key-exposed-in-frontend","Una clave AWS expuesta en tu frontend no es como una clave OpenAI","Una clave de acceso de AWS expuesta en tu frontend lee y escribe tu almacenamiento. La solución suele ser una presigned URL, no una clave nueva.","2026-10-12","\u002Fblog\u002Faws-access-key-exposed-in-frontend\u002Fcard-800x500.png",{"path":918,"title":919,"description":920,"published":921,"category":709,"image":922,"draft":713},"\u002Fblog\u002Fstorage-upload-violates-row-level-security","\"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.","2026-10-11","\u002Fblog\u002Fstorage-upload-violates-row-level-security\u002Fcard-800x500.png",{"path":924,"title":925,"description":926,"published":927,"category":709,"image":928,"draft":713},"\u002Fblog\u002Fbase44-security-scan","Escaneo de seguridad de Base44: lo que solo él ve","El escaneo de seguridad de Base44 revisa siete tipos de problema desde dentro. Esta es la mitad que lee y nadie más puede leer, y la mitad que nunca mira.","2026-10-10","\u002Fblog\u002Fbase44-security-scan\u002Fcard-800x500.png",{"path":930,"title":931,"description":932,"published":933,"category":709,"image":934,"draft":713},"\u002Fblog\u002Fapi-key-leaked-what-to-do","Tu clave API se filtró. El orden en el que hay que actuar","Una clave API se filtró y no sabes por dónde empezar. No todas las claves de tu frontend lo son, y el orden importa más que la velocidad.","2026-10-09","\u002Fblog\u002Fapi-key-leaked-what-to-do\u002Fcard-800x500.png",{"path":936,"title":937,"description":938,"published":939,"category":709,"image":940,"draft":713},"\u002Fblog\u002Finfinite-recursion-in-policy-for-relation","\"Infinite recursion detected in policy\" sin desactivar RLS","\"Infinite recursion detected in policy for relation\" significa que tu policy preguntó a la tabla que protege. Así se rompe el círculo.","2026-10-08","\u002Fblog\u002Finfinite-recursion-in-policy-for-relation\u002Fcard-800x500.png",{"path":942,"title":943,"description":944,"published":945,"category":709,"image":946,"draft":713},"\u002Fblog\u002Fno-api-key-found-in-request","\"No API key found in request\" en Supabase, y el arreglo equivocado","\"No API key found in request\" significa que tu petición llegó a Supabase sin clave. Casi todas las respuestas apuntan a las reglas de tu base de datos.","2026-10-07","\u002Fblog\u002Fno-api-key-found-in-request\u002Fcard-800x500.png",{"path":948,"title":949,"description":950,"published":951,"category":709,"image":952,"draft":713},"\u002Fblog\u002Fis-base44-safe","¿Es seguro Base44? Lo que vimos en 5.442 apps Base44","¿Es seguro Base44? Pasamos nueve comprobaciones a 5.442 apps Base44 en producción. Casi todas sacaron B por motivos que su dueño nunca eligió.","2026-10-06","\u002Fblog\u002Fis-base44-safe\u002Fcard-800x500.png",{"path":208,"title":954,"description":955,"published":956,"category":709,"image":957,"draft":713},"Ocultar una clave API: muévela a una Edge Function de Supabase","Ocultar una clave API significa sacarla del navegador, y una Edge Function de Supabase es el sitio más pequeño donde ponerla. Dos pasos importan más.","2026-10-05","\u002Fblog\u002Fmove-a-secret-to-an-edge-function\u002Fcard-800x500.png",{"path":959,"title":960,"description":961,"published":962,"category":709,"image":963,"draft":713},"\u002Fblog\u002Fenv-file-exposed-on-your-server","¿Tu archivo .env está expuesto? Las doce rutas a revisar","¿Tu archivo .env está expuesto en tu propio servidor web? Doce direcciones te lo dicen, y un acierto significa que todo lo que contiene ya es público.","2026-10-04","\u002Fblog\u002Fenv-file-exposed-on-your-server\u002Fcard-800x500.png",{"path":965,"title":966,"description":967,"published":968,"category":709,"image":969,"draft":713},"\u002Fblog\u002Fis-v0-safe","Seguridad de v0: las 1.790 aplicaciones escaneadas sacaron una A","Seguridad de v0, medida en 1.790 aplicaciones v0 en vivo: todas sacaron A. Solo 17 nombraban una base de datos, y eso es casi todo lo que mide la A.","2026-10-03","\u002Fblog\u002Fis-v0-safe\u002Fcard-800x500.png",{"path":971,"title":972,"description":973,"published":974,"category":709,"image":975,"draft":713},"\u002Fblog\u002Fis-bolt-safe","¿Es seguro Bolt? Lo que vimos en 1.123 aplicaciones Bolt","¿Es seguro Bolt? Pasamos 1.123 aplicaciones Bolt en vivo por nueve comprobaciones. El hosting salió limpio. Los hallazgos eran claves y tablas abiertas dentro.","2026-10-02","\u002Fblog\u002Fis-bolt-safe\u002Fcard-800x500.png",{"path":977,"title":978,"description":979,"published":980,"category":709,"image":981,"draft":713},"\u002Fblog\u002Fis-supabase-down-or-your-app","¿Supabase está caído o es tu app? Cómo distinguirlo","¿Supabase está caído o es tu app? Un proyecto pausado, una base de datos llena y un fallo tuyo parecen lo mismo. Cuatro comprobaciones los separan.","2026-10-01","\u002Fblog\u002Fis-supabase-down-or-your-app\u002Fcard-800x500.png",{"path":983,"title":984,"description":985,"published":986,"category":709,"image":987,"draft":713},"\u002Fblog\u002Fdoes-supabase-encrypt-my-data","¿Supabase cifra mis datos? Sí. Esto es lo que detiene","¿Supabase cifra los datos? Sí: AES-256 en reposo, TLS en tránsito, SOC 2 e ISO 27001. Qué cubre cada uno, y la fuga que ninguno de ellos detiene.","2026-09-30","\u002Fblog\u002Fdoes-supabase-encrypt-my-data\u002Fcard-800x500.png",{"path":989,"title":990,"description":991,"published":992,"category":709,"image":993,"draft":713},"\u002Fblog\u002Fsupabase-free-plan-limits","¿Cuánto cuesta Supabase? Los límites del plan gratuito","Los límites del plan gratuito de Supabase: 500 MB de base de datos, 5 GB de egress y ningún backup. Qué pasa en cada uno y qué cambia Pro por $25.","2026-09-29","\u002Fblog\u002Fsupabase-free-plan-limits\u002Fcard-800x500.png",{"path":995,"title":996,"description":997,"published":998,"category":810,"image":999,"draft":713},"\u002Fblog\u002Ftest-your-supabase-backup","Prueba tu backup de Supabase antes del día que lo necesites","Cómo probar tu backup de Supabase: restaurarlo en un proyecto de prueba, contar filas, iniciar sesión y buscar la línea que le falta a un archivo cortado.","2026-09-28","\u002Fblog\u002Ftest-your-supabase-backup\u002Fcard-800x500.png",{"path":1001,"title":1002,"description":1003,"published":1004,"category":810,"image":1005,"draft":713},"\u002Fblog\u002Fsupabase-backup-github-action","Backup gratis de Supabase con una GitHub Action, y la trampa","Un backup de Supabase con GitHub Action es gratis y sirve a muchas apps. El workflow, el connection string correcto y el egress de cada ejecución.","2026-09-27","\u002Fblog\u002Fsupabase-backup-github-action\u002Fcard-800x500.png",{"path":1007,"title":1008,"description":1009,"published":1010,"category":810,"image":1011,"draft":713},"\u002Fblog\u002Fdownload-your-supabase-backup","Por qué no puedes descargar tu backup de Supabase","No puedes descargar tu backup de Supabase en un proyecto actual: la copia diaria es una instantánea física. Cómo comprobarlo y tener tu propia copia.","2026-09-26","\u002Fblog\u002Fdownload-your-supabase-backup\u002Fcard-800x500.png",{"path":1013,"title":1014,"description":1015,"published":1010,"category":709,"image":1016,"draft":713},"\u002Fblog\u002Fsupabase-permission-denied-for-table","Supabase \"permission denied for table\": el grant que falta","Desde el 30 de octubre, una tabla nueva de Supabase responde \"permission denied for table\" hasta que le das acceso. El grant del email es la mitad del arreglo.","\u002Fblog\u002Fsupabase-permission-denied-for-table\u002Fcard-800x500.png",{"path":1018,"title":1019,"description":1020,"published":1021,"category":709,"image":1022,"draft":713},"\u002Fblog\u002Fopen-api-endpoint-exposed","¿Un endpoint de API abierto es un problema? Mira el JSON","Tu escaneo señaló un endpoint de API abierto. Que importe depende de lo que devolvió, y la mayoría de los que encontramos eran de la plataforma.","2026-09-25","\u002Fblog\u002Fopen-api-endpoint-exposed\u002Fcard-800x500.png",{"path":1024,"title":1025,"description":1026,"published":1027,"category":709,"image":1028,"draft":713},"\u002Fblog\u002Flovable-security-scan","Escaneo de seguridad de Lovable: lo que no puede probar","Escaneo de seguridad de Lovable: qué revisan el Quick scan y el Deep scan, cuándo corre cada uno, y lo único que ningún escaneo desde dentro puede probar.","2026-09-24","\u002Fblog\u002Flovable-security-scan\u002Fcard-800x500.png",{"path":1030,"title":1031,"description":1032,"published":1033,"category":810,"image":1034,"draft":713},"\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","\u002Fblog\u002Fsupabase-backup-auth-users\u002Fcard-800x500.png",{"path":1036,"title":1037,"description":1038,"published":1039,"category":709,"image":1040,"draft":713},"\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":1042,"title":1043,"description":1044,"published":1045,"category":709,"image":1046,"draft":713},"\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":1048,"title":1049,"description":1050,"published":1051,"category":709,"image":1052,"draft":713},"\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":1054,"title":1055,"description":1056,"published":1057,"category":709,"image":1058,"draft":713},"\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":1060,"title":1061,"description":1062,"published":1063,"category":709,"image":1064,"draft":713},"\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":1066,"title":1067,"description":1068,"published":1069,"category":709,"image":1070,"draft":713},"\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":1072,"title":1073,"description":1074,"published":1075,"category":709,"image":1076,"draft":713},"\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":1078,"title":1079,"description":1080,"published":1081,"category":709,"image":1082,"draft":713},"\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":1084,"title":1085,"description":1086,"published":1087,"category":810,"image":1088,"draft":713},"\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":1090,"title":1091,"description":1092,"published":1093,"category":810,"image":1094,"draft":713},"\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":1096,"title":1097,"description":1098,"published":1099,"category":810,"image":1100,"draft":713},"\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":1102,"title":1103,"description":1104,"published":1105,"category":709,"image":1106,"draft":713},"\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":1108,"title":1109,"description":1110,"published":1111,"category":709,"image":1112,"draft":713},"\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":1114,"title":1115,"description":1116,"published":1117,"category":709,"image":1118,"draft":713},"\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":427,"title":1120,"description":1121,"published":1122,"category":709,"image":1123,"draft":713},"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":1125,"title":1126,"description":1127,"published":1128,"category":709,"image":1129,"draft":713},"\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. Ocho herramientas, con precios.","2026-09-07","\u002Fblog\u002Fvibe-coding-security-scanners-compared\u002Fcard-800x500.png",{"path":1131,"title":1132,"description":1133,"published":1134,"category":709,"image":1135,"draft":713},"\u002Fblog\u002Fsupabase-security-checker","Supabase security checker: haz tú mismo las cinco comprobaciones","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.","2026-09-06","\u002Fblog\u002Fsupabase-security-checker\u002Fcard-800x500.png",{"path":1137,"title":1138,"description":1139,"published":1140,"category":709,"image":1141,"draft":713},"\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":1143,"title":1144,"description":1145,"published":1146,"category":709,"image":1147,"draft":713},"\u002Fblog\u002Fmissing-security-headers","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":1149,"title":1150,"description":1151,"published":1152,"category":709,"image":1153,"draft":713},"\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":1155,"title":1156,"description":1157,"published":1158,"category":709,"image":1159,"draft":713},"\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":1161,"title":1162,"description":1163,"published":1164,"category":810,"image":1165,"draft":713},"\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":1167,"title":1168,"description":1169,"published":1170,"category":709,"image":1171,"draft":713},"\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":1173,"title":1174,"description":1175,"published":1176,"category":810,"image":1177,"draft":713},"\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":1179,"title":1180,"description":1181,"published":1182,"category":709,"image":1183,"draft":713},"\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":1185,"title":1186,"description":1187,"published":1188,"category":709,"image":1189,"draft":713},"\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":1191,"title":1192,"description":1193,"published":1194,"category":709,"image":1195,"draft":713},"\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":1197,"title":1198,"description":1199,"published":1200,"category":709,"image":1201,"draft":713},"\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":1203,"title":1204,"description":1205,"published":1206,"category":810,"image":1207,"draft":713},"\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":1209,"title":1210,"description":1211,"published":1212,"category":810,"image":1213,"draft":713},"\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":1215,"title":1216,"description":1217,"published":1218,"category":709,"image":1219,"draft":713},"\u002Fblog\u002Fsupabase-storage-bucket-public","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":1221,"title":1222,"description":1223,"published":1224,"category":709,"image":1225,"draft":713},"\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":1227,"title":1228,"description":1229,"published":1230,"category":810,"image":1231,"draft":713},"\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":1233,"title":1234,"description":1235,"published":1236,"category":709,"image":1237,"draft":713},"\u002Fblog\u002Fcan-anyone-read-your-supabase-database","¿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":1239,"title":1240,"description":1241,"published":1236,"category":709,"image":1242,"draft":713},"\u002Fblog\u002Fsource-maps-exposed-in-production","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":1244,"title":1245,"description":1246,"published":1247,"category":709,"image":1248,"draft":713},"\u002Fblog\u002Fsupabase-new-api-keys","Supabase publishable key: ¿es la misma que la anon key?","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":1250,"title":1251,"description":1252,"published":1253,"category":810,"image":1254,"draft":713},"\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":1256,"title":1257,"description":1258,"published":1259,"category":709,"image":1260,"draft":713},"\u002Fblog\u002Fsupabase-rls-on-but-table-still-public","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":1262,"title":1263,"description":1264,"published":1259,"category":810,"image":1265,"draft":713},"\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":1267,"title":1268,"description":1269,"published":1270,"category":810,"image":1271,"draft":713},"\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":179,"title":1273,"description":1274,"published":1270,"category":709,"image":1275,"draft":713},"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",1794213644831]