Auditoria de seguridad — El cazador adversario
"La app funciona, respeta la configuracion, va rapida y se ve bien. Aun asi, una tabla puede tener una policy USING(true) que deja a cualquiera con la llave publica (que esta en el HTML) leer los datos de todos los usuarios. Nadie lo vio porque la app funciona. Esa es la clase de hueco que esta skill caza — pensando como el atacante, no como el usuario."
Esta skill es el complemento ofensivo de validacion-visual: aquella prueba que la app funciona; ésta intenta romperla. Recorre 7 dimensiones de ataque, asigna severidad a cada hallazgo, y arregla en un bucle caza-arregla-revalida.
Las 7 dimensiones
D1 — Secretos y exposición
¿Hay credenciales filtradas en el código, en el bundle que ve el navegador, o en git?
- Grep del repo por patrones de secreto:
eyJ (JWT), sk-, service_role, SECRET, PRIVATE_KEY, password\s*=, BEGIN.*PRIVATE KEY.
- Confirmar que
.env.local / .env* están en .gitignore y nunca se commitearon (git log --all -- .env*).
- Llave maestra en el cliente: grep de
SUPABASE_SERVICE_ROLE_KEY o createAdminClient en archivos 'use client' → CRÍTICO si aparece.
NEXT_PUBLIC_* que filtra: solo deben ser públicas la URL de Supabase, la anon key y sitekeys de captcha. Cualquier otro secreto con ese prefijo se filtra al navegador.
- Buscar secretos en el bundle de producción (
.next/static/**/*.js y el HTML servido). La anon key es pública por diseño; cualquier OTRO secreto = CRÍTICO.
D2 — Base de datos / Supabase (RLS, grants, funciones)
- Los advisors de seguridad de Supabase → 0 errores (los warnings se evalúan caso a caso).
- RLS habilitado en TODAS las tablas públicas:
SELECT relname FROM pg_class WHERE relnamespace='public'::regnamespace
AND relkind='r' AND relrowsecurity=false; -- debe estar vacío
- Ninguna policy
USING(true) / WITH CHECK(true) sin justificación explícita:SELECT tablename, policyname, cmd, qual, with_check FROM pg_policies
WHERE schemaname='public' AND (qual='true' OR with_check='true');
- Funciones
SECURITY DEFINER invocables por anon/authenticated sin justificación → revisar REVOKE.
- Funciones con
search_path mutable (riesgo de schema hijack).
- Tablas server-only con
REVOKE ALL FROM anon, authenticated; buckets de Storage privados salvo justificación.
D3 — Control de acceso ofensivo (IDOR, escalación, bypass)
Con dos usuarios de prueba A y B:
- IDOR: B intenta acceder a recursos de A por ID directo en URL y en cada API (
GET/PATCH/DELETE /recurso/[id_de_A]). Debe dar 403/404, nunca el recurso.
- Bypass de middleware: pegar URLs protegidas sin sesión; confirmar que la protección es server-side, no solo un guard de cliente. Probar también las server actions y route handlers directamente.
- Escalación de privilegios: un usuario normal intenta acciones de admin (endpoints, server actions, paneles
/admin/*). Manipular el rol en el cliente y confirmar que el server lo re-valida.
- Manipulación de sesión: alterar la cookie/JWT, usar uno expirado, reusar el de otro usuario.
- Multi-tenant: si hay
organization_id, cruzar tenants.
D4 — Headers de seguridad y CSP
curl -I contra producción + revisar next.config:
- Presencia y valor de
Strict-Transport-Security, X-Frame-Options (o frame-ancestors), X-Content-Type-Options: nosniff, Referrer-Policy, Permissions-Policy, Content-Security-Policy.
- CSP:
object-src 'none', frame-ancestors, base-uri 'self'. (Next.js suele requerir unsafe-inline/unsafe-eval para hidratar — documentarlo como aceptado, no como bug.)
- Cookies de auth con
Secure + HttpOnly + SameSite; HTTPS forzado.
D5 — Inyección y validación de entrada
- XSS: grep de
dangerouslySetInnerHTML, innerHTML, render de input sin escapar. Probar payloads <script>, "><img onerror> en campos y parámetros de URL.
- SQLi: PostgREST parametriza por defecto; el riesgo real es SQL crudo (
rpc mal construido, interpolación de strings en queries). Grep de concatenación en queries.
- SSRF: endpoints que hacen
fetch a una URL derivada de input del usuario (proxies de imagen, webhooks). Verificar allowlist de hosts.
- Path traversal en Storage / manejo de archivos (
../, paths absolutos).
- Mass assignment: server actions que hacen
update(body) con todo el body sin allowlist → un atacante setea role: 'admin' o un user_id ajeno.
D6 — Endpoints públicos, webhooks y supply chain
- Endpoints de prueba olvidados: grep de
/api/public/dev-*, /api/debug, /api/test, dev-magic-session. (Las skills de QA crean endpoints temporales; confirmar que se borraron.) Probar en producción.
- Webhooks: todos los
/api/webhooks/* deben verificar origen (HMAC, signature, secret-per-request). Webhook sin secret → debe dar 401/400.
- Rate limiting: login/signup/forgot-password y endpoints con LLM deben limitar intentos (fuerza bruta + abuso de tokens). Ráfaga → 429 o captcha.
- Métodos HTTP: endpoints que solo aceptan POST rechazan GET/PUT/DELETE (405).
- CORS: sin
Access-Control-Allow-Origin: * en endpoints con datos.
- Supply chain:
npm audit --omit=dev → revisar críticas/altas. Lockfile commiteado.
D7 — Seguridad de IA (solo si el proyecto tiene agente/LLM)
Auto-detección: si hay un asistente/chat con LLM, activar; si no, omitir y aclararlo.
- Prompt injection: "ignora tus instrucciones", pedir que revele su system prompt o su stack técnico.
- Guardrails: confirmar que el asistente rechaza usos fuera de su función.
- Exfiltración: intentar que devuelva datos de otros usuarios vía su herramienta de búsqueda o tools.
- Sanitizer de salida: que filtre datos sensibles antes de mostrarlos.
- Límite de gasto: rate limit del chat para que nadie drene tu presupuesto de tokens.
Triage por severidad (esto define qué bloquea la publicación)
| Severidad |
Definición |
Efecto |
| Crítico |
Explotable hoy, expone datos/credenciales o da acceso no autorizado (secret en cliente, IDOR, RLS off sobre datos sensibles, endpoint dev en prod) |
BLOQUEA la publicación. |
| Alto |
Explotable con esfuerzo, o defensa importante ausente (sin CSP, webhook sin auth) |
Bloquea entrega/lanzamiento. Se arregla. |
| Medio |
Riesgo real pero acotado |
No bloquea; se arregla si es barato. |
| Bajo |
Higiene / defensa en profundidad |
Recomendación documentada. |
Autonomía: se pausa por IMPACTO, no por dificultad técnica
Arreglar al momento (reversible con git): código, headers, CSP, validaciones, guards, sanitización, allowlists, RLS/policies/grants faltantes (verificando que no se deje fuera ningún acceso legítimo), borrar endpoints de prueba, npm audit fix sin breaking.
Pausar y preguntar (decisión de negocio): rotar una credencial (puede tumbar una integración viva — el cuándo lo decide el dueño), tocar datos reales de producción, un cambio que podría dejar fuera a usuarios reales, algo con costo o irreversible, o un crítico que requiere rediseño.
Modulación por tráfico: app sin usuarios → autonomía máxima. App con clientes reales → más cuidado con lo que toca acceso/datos/sesiones; ante la duda, verificar antes de cerrar.
El flujo
- Mapear la superficie de ataque: rutas, API routes, webhooks, server actions, páginas admin, tablas, si hay agente IA (activa D7), si hay pagos. Detectar si la app tiene usuarios reales en producción (modula la autonomía).
- Setup (si hay auth): crear dos usuarios de prueba A y B; borrarlos al final.
- Ejecutar las 7 dimensiones en orden. Cada hallazgo: severidad + evidencia + causa raíz.
- Loop caza-arregla-revalida: arreglar lo de bajo impacto al momento (y re-probar el vector); pausar lo que es decisión de negocio. Un crítico sin resolver bloquea.
- Limpieza OBLIGATORIA: borrar usuarios de prueba, registros y payloads de prueba (NUNCA dejar un
<script> de prueba persistido en la base), endpoints temporales; cerrar el navegador.
- Reporte + autorización: resumen por severidad (encontrados / arreglados / pausados). El push/deploy SIEMPRE lo autoriza el dueño.
Reglas duras
- Scope estrictamente defensivo: solo TUS apps. Nunca targets externos.
- Mentalidad adversaria real (intentar romper, no marcar checkboxes) pero sin causar daño (no DoS real, no borrar datos, payloads inertes).
- Se pausa por impacto de negocio, no por dificultad técnica.
- Un crítico bloquea la publicación. Sin excepción.
- Cada hallazgo lleva severidad — sin severidad no hay triage.
- Todo cambio de RLS se re-valida contra los flujos reales, para confirmar que no rompió acceso legítimo.
- NUNCA dejar payloads de prueba persistidos en la base o el Storage.
- Secretos jamás al chat, logs o screenshots. Los scripts leen
.env.local y no imprimen secretos.
- El push/deploy final SIEMPRE lo autoriza el dueño.
- Si una dimensión no aplica (sin auth, sin IA, sin pagos), aclararlo — no saltarla en silencio.
Anti-patrones (prohibidos)
- "Corrí el advisor y dio 0, listo." → El advisor es solo D2. Faltan IDOR, headers, inyección, endpoints, supply chain, IA.
- "Pausé el fix porque era complejo." → Se pausa por consecuencia de negocio, no por dificultad.
- "Arreglé la policy RLS y seguí sin probar." → Todo cambio de RLS se re-valida contra los flujos reales.
- "Dejé el payload
<script> de prueba en la base."
- "Encontré un crítico pero el resto estaba bien, aprobé." → Un crítico sin resolver BLOQUEA.
- "La app no tiene pagos, así que omití todo." → Solo se omite la dimensión que no aplica; las demás corren.
1---2name: auditoria-de-seguridad3description: Auditoria de seguridad ofensiva y defensiva de tu app Next.js + Supabase. Piensa como ATACANTE: intenta romper los candados (secretos expuestos, RLS debil, IDOR/escalacion de privilegios, headers faltantes, inyeccion XSS/SQLi/SSRF, endpoints de prueba olvidados, webhooks sin auth, dependencias vulnerables, inyeccion de prompts en agentes IA), caza lo que se escapo, y lo ARREGLA en un loop caza-arregla-revalida con triage por severidad. Activar cuando el usuario dice: auditoria de seguridad, busca huecos de seguridad, pentest, red team, /auditoria-de-seguridad. OBLIGATORIA antes de un lanzamiento publico o entrega a cliente, y ante cualquier cambio que toque auth/RLS/secrets/pagos.4---56# Auditoria de seguridad — El cazador adversario78> "La app funciona, respeta la configuracion, va rapida y se ve bien. Aun asi, una tabla puede tener una policy `USING(true)` que deja a cualquiera con la llave publica (que esta en el HTML) leer los datos de todos los usuarios. Nadie lo vio porque la app funciona. Esa es la clase de hueco que esta skill caza — pensando como el atacante, no como el usuario."910Esta skill es el complemento ofensivo de `validacion-visual`: aquella prueba que la app **funciona**; ésta intenta **romperla**. Recorre 7 dimensiones de ataque, asigna severidad a cada hallazgo, y arregla en un bucle caza-arregla-revalida.1112---1314## Las 7 dimensiones1516### D1 — Secretos y exposición17¿Hay credenciales filtradas en el código, en el bundle que ve el navegador, o en git?18- Grep del repo por patrones de secreto: `eyJ` (JWT), `sk-`, `service_role`, `SECRET`, `PRIVATE_KEY`, `password\s*=`, `BEGIN.*PRIVATE KEY`.19- Confirmar que `.env.local` / `.env*` están en `.gitignore` y nunca se commitearon (`git log --all -- .env*`).20- **Llave maestra en el cliente:** grep de `SUPABASE_SERVICE_ROLE_KEY` o `createAdminClient` en archivos `'use client'` → **CRÍTICO** si aparece.21- **`NEXT_PUBLIC_*` que filtra:** solo deben ser públicas la URL de Supabase, la anon key y sitekeys de captcha. Cualquier otro secreto con ese prefijo se filtra al navegador.22- Buscar secretos en el bundle de producción (`.next/static/**/*.js` y el HTML servido). La anon key es pública por diseño; cualquier OTRO secreto = CRÍTICO.2324### D2 — Base de datos / Supabase (RLS, grants, funciones)25- Los advisors de seguridad de Supabase → **0 errores** (los warnings se evalúan caso a caso).26- RLS habilitado en TODAS las tablas públicas:27 ```sql28 SELECT relname FROM pg_class WHERE relnamespace='public'::regnamespace29 AND relkind='r' AND relrowsecurity=false; -- debe estar vacío30 ```31- **Ninguna policy `USING(true)` / `WITH CHECK(true)`** sin justificación explícita:32 ```sql33 SELECT tablename, policyname, cmd, qual, with_check FROM pg_policies34 WHERE schemaname='public' AND (qual='true' OR with_check='true');35 ```36- Funciones `SECURITY DEFINER` invocables por anon/authenticated sin justificación → revisar `REVOKE`.37- Funciones con `search_path` mutable (riesgo de schema hijack).38- Tablas server-only con `REVOKE ALL FROM anon, authenticated`; buckets de Storage privados salvo justificación.3940### D3 — Control de acceso ofensivo (IDOR, escalación, bypass)41Con dos usuarios de prueba A y B:42- **IDOR:** B intenta acceder a recursos de A por ID directo en URL y en cada API (`GET/PATCH/DELETE /recurso/[id_de_A]`). Debe dar 403/404, nunca el recurso.43- **Bypass de middleware:** pegar URLs protegidas sin sesión; confirmar que la protección es **server-side**, no solo un guard de cliente. Probar también las server actions y route handlers directamente.44- **Escalación de privilegios:** un usuario normal intenta acciones de admin (endpoints, server actions, paneles `/admin/*`). Manipular el rol en el cliente y confirmar que el server lo re-valida.45- **Manipulación de sesión:** alterar la cookie/JWT, usar uno expirado, reusar el de otro usuario.46- **Multi-tenant:** si hay `organization_id`, cruzar tenants.4748### D4 — Headers de seguridad y CSP49`curl -I` contra producción + revisar `next.config`:50- Presencia y valor de `Strict-Transport-Security`, `X-Frame-Options` (o `frame-ancestors`), `X-Content-Type-Options: nosniff`, `Referrer-Policy`, `Permissions-Policy`, `Content-Security-Policy`.51- CSP: `object-src 'none'`, `frame-ancestors`, `base-uri 'self'`. (Next.js suele requerir `unsafe-inline`/`unsafe-eval` para hidratar — documentarlo como aceptado, no como bug.)52- Cookies de auth con `Secure` + `HttpOnly` + `SameSite`; HTTPS forzado.5354### D5 — Inyección y validación de entrada55- **XSS:** grep de `dangerouslySetInnerHTML`, `innerHTML`, render de input sin escapar. Probar payloads `<script>`, `"><img onerror>` en campos y parámetros de URL.56- **SQLi:** PostgREST parametriza por defecto; el riesgo real es SQL crudo (`rpc` mal construido, interpolación de strings en queries). Grep de concatenación en queries.57- **SSRF:** endpoints que hacen `fetch` a una URL derivada de input del usuario (proxies de imagen, webhooks). Verificar allowlist de hosts.58- **Path traversal** en Storage / manejo de archivos (`../`, paths absolutos).59- **Mass assignment:** server actions que hacen `update(body)` con todo el body sin allowlist → un atacante setea `role: 'admin'` o un `user_id` ajeno.6061### D6 — Endpoints públicos, webhooks y supply chain62- **Endpoints de prueba olvidados:** grep de `/api/public/dev-*`, `/api/debug`, `/api/test`, `dev-magic-session`. (Las skills de QA crean endpoints temporales; confirmar que se borraron.) Probar en producción.63- **Webhooks:** todos los `/api/webhooks/*` deben verificar origen (HMAC, signature, secret-per-request). Webhook sin secret → debe dar 401/400.64- **Rate limiting:** login/signup/forgot-password y endpoints con LLM deben limitar intentos (fuerza bruta + abuso de tokens). Ráfaga → 429 o captcha.65- **Métodos HTTP:** endpoints que solo aceptan POST rechazan GET/PUT/DELETE (405).66- **CORS:** sin `Access-Control-Allow-Origin: *` en endpoints con datos.67- **Supply chain:** `npm audit --omit=dev` → revisar críticas/altas. Lockfile commiteado.6869### D7 — Seguridad de IA (solo si el proyecto tiene agente/LLM)70Auto-detección: si hay un asistente/chat con LLM, activar; si no, omitir y aclararlo.71- **Prompt injection:** "ignora tus instrucciones", pedir que revele su system prompt o su stack técnico.72- **Guardrails:** confirmar que el asistente rechaza usos fuera de su función.73- **Exfiltración:** intentar que devuelva datos de otros usuarios vía su herramienta de búsqueda o tools.74- **Sanitizer de salida:** que filtre datos sensibles antes de mostrarlos.75- **Límite de gasto:** rate limit del chat para que nadie drene tu presupuesto de tokens.7677---7879## Triage por severidad (esto define qué bloquea la publicación)8081| Severidad | Definición | Efecto |82|---|---|---|83| **Crítico** | Explotable hoy, expone datos/credenciales o da acceso no autorizado (secret en cliente, IDOR, RLS off sobre datos sensibles, endpoint dev en prod) | **BLOQUEA la publicación.** |84| **Alto** | Explotable con esfuerzo, o defensa importante ausente (sin CSP, webhook sin auth) | Bloquea entrega/lanzamiento. Se arregla. |85| **Medio** | Riesgo real pero acotado | No bloquea; se arregla si es barato. |86| **Bajo** | Higiene / defensa en profundidad | Recomendación documentada. |8788---8990## Autonomía: se pausa por IMPACTO, no por dificultad técnica9192**Arreglar al momento (reversible con git):** código, headers, CSP, validaciones, guards, sanitización, allowlists, RLS/policies/grants faltantes (verificando que no se deje fuera ningún acceso legítimo), borrar endpoints de prueba, `npm audit fix` sin breaking.9394**Pausar y preguntar (decisión de negocio):** rotar una credencial (puede tumbar una integración viva — el *cuándo* lo decide el dueño), tocar datos reales de producción, un cambio que podría dejar fuera a usuarios reales, algo con costo o irreversible, o un crítico que requiere rediseño.9596**Modulación por tráfico:** app sin usuarios → autonomía máxima. App con clientes reales → más cuidado con lo que toca acceso/datos/sesiones; ante la duda, verificar antes de cerrar.9798---99100## El flujo1011021. **Mapear la superficie de ataque:** rutas, API routes, webhooks, server actions, páginas admin, tablas, si hay agente IA (activa D7), si hay pagos. Detectar si la app tiene usuarios reales en producción (modula la autonomía).1032. **Setup** (si hay auth): crear dos usuarios de prueba A y B; borrarlos al final.1043. **Ejecutar las 7 dimensiones** en orden. Cada hallazgo: severidad + evidencia + causa raíz.1054. **Loop caza-arregla-revalida:** arreglar lo de bajo impacto al momento (y re-probar el vector); pausar lo que es decisión de negocio. Un crítico sin resolver bloquea.1065. **Limpieza OBLIGATORIA:** borrar usuarios de prueba, registros y payloads de prueba (NUNCA dejar un `<script>` de prueba persistido en la base), endpoints temporales; cerrar el navegador.1076. **Reporte + autorización:** resumen por severidad (encontrados / arreglados / pausados). El push/deploy SIEMPRE lo autoriza el dueño.108109---110111## Reglas duras1121131. **Scope estrictamente defensivo:** solo TUS apps. Nunca targets externos.1142. **Mentalidad adversaria real** (intentar romper, no marcar checkboxes) pero **sin causar daño** (no DoS real, no borrar datos, payloads inertes).1153. **Se pausa por impacto de negocio, no por dificultad técnica.**1164. **Un crítico bloquea la publicación.** Sin excepción.1175. **Cada hallazgo lleva severidad** — sin severidad no hay triage.1186. **Todo cambio de RLS se re-valida** contra los flujos reales, para confirmar que no rompió acceso legítimo.1197. **NUNCA dejar payloads de prueba persistidos** en la base o el Storage.1208. **Secretos jamás al chat, logs o screenshots.** Los scripts leen `.env.local` y no imprimen secretos.1219. **El push/deploy final SIEMPRE lo autoriza el dueño.**12210. **Si una dimensión no aplica** (sin auth, sin IA, sin pagos), aclararlo — no saltarla en silencio.123124---125126## Anti-patrones (prohibidos)127128- "Corrí el advisor y dio 0, listo." → El advisor es solo D2. Faltan IDOR, headers, inyección, endpoints, supply chain, IA.129- "Pausé el fix porque era complejo." → Se pausa por consecuencia de negocio, no por dificultad.130- "Arreglé la policy RLS y seguí sin probar." → Todo cambio de RLS se re-valida contra los flujos reales.131- "Dejé el payload `<script>` de prueba en la base."132- "Encontré un crítico pero el resto estaba bien, aprobé." → Un crítico sin resolver BLOQUEA.133- "La app no tiene pagos, así que omití todo." → Solo se omite la dimensión que no aplica; las demás corren.