
Escáner de seguridad estático local para aplicaciones Next.js, Supabase y Stripe: 24 comprobaciones basadas en AST para claves filtradas, RLS y permisos faltantes, webhooks inseguros. CLI y GitHub Action.
Análisis de seguridad local y gratuito para aplicaciones Next.js + Supabase + Stripe: claves filtradas, RLS y permisos ausentes, webhooks de Stripe inseguros.
Tu agente de IA desplegó la aplicación. ¿También desplegó tu clave secreta de Stripe, una tabla sin RLS o un webhook que cualquiera puede invocar? Un solo comando lo averigua, localmente, en menos de un segundo.
npx github:edtrackai/nextjs-supabase-stripe-security-checker /path/to/your/app
Todavía no está en npm, así que npx lo ejecuta directamente desde este repositorio de GitHub (requiere Node 18+ y git).
- uses: edtrackai/nextjs-supabase-stripe-security-checker@v0
with:
fail-on: high
El flujo de trabajo completo y los permisos se encuentran a continuación y en docs/github-action.md.
Verificador de RLS de Supabase · Seguridad de webhooks de Stripe · Escaneo de seguridad de Next.js · Permisos de la API de datos de Supabase (42501)

git clone https://github.com/edtrackai/nextjs-supabase-stripe-security-checker.git
cd nextjs-supabase-stripe-security-checker && npm install
node bin/nss-security-check.js /path/to/your/app
Se requiere Node 18 o posterior. Hay una dependencia (@babel/parser). Es gratuita y tiene licencia MIT, no necesita cuenta, no realiza llamadas de red y nunca sube tu código.
--fail-on=high sale con código 1, y --json proporciona salida legible por máquina.Anuncio (de pago, mismo autor): ¿Quieres las correcciones? Launch Audit (12 $ más IVA donde corresponda) añade 28 comprobaciones, un agente auditor de IA sin herramientas Edit ni Write, y un plan de corrección archivo:línea.
| Flag | Qué hace |
|---|---|
--json | Salida legible por máquina: {tool, version, root, filesScanned, counts, findings[]} |
--fail-on=<sev> | Sale con código 1 si algún hallazgo es igual o superior a critical / high / medium / low (none nunca falla). Sale con código 2 ante argumentos incorrectos. |
--list | Imprime el catálogo de comprobaciones |
--quiet | Omite el pie de una línea sobre las herramientas de pago (se imprime en stderr solo en una terminal; también se desactiva con NO_PROMO=1, CI o GITHUB_ACTIONS) |
--help | Uso |
En CI: node path/to/bin/nss-security-check.js . --fail-on=high, o usa la GitHub Action (resumen del job, anotaciones, un comentario fijo en el PR, solo GITHUB_TOKEN). Consulta el workflow de ejemplo y los permisos en docs/github-action.md.
# .github/workflows/security-check.yml
name: Security check
on: [pull_request]
permissions:
contents: read
pull-requests: write # only for the PR comment; drop it with comment: false
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: edtrackai/nextjs-supabase-stripe-security-checker@v0 # or pin @v0.5.0 / a commit SHA
with:
path: .
fail-on: high
Los cambios se enumeran en CHANGELOG.md (0.5.0 añade la Action).
| ID | Gravedad | Área | Comprobación |
|---|---|---|---|
| K1 | crítica | Claves | Clave secreta/restringida de Stripe en vivo (sk_live_/rk_live_) en el código fuente, incluidas las claves divididas con + (la evidencia se redacta) |
| K2 | crítica | Claves | Variable NEXT_PUBLIC_ con aspecto de secreto (service role, clave secreta, secreto de webhook…) |
| K3 | crítica | Claves | Archivo .env con valores rastreados por git |
| K4 | crítica | Claves | Secreto de servidor o cliente admin de Supabase usado en un componente "use client" |
| A1 | alta | Autenticación | auth.getSession() confiado en el servidor en lugar de getUser()/getClaims() |
| A2 | alta | Autenticación | Server Action escribe datos sin comprobación de autenticación |
| A3 | alta | Autenticación | Ruta de API (POST/PUT/PATCH/DELETE) escribe datos sin comprobación de autenticación (se excluyen rutas de webhook, cron y autenticación) |
| A4 | alta | Autenticación | Cliente admin consultado por un id proporcionado en la petición sin filtro de propietario (IDOR) |
| A5 | media | Autenticación | Redirección abierta desde un parámetro de consulta |
| R1 | crítica | RLS | Tabla creada sin row level security |
| R2 | crítica | RLS | Política de escritura con using (true) / with check (true) |
| R3 | alta | RLS | Política que permite escribir al rol anon |
| R4 | alta | RLS | Función security definer sin un search_path fijo |
| R5 | media | RLS | Tabla en public sin un GRANT explícito. Supabase anunció que dejaría de aplicar grants por defecto en las nuevas tablas públicas a partir del 30 oct 2026, pero a finales de agosto un mantenedor de Supabase CLI escribió (supabase/cli PR #6337) que el cambio de valores por defecto anterior "nunca se aplicó" y que los proyectos alojados seguían concediendo permisos automáticamente, por lo que la fecha podría cambiar: comprueba qué hace tu propio proyecto. Sin un grant, la Data API devuelve 42501 |
| R6 | alta | RLS | grant ... on all tables in schema public / alter default privileges ... grant indiscriminado a anon/authenticated (la solución rápida al 42501 que también expone tablas sin RLS) |
| S1 | crítica | Stripe | Webhook sin verificación de firma (constructEvent) |
| S2 | crítica | Stripe | El webhook analiza request.json() en lugar del cuerpo sin procesar |
| S3 | media | Stripe | El webhook no tiene idempotencia sobre event.id |
| S4 | alta | Stripe | Precio o importe del checkout tomado de la petición |
| S5 | alta | Stripe | Acceso concedido en la página de éxito del checkout en lugar de en el webhook |
| S6 | media | Stripe | Deduplicación de webhooks que puede descartar un evento fallido: duplicados omitidos sin estado pending/completed |
| S7 | baja | Stripe | Periodo de facturación leído de items.data[0] (incorrecto para suscripciones con varios elementos desde la API 2025-03-31.basil) |
| H1 | media | Endurecimiento | Sin cabeceras de seguridad en next.config |
| H2 | media | Endurecimiento | dangerouslySetInnerHTML con un valor no literal |