Saltar al contenido
WebViewGold Del vibe coding a la App Store
Español
Consigue WebViewGold

Seguridad

Claves de API y secretos en apps creadas con IA: lo que un envoltorio nativo no oculta y cómo revisar el código de cualquier agente

Envolver una app web en un contenedor nativo no oculta su JavaScript: el mismo bundle se carga desde tu URL, y cualquiera puede abrirlo en un navegador. Así que se aplican las reglas de la web. Las claves publicables (Supabase publishable o anon, Stripe pk_, configuración web de Firebase, App ID de OneSignal) pueden ir en el cliente; las claves secretas (Supabase secret o service_role, Stripe sk_, App API keys de OneSignal, cuentas de servicio, claves de API de LLM) nunca. Aquí verás cómo distinguirlas, por qué la seguridad a nivel de fila es el verdadero candado y una rutina de revisión que funciona con cualquier agente de programación.

Desde $109 de pago único por plataforma, o deja que nuestro equipo lo haga todo desde $698.

  • Garantía de aprobación – o te devolvemos el dinero en 14 días
  • Sin programar
  • Pago único, actualizaciones gratuitas
  • Soporte humano 24/7
  • Hecho en Alemania desde 2014 · ISO 27001

Por qué el envoltorio nativo no protege tus claves

Una app WebViewGold es un contenedor nativo alrededor de tu URL desplegada. El JavaScript que ejecutan tus usuarios se descarga de tu servidor, exactamente igual que en Safari o Chrome. Cualquiera puede abrir tu dominio en un portátil, abrir las herramientas de desarrollo y leer cada cadena del bundle, incluidas las variables de entorno que tu herramienta de compilación haya insertado. La ficha en la tienda no cambia nada de eso.

El proyecto nativo tampoco es una caja fuerte. Los valores de Config.swift, Config.java, Info.plist, google-services.json y de los archivos custom.js y custom.css acaban dentro del IPA o del APK, y un paquete de app es un archivo comprimido que las herramientas descomprimen en segundos. Trata como público todo lo que llega a un dispositivo. A las claves pensadas para clientes eso no les afecta; a las claves secretas, sí.

Las herramientas de compilación ponen fácil el error. Las variables con el prefijo VITE_ (Vite) o NEXT_PUBLIC_ (Next.js) se insertan en el bundle del cliente por diseño. Un agente que "arregla" un error 401 moviendo una clave a una de esas variables, o pegándola en una llamada fetch, acaba de publicarla. Los agentes lo hacen porque así desaparece el error; es tu revisión la que tiene que detectarlo.

Qué claves pueden ir en el cliente y cuáles nunca

ServicioAceptable en el código del clienteSolo en el servidorLo que de verdad te protege
SupabasePublishable key (sb_publishable_...) o la antigua clave anonSecret key (sb_secret_...) o la antigua clave service_role; ambas se saltan la seguridad a nivel de filaPolíticas RLS en cada tabla y cada bucket de storage
FirebaseConfiguración web (apiKey, authDomain, projectId, appId)JSON de la cuenta de servicio, credenciales del Admin SDKSecurity Rules, App Check, restricciones de la clave de API en Google Cloud
StripeClave publicable (pk_live_...)Clave secreta (sk_live_...), claves restringidas (rk_live_...), secreto de firma de webhooks (whsec_...)Crear Checkout Sessions y PaymentIntents en tu servidor; verificar los webhooks
OneSignalApp ID (el valor de Config.swift / build.gradle)App API key (os_v2_app_...), la antigua REST API key, Organization API keyEnviar los push desde tu servidor a los IDs de suscripción que guardaste
RevenueCatClave pública del SDK específica de la app, la que usa una app nativaSecret API key para llamadas RESTDerechos de acceso comprobados en el servidor mediante webhooks
API de LLM (Gemini, OpenAI, Anthropic)NadaCualquier clave de APIUn servidor o una edge function que autentique a los usuarios y limite la frecuencia de peticiones

El patrón que los agentes fallan más a menudo

Tres versiones del mismo error y, después, la solución. La solución siempre tiene la misma forma: el cliente llama a tu propio endpoint con la sesión del usuario, y solo el servidor guarda el secreto.

