Back to skill

Security audit

conecta-tu-app-f100k

Security checks for vulnerabilities and agentic risk

Overview

The skill is not malware, but it needs review because it can deploy to production and automatically change paid-member access, and its Stripe template is under-scoped.

Before installing, review it as a high-authority deployment and billing-access skill. Use a project-scoped Vercel token, prefer a preview or staging deployment, require confirmation before production deploys, keep service-role and billing secrets server-only, back up the membership tables, and add Stripe product or price allowlists before relying on the Stripe templates for paid access.

Vulnerability Patterns
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (1)

T09 · Insecure Skill Coding Practices

Error
Location
plantillas/app/api/webhooks/stripe/route.ts:28
Finding

Stripe Events and Subscriptions Are Not Scoped to the Product Authorizing Application Access

Content
View full analysis

Vulnerability Details

File Location:

  • plantillas/app/api/webhooks/stripe/route.ts:28-38
  • plantillas/app/api/cron/refresco/route.ts:25-31

Vulnerability Type: Improper authorization caused by missing Stripe product and price validation
Risk Level: High

Vulnerable Code

Webhook access grant:

ts
if (evento.type === 'checkout.session.completed') {
  const s = evento.data.object
  const email = normalizarCorreo(s.customer_details?.email ?? s.customer_email)
  if (!EMAIL_REGEX.test(email)) return new Response('Sin correo en el pago.', { status: 200 })
  const { data: lote, error: e1 } = await svc.from('lotes_miembros')
    .insert({ hecho_por: 'stripe', origen: 'pasarela', agregados: 1, nota: `pago: ${email}` }).select('id').single()
  if (e1 || !lote) return new Response('No pude crear el lote.', { status: 500 })
  const { error } = await svc.from('miembros_activos')
    .upsert({ email, nombre: s.customer_details?.name ?? null, lote_id: lote.id, origen: 'pasarela' })

Scheduled subscription reconciliation:

ts
for (const status of ['active', 'trialing', 'past_due'] as const) {
  for await (const sub of stripe.subscriptions.list({ status, limit: 100, expand: ['data.customer'] })) {
    const c = sub.customer as Stripe.Customer | Stripe.DeletedCustomer
    if ('email' in c && c.email) vistos.set(normalizarCorreo(c.email), c.name ?? null)
  }
}
return [...vistos].map(([email, nombre]) => ({ email, nombre }))

Technical Analysis

The webhook verifies Stripe’s signature, which establishes that the event originated from Stripe. However, signature verification does not establish that the Checkout Session concerns the specific product or price that should authorize access to this application.

For every checkout.session.completed event delivered to the endpoint, the handler extracts the customer email and inserts it into the privileged miembros_activos table through a Supabase service-role client. It does not inspect the Checko ...[truncated 2192 chars]

Remediation
View remediation

Remediation Suggestions

  1. Define an explicit allowlist of Stripe product IDs or price IDs that authorize access to this application. Store this configuration in server-only environment variables or another protected server-side configuration source.

  2. For checkout.session.completed, retrieve and inspect the Checkout Session line items before granting access. Require at least one line item whose product or price ID matches the application’s entitlement allowlist.

  3. Validate the relevant payment state before granting access. The handler should confirm that the event represents the expected successful payment or subscription state for the selected payment method.

  4. Scope scheduled reconciliation to authorized products or prices. Enumerate subscription items and retain only subscriptions containing an allowed price ID rather than importing every subscription in the Stripe account.

  5. Apply the same product and price scoping to cancellation processing so an unrelated subscription event cannot incorrectly alter application membership.

  6. Prefer recording a stable entitlement mapping containing the Stripe customer, subscription, product, and price identifiers. Use that mapping when granting, reconciling, or revoking access instead of relying only on the customer email address.

  7. Add automated security tests covering:

    • An authorized product grants access.
    • An unrelated product does not grant access.
    • A cheaper product in the same Stripe account does not grant access.
    • Reconciliation excludes unrelated active subscriptions.
    • Cancellation of an unrelated subscription does not alter a valid application entitlement.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (20)

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · references/prompts-por-plataforma.md (reported line 114)May include surrounding context.

md
Cómo se llena la lista desde Mercado Pago:
- Uso el link de suscripción de Mercado Pago (no programes un checkout).
- Webhook /api/webhooks/mercadopago para las suscripciones. NUNCA le creas al aviso solo: trae un id; consulta ese id en la API de Mercado Pago con mi Access Token (solo en el servidor) y según el estado agrega el correo (autorizada) o lo quita (cancelada), menos los que agregué a mano.
- Verifica también la firma del aviso con la clave secreta del webhook.
- Busca en la documentación ACTUAL de Mercado Pago cómo se llama el tema de suscripciones y sus estados. No lo digas de memoria.
- Cron diario /api/cron/refresco (protegido con CRON_SECRET) que rehaga la lista con las suscripciones autorizadas y NO aplique si quitaría a más del 30%.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding

La skill describe capacidades operativas sensibles sobre variables de entorno y despliegue, pero no declara un alcance explícito de herramientas o permisos. Eso deja ambigua la autorización del agente y puede permitir acciones con secretos o infraestructura sin una restricción formal verificable.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

La descripción enumera varios triggers específicos, pero también incorpora expresiones genéricas de uso cotidiano en soporte técnico o desarrollo. Sin restricciones adicionales, estas frases pueden activar la skill fuera del contexto previsto de conectar pagos, login o control de acceso.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

La skill autoriza despliegues directos en producción con el VERCEL_TOKEN de la usuaria y además indica que no hace falta pedir confirmación. Esto elimina un control de seguridad importante para una acción de alto impacto sobre infraestructura ajena, y puede causar cambios no deseados, exposición de código o interrupciones del servicio.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

En L016 se indica que 'El MCP de Supabase ... no existen aquí' y que se use la API de Supabase solo si la usuaria conectó un token; sin embargo, en L033 se dice 'Tú haces todo lo que puedas hacer... SQL por MCP'. Estas instrucciones no son meramente incompletas: prescriben mecanismos opuestos para la misma tarea en el mismo entorno OpenClaw.

Content

No source excerpt is available for this finding.

Rp1

Medium
Category
MCP Rug Pull
Confidence
70% confidence
Finding

npx commands without a version suffix (e.g. @1.0.0) create a rug-pull risk if the upstream server is compromised and publishes a malicious update.

Content

No source excerpt is available for this finding.

Rp1

Medium
Category
MCP Rug Pull
Confidence
70% confidence
Finding

npx commands without a version suffix (e.g. @1.0.0) create a rug-pull risk if the upstream server is compromised and publishes a malicious update.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The page formats dates with toLocaleString('es', ...), which hard-codes Spanish locale behavior rather than respecting user preference or offering a choice. This is a natural-language/locale policy issue because the skill presents user-facing content in a fixed language/locale without opt-in or documented regional justification.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

This endpoint invokes reemplazar_miembros, which appears to overwrite the membership list and remove entries based on external billing data. Although comments describe safeguards against suspicious changes, there is no user-facing disclosure, confirmation, or runtime notice that this cron job can automatically modify access data.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

This route deletes records from the 'miembros_activos' table based on webhook input, which is a destructive operation. While comments explain intended use, there is no runtime confirmation, logging/print disclosure, or explicit user-facing warning in the code path before the deletion occurs.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · references/06-publicar.md (reported line 19)May include surrounding context.

md
## Las 6 trampas de publicar (todas silenciosas)

1. **El dominio final.** Si `tuapp.com` redirige a `www.tuapp.com` (o al revés), los webhooks y el cron tienen que apuntar a **la que queda en la barra del navegador**. Si apuntan a la otra, la redirección se come la llamada: Zapier o Stripe ven una respuesta 3xx, tu código nunca corre y no aparece ningún error. Claude lo comprueba con `curl -sI https://tu-dominio/api/webhooks/alta` (tiene que responder 405 o 401, **no** 307/308).
2. **Cron en plan gratis (Hobby): máximo 1 vez al día**, y corre en algún momento dentro de esa hora (±59 min). Si pones algo más frecuente, el despliegue falla.
3. **El cron que "corre" sin hacer nada.** Hay que probarlo de verdad: `vercel crons run` (o llamar la ruta con el `CRON_SECRET`) y leer `vercel logs`. Revisa que la respuesta sea `{"ok":true,...}` y no un HTML de login.
4. **Redirect URLs del login.** Al tener dominio, agrégalo en Supabase → Authentication → URL Configuration (Site URL + `/auth/callback`). Si falta, el login "no hace nada" en producción aunque funcione en localhost.

Intent-Code Divergence

Low
Category
Not specified by scanner
Confidence
88% confidence
Finding

En L015 se afirma que 'la sección de Claude in Chrome / uso de la computadora no aplica' para esta versión OpenClaw, mientras que en L045 se ordena ofrecer precisamente Claude in Chrome para operar paneles. Eso crea una contradicción de intención sobre si esa capacidad forma parte o no del modo de operación de esta skill.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
91% confidence
Finding

La instrucción "Tutea." fuerza una modalidad lingüística concreta sin opt-in del usuario. Esto constituye una restricción de lenguaje/locale en lenguaje natural y puede entrar en conflicto con preferencias de trato o políticas de adaptación al usuario.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
79% confidence
Finding

This code performs database writes using email and optional name received from an external webhook, creating a batch record and upserting an active member record. The file contains setup comments for operators, but no user-facing warning, confirmation, or explicit disclosure that personal data submitted via the webhook will be stored.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
78% confidence
Finding

The handler reads 'process.env.WEBHOOK_SECRET', which is a sensitive credential. This file does not include any explicit warning or user-facing note about reliance on environment-stored secrets or the operational sensitivity of that configuration.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
91% confidence
Finding

This TypeScript file contains user-facing response text and operational instructions entirely in Spanish, such as 'Falta la firma de Stripe' and 'Firma inválida'. The provided policy says to flag language/locale constraints when the skill forces a specific language without user opt-in, and there is no indication here that Spanish is optional or justified by a region-specific requirement.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
87% confidence
Finding

The file's natural-language comments and exported function names are exclusively in Spanish, which can reflect a language-specific skill constraint without any visible user choice or justification. Under the stated policy, forcing a specific language or locale without opt-in is a natural-language policy concern.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
96% confidence
Finding

This file contains natural-language guidance such as execution instructions and operational warnings only in Spanish. Under the policy rule, forcing a specific language without user opt-in or documented justification is a locale/language policy violation.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
72% confidence
Finding

Todo el archivo impone contenido e instrucciones en español, y no ofrece al usuario una elección de idioma ni aclara que el skill esté limitado a un contexto hispanohablante. Según la política dada, forzar un idioma sin opt-in puede ser una violación de política lingüística.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
81% confidence
Finding

La instrucción "Instalación (Claude)" y la referencia posterior a que "Claude busca en la documentación actual" imponen una herramienta/modelo específico en el texto. Esto puede constituir una restricción de herramienta o idioma/locale sin opción explícita para el usuario.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.