¿Claves de OpenAI o Stripe en tu app hecha con IA? Llévalas a una función en el servidor
Cualquiera puede copiar una clave del frontend. Qué claves pueden ir en el navegador, cómo encontrar las que no y cómo moverlas a una Edge Function de Supabase.
4 min de lectura
Le pediste a tu herramienta de IA que añadiera un chatbot, pagos o envío de correos a tu app. Funcionó a la primera, y muchas veces funcionó tan rápido porque la clave de la API fue directa al código del frontend.
Todo lo que está en el frontend se envía al navegador de cada visitante. Si una clave está ahí, cualquiera puede abrir las herramientas de desarrollo, copiarla y usarla, con tu cuenta y tu factura. Hay escáneres automáticos que buscan justo estas claves en webs públicas.
Qué claves pueden estar en el navegador
No todas las claves son secretas. Muchos servicios emiten dos tipos a propósito:
| Pueden estar en el navegador | Nunca en el navegador |
|---|---|
Clave publicable de Stripe (pk_live_…) |
Clave secreta o restringida de Stripe (sk_live_…, rk_live_…) |
| Clave publicable o anon de Supabase | Clave secreta o service role de Supabase (qué hacer si se filtró) |
| IDs de analítica y de etiquetas | Claves de OpenAI, Anthropic y otras IA (sk-…) |
| Claves de mapas restringidas a tu dominio | Claves de envío de correo (Resend re_…, SendGrid, Postmark) |
La regla: si la clave permite gastar dinero, leer datos privados o enviar algo en tu nombre, su sitio es un servidor.
Lo que cuesta una clave filtrada depende del servicio. Una clave de IA puede generar una factura grande en horas. Una clave secreta de Stripe puede leer tus clientes y emitir reembolsos. Una clave de correo permite enviar phishing desde tu dominio, lo que además perjudica la entrega de tus correos durante meses.
Encuentra las claves en tu app publicada
Abre tu app, pulsa F12, ve a Sources (Chrome) o Debugger (Firefox) y busca en todos los archivos (Ctrl+Shift+F o Cmd+Option+F): sk-, sk_live, rk_live, re_, sb_secret_, service_role.
Si tienes el código en local, genera el build y busca en el resultado, que es exactamente lo que reciben los visitantes:
npm run build
grep -rEn "sk-[A-Za-z0-9_-]{20,}|sk_live_|rk_live_|re_[A-Za-z0-9_]{20,}|sb_secret_|service_role" dist/Un origen habitual es el archivo de entorno. En proyectos Vite, que incluyen la mayoría de las apps de Lovable y Bolt, toda variable que empieza por VITE_ se copia al código del navegador. En Next.js ocurre lo mismo con NEXT_PUBLIC_. Un secreto en VITE_OPENAI_API_KEY es público, aunque viva en .env.
Cómo corregirlo
1. Reemplaza la clave primero
Mover el código no “desfiltra” la clave. En el panel del proveedor, crea una clave nueva y elimina la antigua. Hazlo primero, aunque creas que nadie la ha visto.
Ya que estás, pon un límite de gasto mensual en los proveedores de IA. Convierte un posible desastre en uno con tope.
2. Guarda la clave nueva como secreto del servidor
En Supabase, las Edge Functions leen los secretos de su entorno. Añade la clave en el panel, en Edge Functions → Secrets, o con la CLI:
supabase secrets set OPENAI_API_KEY=sk-your-new-keyLovable tiene su propio lugar para los secretos y los pide cuando una integración necesita uno, en lugar de ponerlos en el código. En Vercel o Netlify, usa variables de entorno sin el prefijo VITE_ o NEXT_PUBLIC_, que solo leen las funciones de servidor.
3. Llama al servicio desde una función en el servidor
El navegador llama a tu función; tu función llama al servicio con la clave secreta. Una Edge Function mínima para una función de IA:
import { createClient } from 'npm:@supabase/supabase-js@2';
Deno.serve(async (req) => {
// 1. Only signed-in users may call this function.
const supabase = createClient(
Deno.env.get('SUPABASE_URL')!,
Deno.env.get('SUPABASE_ANON_KEY')! // public key: it only identifies the project
);
const token = (req.headers.get('Authorization') ?? '').replace('Bearer ', '');
const { data: { user } } = await supabase.auth.getUser(token);
if (!user) return new Response('Unauthorized', { status: 401 });
// 2. Accept only what the feature needs.
const { question } = await req.json();
if (typeof question !== 'string' || question.length > 2000) {
return new Response('Bad request', { status: 400 });
}
// 3. The secret key never leaves the server.
const response = await fetch('https://api.openai.com/v1/chat/completions', {
method: 'POST',
headers: {
Authorization: `Bearer ${Deno.env.get('OPENAI_API_KEY')}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
model: Deno.env.get('OPENAI_MODEL'),
max_tokens: 500,
messages: [{ role: 'user', content: question }]
})
});
const data = await response.json();
return Response.json({ answer: data.choices?.[0]?.message?.content ?? '' });
});En la app, la llamada pasa a ser supabase.functions.invoke('ask', { body: { question } }), y el cliente de Supabase envía con ella el token del usuario que inició sesión.
4. Pagos: que el servidor decida el precio
En Stripe, mover la clave no basta. Si el navegador le dice a tu función cuánto cobrar, alguien puede cambiar el importe. El navegador solo debe enviar qué se compra (el ID de un producto o plan); el servidor busca el precio, crea la Checkout Session con la clave secreta y devuelve el enlace. Después, confirma los pagos mediante webhooks verificados con la firma de Stripe, nunca desde una página de “éxito” que el usuario puede abrir directamente.
Antes de cada publicación
- Busca en el build los patrones de arriba.
- Mantén los secretos fuera de cualquier variable con prefijo público.
- Cuando la IA proponga poner una clave en el frontend “por ahora”, pídele una función en el servidor.
Si tu app maneja pagos, funciones de IA o datos personales, nuestro Security Check gratuito busca claves expuestas, funciones abiertas y webhooks sin firma desde fuera, en modo de solo lectura, y te envía por escrito los problemas más graves.
Claves de APIApps hechas con IASupabaseBolt
Este artículo también está disponible en: EnglishPortuguês日本語中文
Sigue leyendo
Todos los artículos
Seguridad de apps
Antes de lanzar una app hecha con Lovable: lista de seguridad previa al lanzamiento
Quince comprobaciones antes de que lleguen usuarios y datos reales: reglas, claves, storage, inicio de sesión, pagos, funciones y copias. Casi todas en minutos.
4 min de lectura

Seguridad de apps
Buckets públicos en Supabase Storage: ¿están expuestos los archivos de tus usuarios?
Los avatares pueden ser públicos; los documentos, facturas y contratos, no. Cómo revisar los buckets y reglas de Supabase Storage y corregirlos sin romper nada.
4 min de lectura

Seguridad de apps
¿Encontraste tu clave secreta de Supabase en el navegador? Qué hacer en la primera hora
Una clave service role o secreta en el frontend da acceso total a tu base de datos. El orden correcto: revocar, reemplazar, revisar logs y corregir la causa.
5 min de lectura