javascript
// MAL: clave service_role en el navegador. Se salta todas las políticas RLS.
const admin = createClient(SUPABASE_URL, import.meta.env.VITE_SUPABASE_SERVICE_ROLE_KEY);

// MAL: clave secreta de Stripe en un fetch desde el cliente
await fetch("https://api.stripe.com/v1/checkout/sessions", {
  method: "POST",
  headers: { Authorization: "Bearer " + import.meta.env.VITE_STRIPE_SECRET_KEY }
});

// MAL: enviar push desde el navegador con la App API key de OneSignal
await fetch("https://api.onesignal.com/notifications", {
  method: "POST",
  headers: { Authorization: "Key " + ONESIGNAL_APP_API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({ app_id: ONESIGNAL_APP_ID, contents: { en: "Hi" } })
});

// BIEN: el cliente llama a tu endpoint con el token de sesión del usuario
const res = await fetch("/api/checkout", {
  method: "POST",
  headers: { Authorization: "Bearer " + session.access_token, "Content-Type": "application/json" },
  body: JSON.stringify({ priceId: "price_basic_monthly" })
});

// BIEN: en el servidor (ruta de API, Supabase Edge Function, Cloud Function)
// 1. verifica el token de sesión y obtén de él el ID de usuario, nunca del cuerpo
// 2. lee STRIPE_SECRET_KEY / ONESIGNAL_APP_API_KEY / SUPABASE_SECRET_KEY del entorno del servidor
// 3. llama al proveedor y devuelve solo lo que necesita el cliente

La seguridad a nivel de fila es el verdadero candado

Una clave publicable de Supabase solo es segura porque Postgres decide qué puede ver cada petición: sin usuario con sesión iniciada se ejecuta como anon, con uno como authenticated, y las políticas filtran las filas. Una clave secreta se ejecuta como service_role, que tiene BYPASSRLS (documentación de claves de API de Supabase). Cada tabla del esquema expuesto necesita RLS más políticas como estas; Firebase consigue lo mismo con las Security Rules. El checklist de Supabase y el checklist de Firebase cubren el resto del backend.

text
-- Supabase SQL: activa RLS y deja que cada usuario vea y cambie solo su propia fila
alter table public.profiles enable row level security;

create policy "profiles: read own" on public.profiles
  for select to authenticated
  using ((select auth.uid()) = id);

create policy "profiles: update own" on public.profiles
  for update to authenticated
  using ((select auth.uid()) = id)
  with check ((select auth.uid()) = id);

-- Reglas de Firestore, misma idea:
-- match /profiles/{uid} {
--   allow read, write: if request.auth != null && request.auth.uid == uid;
-- }

Cómo revisar el código de cualquier agente en busca de secretos filtrados

Funciona igual con Claude Code, Codex, Cursor, Gemini CLI, Antigravity, Kiro, Cline y cualquier app builder.

  1. Escanea el bundle compilado, no el código fuente

    Ejecuta la compilación de producción y busca en la carpeta de salida (escaneo más abajo). El código fuente puede parecer limpio mientras una variable de entorno insertada está en el bundle.

  2. Revisa cada prefijo público

    Enumera todas las variables que empiezan por VITE_, NEXT_PUBLIC_ o similares. Cada una tiene que ser algo que pondrías en una valla publicitaria.

  3. Revisa los proyectos nativos

    Config.swift, Config.java, Info.plist, build.gradle, custom.js y los archivos de configuración de Firebase solo pueden contener IDs públicos (App ID de OneSignal, clave pública de RevenueCat, IDs de AdMob, configuración de Firebase).

  4. Revisa el backend

    Supabase: RLS activado en cada tabla, ninguna política de escritura que sea simplemente true, avisos del Security Advisor resueltos. Firebase: ninguna regla if true. Las edge functions obtienen el usuario del token, nunca de un ID de usuario en el cuerpo de la petición.

  5. Revisa el historial y rota

    Una clave que alguna vez estuvo en un commit o en un despliegue está filtrada, aunque el último commit la haya quitado. Rótala primero en el panel del proveedor y después limpia. Activa el secret scanning y la push protection en el repositorio.

  6. Enséñaselo al agente

    Añade una sección sobre secretos a tu archivo de contexto para que la próxima sesión no lo repita: CLAUDE.md, AGENTS.md, GEMINI.md, .clinerules o el steering de Kiro, según tu agente de programación.

Un escaneo de fugas de dos minutos después de cada compilación

Ajusta las carpetas a tu stack (Vite: dist/, Next.js: .next/static/, Create React App: build/). Una coincidencia con AIza es lo esperado en una configuración web de Firebase; confirma que esa clave tiene restricciones y que no es una clave de Gemini.

bash
# 1. Compila el bundle de producción
npm run build

# 2. Busca en los archivos publicados cadenas que parezcan secretos
grep -rEn "sk_live_|sk_test_|rk_live_|whsec_|sb_secret_|os_v2_app_|BEGIN (RSA )?PRIVATE KEY|AIza[0-9A-Za-z_-]{35}" dist/ .next/static/ build/ 2>/dev/null

# 3. Las claves antiguas de Supabase son JWT: decodifica cada una y mira "role"
#    "anon" es lo esperado; "service_role" significa que se publicó una clave secreta
grep -rhoE "eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+" dist/ 2>/dev/null | sort -u | while read -r t; do
  echo "$t" | cut -d. -f2 | tr '_-' '/+' | base64 -d 2>/dev/null; echo
done

# 4. Proyectos nativos e historial de Git
grep -rEn "sk_live_|sb_secret_|os_v2_app_|service_role" ios/ android/ 2>/dev/null
git log -p --all | grep -En "sk_live_|sb_secret_|os_v2_app_|SERVICE_ROLE" | head -20

Prompt de revisión de seguridad para cualquier agente de programación

Pide primero un informe y nunca muestra los secretos completos, así que puedes ejecutarlo incluso en una sesión compartida.

Pégalo en tu agente en la raíz del proyecto
Haz una revisión de secretos y de acceso a datos de este repositorio antes de publicarlo como apps de iOS y Android con WebViewGold. Recuerda: las apps cargan nuestra app web desplegada, así que cada archivo del bundle del cliente es público, y cada valor de ios/ y android/ va dentro del paquete de la app.

1. Enumera cada clave de API, token, secreto y credencial referenciados en cualquier parte: código fuente, archivos env, configuración, workflows de CI, ios/, android/. Para cada uno: servicio, dónde se usa, si llega al bundle del cliente y si es publicable (Supabase publishable o anon, Stripe pk_, configuración web de Firebase, App ID de OneSignal, clave pública del SDK de RevenueCat) o secreto (Supabase secret o service_role, Stripe sk_, rk_ o whsec_, App API key de OneSignal, cuentas de servicio, cualquier clave de API de un LLM).
2. Para cada secreto que llegue al cliente: propone trasladar la llamada a una ruta o función del servidor que verifique primero la sesión del usuario y lea el secreto de variables de entorno del servidor. Muéstrame el plan antes de cambiar código de pagos.
3. Supabase: enumera cada tabla y bucket de storage, si tiene RLS activado y qué políticas existen. Marca las tablas sin RLS y las políticas que no comprueben auth.uid(). Firebase: marca las reglas que permitan el acceso sin request.auth o entre usuarios.
4. Edge functions y rutas de API: marca las que tomen un ID de usuario del cuerpo de la petición en lugar del token verificado.
5. Historial de Git: encuentra archivos .env o claves que estén en commits y enumera qué claves hay que rotar. No reescribas el historial.
6. Añade a nuestro archivo de contexto del agente una sección breve sobre secretos que nombre las claves publicables permitidas y prohíba todo lo demás en el código del cliente.

Presenta el informe como tabla. Nunca muestres valores secretos completos; enseña solo los 6 primeros caracteres.

Errores que sobreviven a una revisión rápida

  • "Es una app, nadie puede ver el código." El código está en tu URL y en el paquete; ambos son públicos.
  • Ofuscar en lugar de eliminar. Base64, partir cadenas o minificar no protegen una clave; cualquiera que ejecute la app puede ver cómo se usa.
  • Una "página de administración" en el mismo bundle que usa la clave service role. Las funciones de administración necesitan sus propios endpoints en el servidor con comprobación de roles.
  • Un endpoint de eliminación de cuenta o de push que confía en un ID de usuario de la petición. Así cualquiera puede borrar o enviar mensajes a cualquiera; obtén el usuario del token verificado.
  • Funciones de pago desbloqueadas con un flag en el cliente. Comprueba los derechos de acceso en el servidor; la guía de RevenueCat muestra el patrón con webhooks.
  • Borrar del código una clave filtrada sin rotarla. El valor antiguo sigue funcionando para quien lo copió.

Checklist de lanzamiento

  • El escaneo de fugas no encuentra prefijos de secretos en el resultado de la compilación, en los proyectos nativos ni en el historial de Git.
  • Cada tabla y bucket de Supabase tiene RLS; las reglas de Firebase no tienen ningún if true.
  • Los pagos, los push, las llamadas a LLM y la eliminación de cuenta pasan por código del servidor que verifica al usuario.
  • Las claves públicas de Google API están restringidas a las API que necesitan; Gemini tiene su propia clave en el servidor.
  • Los push de OneSignal se envían desde tu servidor con la App API key, usando los IDs de suscripción que comunican las apps (consulta la documentación de notificaciones push).
  • Cualquier clave que se haya filtrado alguna vez se ha rotado, no solo eliminado.

Preguntas frecuentes

¿Es segura la clave anon de Supabase en mi app?

Sí, está pensada para clientes públicos, igual que la nueva publishable key, siempre que la seguridad a nivel de fila esté activada con políticas razonables en cada tabla. Supabase prevé retirar las antiguas claves anon y service_role a finales de 2026, así que los proyectos nuevos deberían usar publishable y secret keys.

¿Alguien puede extraer claves de mi app de iOS o Android?

Sí. El bundle web se descarga de tu URL y se puede leer en cualquier navegador, y los archivos IPA y APK son archivos comprimidos cuyos archivos de configuración y cadenas son fáciles de inspeccionar. Incluye solo claves que estén pensadas para ser públicas.

¿La apiKey de Firebase es un secreto?

No por diseño; identifica tu proyecto, y quienes protegen son las Security Rules y App Check. Como las claves de Google API sin restricciones también pueden llegar a la Gemini API una vez activada en el proyecto, restringe la clave a las API que necesita y mantén las llamadas a Gemini en el servidor.

¿Qué valor de OneSignal va en la app WebViewGold?

Solo el App ID público, en Config.swift en iOS y en build.gradle en Android. La App API key que envía las notificaciones se queda en tu servidor, que se dirige a los usuarios mediante los IDs de suscripción que la app pasa a tu app web.

Se me ha filtrado una clave. ¿Y ahora qué?

Rótala de inmediato en el panel del proveedor, revisa los registros y la facturación del proveedor por si hay abusos, y después quítala del código y traslada la llamada al servidor. Eliminarla sin rotarla deja la clave antigua válida.

¿Rechazarán Apple o Google mi app por una clave expuesta?

La revisión de las tiendas no es una auditoría de seguridad, así que no cuentes con ella para detectar fugas. Los riesgos reales son el abuso, las facturas inesperadas y los datos de usuarios expuestos, que pueden convertirse en un problema de privacidad con ambas tiendas.

Tres formas de llevar tu app a las tiendas

El mismo resultado, con distinta cantidad de trabajo por tu parte. Las tres incluyen la garantía de aprobación y soporte humano 24/7.

Hazlo tú mismo

$109 por plataforma, pago único

La licencia de WebViewGold: un proyecto de Xcode o Android Studio listo para usar. Pega tu URL, define nombre e icono, activa funciones, compila y envía. Actualizaciones gratuitas para siempre.

Ver planes en la página principal

Full Service

$698 por plataforma $798

Envíanos tu URL y tu icono. Nuestro equipo configura, compila y entrega tu app en 72 horas, en marca blanca y con los proyectos fuente. Ambas plataformas por $1,158.

Detalles del Full Service Con tecnología de WeSetupYourWebViewApp

Precios de la Oferta de invierno válidos hasta el 31 de diciembre de 2026.

¿Listo para lanzar tu app?

Garantía de aprobación en App Store & Google Play – o te devolvemos el dinero en 14 días

¿Prefieres delegarlo? Full Service: configuramos, compilamos y entregamos en 72 horas, desde $698 por plataforma. ¿Preguntas? Chatea con nosotros 24/7.

Los reembolsos se conceden bajo las condiciones indicadas en nuestras FAQ: un rechazo por motivos ajenos al contenido por parte de Apple y/o Google, reclamado dentro de los 14 días posteriores a la compra.