Fundamentos de seguridad
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.

En resumen
- Una clave de acceso de AWS expuesta en tu frontend es un problema distinto al de una clave que solo puede gastar dinero. Lo que alcanza lo deciden sus permisos.
- Dos cadenas firman una petición de AWS: un identificador que empieza por `AKIA`, y una mitad secreta. Una sin la otra no firma nada.
- Una clave nueva pegada en el mismo sitio es el mismo hallazgo con otro valor. La solución es una presigned URL, para que el navegador no tenga ninguna credencial.
- Encontramos una en 9 de las 31.056 aplicaciones escaneadas donde esa comprobación llegó hasta el final.
Alguien abre tu aplicación en un navegador, pulsa F12 y encuentra una cadena
que empieza por AKIA dentro de tu código de frontend. O GitHub te escribe
para decirte que un commit que subiste en marzo lleva una credencial de AWS. El
consejo que llega con cualquiera de las dos cosas es siempre el mismo: rota la
clave. Para la mayoría de las claves esa es casi toda la respuesta.
Aquí está la parte que guía tras guía se equivoca. Una clave de acceso de AWS expuesta en tu frontend no es una clave que gasta dinero. Es una clave que abre cosas, y las dos salen mal en direcciones distintas. Una clave que solo gasta es una tarjeta de empresa: alguien hace una cuenta, tú anulas la tarjeta, y el daño es una cifra que puedes leer en un extracto. Una clave que abre cosas es el manojo de llaves del almacén. Lo que alcanza depende de lo que le escribieron encima cuando la cortaron, y eso se lee en una sola pantalla de tu consola de AWS.
¿Qué significa AKIA al principio de una clave?
Marca una clave de acceso de larga duración, que pertenece a un usuario de tu
cuenta o al acceso root de la cuenta. AWS anota el prefijo en su propia
referencia de identificadores: AKIA para claves de acceso y ASIA para las
temporales que reparte su servicio de tokens. Así que los cuatro primeros
caracteres te dicen con cuál estás tratando, y AKIA es la que no caduca.
Funciona hasta que alguien la apaga.
Una credencial de AWS son dos cadenas, y una petición necesita las dos para ir
firmada. La primera es el identificador de la clave de acceso, la parte que
empieza por AKIA. La segunda es la clave de acceso secreta, una cadena más
larga sin ningún prefijo delante. El identificador por sí solo no firma nada.
Vale la pena comprobarlo antes de decidir lo mala que es esta mañana, porque
cambia la respuesta. Si tu bundle lleva la cadena AKIA y nada que se parezca
a su mitad secreta, un visitante que leyó tu página se llevó el nombre de la
llave y no la llave. En la práctica las dos viajan juntas, porque lo que metió
una en el navegador metió las dos: un par de variables de entorno llamadas
AWS_ACCESS_KEY_ID y AWS_SECRET_ACCESS_KEY, leídas por código que debía
ejecutarse en un servidor.
¿Qué puede hacer alguien con mi clave de acceso de AWS?
Lo que permitan sus permisos, y nada más allá. Así que hay tres respuestas.
¿Qué puede leer? Cada bucket de S3 que liste la política, y cada objeto dentro, incluidos aquellos a los que nunca has enlazado desde ningún sitio. Un bucket que nada de tu aplicación menciona no está escondido de una credencial que puede listarlo.
¿Qué puede escribir? Los mismos sitios, y esa es la mitad que se olvida. Una clave que puede escribir también puede sobrescribir y borrar, y un archivo sustituido en el sitio se ve exactamente igual que el que tú subiste hasta que alguien lo abre.
¿Qué puede arrancar? Esta es la que lleva contador. Una política lo bastante amplia para lanzar cómputo lanza cómputo, y eso factura por hora mientras siga funcionando. Nada de la copia que tiene el navegador limita qué parte de tu cuenta alcanza; la política es el único límite que hay.
La peor versión de esto es una clave cortada para el acceso root de la cuenta.
AWS dice en sus propias indicaciones sobre el usuario root que ese usuario
tiene acceso sin restricciones a todos los servicios de AWS, a los recursos y a
los datos de facturación, y desaconseja crearle claves de acceso. Si la clave
de tu página empieza por AKIA y pertenece al usuario root, lo expuesto es la
cuenta entera y no un bucket.
Así que no adivines nada de esto. Abre IAM, busca el usuario al que pertenece la clave y lee las políticas que lleva asociadas. Es una pantalla, y responde a las tres preguntas de arriba.
¿Basta con rotarla?
Desactiva hoy la clave expuesta, y no pongas un recambio en el mismo sitio.
Desactivar es el primer movimiento correcto y es más suave de lo que suena. AWS deja que un usuario tenga hasta dos claves de acceso activas a la vez, y eso existe justamente para que un recambio suba antes de que la vieja baje. Desactivar deja la clave ahí y apagada, y se puede volver a encender; borrar es irreversible, y la mitad secreta de una clave borrada no se recupera. Así que desactiva, mira qué se rompe y borra después.
Lo que no cierra el asunto es crear una clave nueva y pegarla donde estaba la
vieja. La filtración nunca fue realmente por ese valor concreto. Fue porque un
navegador tenía una credencial que no caduca, y todo lo que tu página descarga
lo descarga cada visitante. Una cadena AKIA recién hecha en el mismo bundle
vuelve como el mismo hallazgo en el siguiente escaneo.
AWS lo dice con claridad en su guía de auditoría de seguridad, hablando de aplicaciones móviles: nunca deberían llevar claves de acceso incrustadas, ni siquiera cifradas. Un bundle de navegador se lee más fácil que un binario de aplicación, así que la misma respuesta vale aquí y le cuesta menos trabajo a un desconocido.
Si ya has encontrado la clave en un sitio público, el orden en el que trabajas importa, y qué hacer cuando se filtra una clave de API deja ese orden puesto para claves de todo tipo.
Qué hace una presigned URL en su lugar
Deja que el navegador haga una sola cosa con un solo archivo sin tener ninguna credencial.
Tu propio servidor se queda con la clave de acceso, donde un visitante no puede leerla. Cuando tu página tiene que dejar un archivo en S3 o sacar uno, se lo pide a tu servidor, y tu servidor firma una URL para ese objeto y esa acción y se la devuelve. El navegador usa la URL y no se lleva nada más con ella. AWS describe una presigned URL como acceso temporal a objetos de S3 sin credenciales de AWS, y el mismo mecanismo cubre descargas y subidas.
Volvamos al almacén. Una presigned URL es recibir al mensajero en la puerta con la caja que venía a buscar, en lugar de darle el manojo y confiar en que la encuentre. Lo que el mensajero se lleva deja de funcionar cuando su reloj se acaba.
Para el caso en que cada persona con sesión llega a sus propios archivos, AWS
señala otra vez a otro sitio: sus indicaciones sobre asegurar claves de acceso
dicen que las aplicaciones móviles deberían usar Amazon Cognito para gestionar
identidades en lugar de incrustar claves de acceso. Lo que Cognito da al
navegador es una credencial temporal, la clase ASIA del principio de este
artículo, que deja de funcionar sola al cabo de un rato. Una copia sacada de tu
página un martes ya está muerta el miércoles, y esa es la única propiedad que
una clave AKIA no tiene.
¿Cómo compruebo si mi aplicación envía una clave de AWS?
Busca en lo que el navegador se ha descargado, no en lo que hay en tu proyecto. Son dos archivos distintos, y el que cuenta es el que reciben tus visitantes.
En el navegador. Abre tu sitio publicado, pulsa F12 y busca AKIA en el
JavaScript cargado. Chrome y Edge buscan en todos los archivos cargados a la
vez desde el panel Sources.
En tu proyecto. Busca AKIA, AWS_ACCESS_KEY_ID y
AWS_SECRET_ACCESS_KEY. Luego busca los dos prefijos que meten un valor en un
bundle a propósito, VITE_ y NEXT_PUBLIC_, y lee cada valor que haya detrás.
Por qué esos dos prefijos publican todo lo que nombran
es la mecánica que hay detrás de casi todos estos casos, y es una regla del
build que nadie tecleó por error.
Desde fuera. Nuestro escaneo gratuito lee tu sitio publicado como lo haría un desconocido y dice qué credenciales ve en lo que has publicado, esta incluida. Tarda unos 20 segundos y no pide cuenta: escanea tu aplicación gratis.
De todas las aplicaciones que hemos escaneado, este es un hallazgo raro: una clave de acceso de AWS apareció en 9 de las 31.056 aplicaciones donde esa comprobación llegó hasta el final. Las que salen mucho más a menudo son las de Google, que suelen estar bien, y las de OpenAI, que suelen no estarlo. Qué se escapa de verdad de las aplicaciones hechas así las pone todas una al lado de otra con las cifras.
Qué hizo la clave mientras estuvo fuera
Abre el historial de eventos de CloudTrail y fíltralo por el identificador de la clave de acceso.
El historial de eventos está encendido en toda cuenta de AWS sin tener que activar nada, guarda los últimos 90 días y no cuesta nada aparte. Cada entrada lleva el identificador de la clave que hizo la llamada, así que puedes releer lo que hizo la tuya. Eso responde a las preguntas de configuración: un bucket creado, una política cambiada, un usuario añadido.
Luego está la parte que conviene saber antes de ponerse a mirar. El historial guarda eventos de management. Leer un objeto de S3 es un evento de data, y AWS dice explícitamente que los trails no los registran por defecto y que los eventos de data no aparecen en el historial. Encenderlos graba a partir de ese momento.
Así que la lectura honesta es esta. Si la clave se usó para cambiar tu cuenta, el registro te lo enseña. Si se usó para descargar el contenido de un bucket, probablemente no, y estás decidiendo qué contarles a tus clientes sabiendo que pudo pasar.
Cómo dejar una clave fuera del siguiente build
Una credencial sale hacia el navegador en el momento del build, así que el siguiente despliegue es el que hay que vigilar. La clave que quitas hoy vuelve la tarde en que alguien añade una función que necesita S3 y le pide a un agente que la conecte.
Reeve Monitor vuelve a leer tu sitio publicado por ti:
- las nueve comprobaciones cada hora, en hasta tres aplicaciones
- un mensaje cuando un resultado cambia, para que una clave que llega un martes no espere a que mires
- si la aplicación responde, cada 60 segundos
- un informe mensual de lo que vio
Si tu aplicación guarda sus datos en un proyecto de Supabase, Reeve Care también guarda una copia:
- una copia cifrada de tu base de datos de Supabase cada noche, guardada donde tu proyecto no llega
- cada copia verificada antes de contar, contando las filas de cada tabla
- una restauración con un clic cuando la necesitas
- también tus archivos subidos, en cuanto conectas una credencial de Storage
- todo lo que hace Monitor
Cada una de esas nueve comprobaciones describe lo que un desconocido puede alcanzar. Una copia de la base de datos de anoche responde a la otra pregunta, la de qué cambió ya alguien mientras la clave estuvo fuera.
Qué hacer esta semana
Qué hacer
- Abre tu sitio publicado, pulsa F12 y busca
AKIAen el JavaScript cargado. Ese es el archivo que tienen tus visitantes, y es el único que cuenta. - Si encuentras una, desactívala hoy en IAM. Desactivar es reversible y surte efecto al momento; borrar no lo es, y la mitad secreta no se recupera.
- Lee las políticas asociadas al usuario de la clave antes de decidir lo serio que es esto. Qué puede leer, qué puede escribir y qué puede arrancar son tres respuestas separadas.
- Cambia el patrón en vez del valor. Tu servidor guarda la clave y firma una presigned URL para un objeto, o Amazon Cognito le da al navegador una credencial temporal que caduca sola.
- Filtra el historial de eventos de CloudTrail por la clave en los últimos 90 días. Te enseñará qué configuró la clave, y no te enseñará qué archivos leyó salvo que los eventos de data ya estuvieran encendidos.
Empieza por la búsqueda con F12, porque en un minuto resuelve si siquiera tienes un problema. Si saca algo, la lista de seguridad de 10 minutos cubre el resto de lo que una aplicación recién lanzada suele dejar abierto, y mover un secreto a una edge function es el patrón para cada clave que nunca debió estar en la página.
Preguntas frecuentes
¿Alguna vez es seguro poner claves de AWS en el código del frontend?
No. AWS lo dice en su propia guía de auditoría de seguridad, hablando de aplicaciones móviles: una aplicación nunca debería llevar claves de acceso incrustadas, ni siquiera cifradas. Un bundle de navegador se lee más fácil que un binario de aplicación, así que la misma respuesta vale aquí y le cuesta menos trabajo a un desconocido. Si tu página de verdad tiene que llegar a S3, las vías previstas son una presigned URL firmada por tu propio servidor, o credenciales temporales de Amazon Cognito, que caducan solas.
¿Qué puede hacer alguien con mi clave de acceso de AWS?
Exactamente lo que permitan sus permisos, y por eso no hay una sola respuesta. Una clave puesta en un usuario con acceso de lectura a un bucket lee ese bucket. Una clave puesta en un administrador hace todo lo que hace un administrador, incluido arrancar máquinas que facturan por hora. Una clave del usuario root de la cuenta es el peor caso, porque AWS dice que el usuario root tiene acceso sin restricciones a todos los servicios, recursos y datos de facturación. Abre IAM y lee las políticas asociadas a la clave antes de decidir cuánto preocuparte.
¿Qué es una presigned URL?
Una URL larga que deja a un navegador hacer una sola cosa con un solo archivo de S3, durante un número fijo de segundos, sin tener ninguna credencial de AWS. Tu propio servidor guarda la clave y firma la URL cuando se la piden. AWS describe las presigned URLs como acceso temporal a objetos de S3 sin credenciales de AWS, y sirven igual para descargas que para subidas.
¿Cómo compruebo si mi aplicación envía una clave de AWS?
Busca en lo que tu navegador se ha descargado, no en lo que hay en tu proyecto. Abre tu sitio publicado, pulsa F12 y busca AKIA en el JavaScript cargado. Luego busca en tu proyecto AKIA, AWS_ACCESS_KEY_ID y AWS_SECRET_ACCESS_KEY, y cualquier nombre de variable que empiece por VITE_ o NEXT_PUBLIC_ y lleve una, porque esos prefijos son lo que mete un valor en el bundle de entrada.
Mi clave de AWS está en GitHub. ¿Y ahora qué?
Desactívala primero en IAM, que es reversible y surte efecto al momento, y luego crea un recambio para el servidor que la necesitaba. Quitarla del código no cierra nada, porque el valor antiguo sigue en tu historial de commits y en cada clon que alguien se haya llevado. Después abre el historial de eventos de CloudTrail y filtra por la clave para ver qué hizo.