Todas las contribuciones
Ingenieríavibe-codingsecuritysemgrep

Vibe coding seguro: pipeline de QA para código generado con Cursor, Aider y Cline

Cómo auditar código IA antes de producción: Semgrep + Bearer + Trivy en pre-commit, SonarQube Community con reglas específicas para IA, DSPy para generar tests automáticos y Promptfoo para validar prompts en CI.

Numoru EngineeringPublicado el 20 de julio de 202614 min de lectura
Compartir
Propuesta de implementacióngithub.com/numoru-ia/secure-ai-codegen-template

TL;DR

"Vibe coding" —describir en lenguaje natural y dejar que Cursor, Aider, Cline o Claude Code escriban el código— es ya la norma en muchos equipos. El problema: el código generado pasa review humano menos cuidadoso, acumula vulnerabilidades (OWASP top ten), dependencias sin vetar y patrones que un humano nunca escribiría por defecto. Este artículo arma un pipeline de QA que corre en cada commit: Semgrep con reglas específicas para código generado por IA, Bearer para detección de PII y secretos, Trivy para CVEs en contenedores, SonarQube Community con rules custom, DSPy para generar tests automáticos a partir del diff, Promptfoo para validar prompts antes de merge y Gitleaks para credenciales. Pre-commit, CI y runtime. El equipo sigue vibeando; el sistema detiene el riesgo.

8-12%
LOC generado por IA con issues
OWASP top-10, secretos, auth faltante
55%
Equipos shippeando código IA sin checks
GitHub dev survey 2024
$6-18k
Ticket de install del pipeline
Por org de ingeniería
~$45
Costo mensual de scan infra
Semgrep + SonarQube + Trivy (OSS)

El problema concreto

En un proyecto típico con vibe coding intensivo observamos 1000 LOC/semana generados. De esos, un 8-12% tienen al menos uno de:

  • SQL inyectable por concatenación de strings.
  • eval()/exec() sobre input no validado.
  • Secretos hardcoded "temporalmente" que pasan a prod.
  • Librerías nuevas sin vetar (supply chain risk).
  • Prompts con inyección inadvertida (instrucciones embebidas que el modelo respeta).
  • Manejo de errores silencioso (except: pass).
  • Endpoints sin autenticación porque "lo arreglaremos después".

El revisor humano los pierde porque el PR tiene 400 líneas y el sello de "hecho con Cursor" genera confianza implícita falsa.

Arquitectura del pipeline

  Developer → Cursor / Aider / Cline / Claude Code
       │
       ▼
  ┌──────────────────────────────────────────────┐
  │ pre-commit                                   │
  │  ├─ gitleaks (secrets)                       │
  │  ├─ semgrep (rules quick)                    │
  │  ├─ bearer (PII/sensitive data)              │
  │  └─ black/ruff/eslint                        │
  └──────────────────────────────────────────────┘
       │
       ▼
  ┌──────────────────────────────────────────────┐
  │ CI (GitHub Actions)                          │
  │  ├─ semgrep full (OWASP + AI-specific)       │
  │  ├─ sonarqube community                      │
  │  ├─ trivy fs/image                           │
  │  ├─ bearer full scan                         │
  │  ├─ dspy: generación + ejecución de tests    │
  │  ├─ promptfoo: validación de prompts         │
  │  └─ license check (supply chain)             │
  └──────────────────────────────────────────────┘
       │
       ▼
  ┌──────────────────────────────────────────────┐
  │ runtime                                      │
  │  ├─ dependency-track (SBOM monitoring)       │
  │  ├─ falco / tetragon (syscall anomalies)     │
  │  └─ traces a Langfuse (si hay LLMs)          │
  └──────────────────────────────────────────────┘

Pre-commit: rápido y local

.pre-commit-config.yaml:

repos:
  - repo: https://github.com/gitleaks/gitleaks
    rev: v8.21.2
    hooks:
      - id: gitleaks

  - repo: https://github.com/returntocorp/semgrep
    rev: v1.95.0
    hooks:
      - id: semgrep
        args:
          - --config=p/owasp-top-ten
          - --config=p/secrets
          - --config=./.semgrep/ai-gen-rules.yml
          - --error
          - --skip-unknown-extensions

  - repo: https://github.com/Bearer/bearer
    rev: v1.49.0
    hooks:
      - id: bearer
        args: ["scan", ".", "--severity=critical,high,medium", "--fail-on-severity=critical,high"]

  - repo: local
    hooks:
      - id: forbidden-patterns
        name: "AI gen anti-patterns"
        entry: scripts/forbidden_patterns.sh
        language: script

scripts/forbidden_patterns.sh:

#!/usr/bin/env bash
set -e
# patrones comunes que Cursor/GPT tienden a generar y no deben llegar a prod
grep -nR --include=*.py --include=*.ts --include=*.go \
  -E '(except:\s*pass|# FIXME|# TODO:|console\.log\("DEBUG|print\("DEBUG)' \
  -- "$@" && {
    echo "❌ anti-patterns encontrados"
    exit 1
  } || true

Semgrep: reglas específicas para IA

Archivo .semgrep/ai-gen-rules.yml:

rules:
  - id: hardcoded-api-key
    pattern-either:
      - pattern: api_key = "sk-..."
      - pattern: $KEY = "pk_live_..."
      - pattern: ANTHROPIC_API_KEY = "sk-ant-..."
    message: "Secret hardcoded. Usa env vars."
    severity: ERROR
    languages: [python, javascript, typescript, go]

  - id: llm-without-timeout
    pattern-either:
      - pattern: openai.ChatCompletion.create(...)
      - pattern: anthropic.messages.create(...)
    pattern-not-inside: |
      $CTX.with_timeout(...)
    message: "Llamada LLM sin timeout; puede colgar el proceso."
    severity: WARNING
    languages: [python]

  - id: prompt-concat-user-input
    patterns:
      - pattern-either:
          - pattern: |
              $PROMPT = f"...{$USER_INPUT}..."
              ...
              $LLM.create(messages=[{"role": "system", "content": $PROMPT}, ...])
      - pattern: |
              $PROMPT = "..." + $USER_INPUT + "..."
              ...
              $LLM.create(...)
    message: "Posible prompt injection: input de usuario concatenado al system prompt."
    severity: ERROR
    languages: [python, typescript]

  - id: sql-string-concat
    pattern-either:
      - pattern: 'cursor.execute(f"SELECT ... {$VAR} ...")'
      - pattern: 'conn.Exec("SELECT ... " + $VAR + " ...")'
    message: "SQL por concatenación. Usa parámetros."
    severity: ERROR
    languages: [python, go]

  - id: agent-tool-no-idempotency
    patterns:
      - pattern: |
          @tool
          def $FN(...):
              ...
      - pattern-not-inside: |
          def $FN(..., operation_id: str, ...):
              ...
    message: "Tool agent-side sin operation_id; falta idempotencia."
    severity: WARNING
    languages: [python]

Estas reglas atrapan lo que SAST clásico no: patrones propios de código IA (prompt injection, tools sin idempotencia, LLM sin timeout).

SonarQube Community Edition

SonarQube genérico se complementa con rules específicas. Archivo .sonar/rules.xml aplicado por sonar-scanner:

  • Complejidad ciclomática >10 por función.
  • Funciones con >60 líneas (tenderán a ser mal comprendidas por humano).
  • Falta de tests para funciones con side effects.
  • Duplicación de código >3%.

Para código generado, baja los umbrales temporalmente (ej. complejidad <8) y sube progresivamente — el modelo se acostumbra a escribir más limpio cuando el feedback es claro.

DSPy: tests generados del diff

La idea: tomar el diff del PR, pasarlo por un modelo de DSPy que genera casos de prueba basados en intenciones evidentes, y ejecutarlos.

import dspy

class GenerateTestFromDiff(dspy.Signature):
    """Genera tests pytest para funciones modificadas. Usa nombres descriptivos, cubre happy path y al menos 2 edge cases."""
    diff: str = dspy.InputField()
    file_path: str = dspy.InputField()
    tests: str = dspy.OutputField()

lm = dspy.LM("anthropic/claude-sonnet-4-6", api_base="https://api.numoru.com/v1", api_key=os.environ["LITELLM_MASTER_KEY"])
dspy.configure(lm=lm)

generator = dspy.ChainOfThought(GenerateTestFromDiff)

def generate_and_run(diff: str, file_path: str):
    out = generator(diff=diff, file_path=file_path)
    tests_file = write_to_tmp(out.tests)
    result = subprocess.run(["pytest", tests_file, "-v"], capture_output=True)
    return result

Si los tests auto-generados fallan, el PR no pasa. Esto detecta regresiones que el humano olvidó cubrir.

Promptfoo: validar prompts antes de merge

Cuando el PR modifica un archivo prompts/*.txt o *.md, Promptfoo corre una suite contra el prompt nuevo y compara contra el anterior. Detalles en evals de agentes en CI/CD.

Trivy: CVEs en contenedores y dependencias

# .github/workflows/security.yml
- name: Trivy fs scan
  uses: aquasecurity/trivy-action@master
  with:
    scan-type: fs
    severity: CRITICAL,HIGH
    ignore-unfixed: true
    exit-code: "1"

- name: Trivy image scan
  if: github.event_name == 'pull_request'
  uses: aquasecurity/trivy-action@master
  with:
    image-ref: ghcr.io/numoru/app:${{ github.sha }}
    severity: CRITICAL,HIGH

Para código generado por IA, la package.json y requirements.txt tienden a crecer con dependencias sugeridas por el modelo — CVEs aparecen rápido.

Supply chain: bloqueo de dependencias nuevas

Problema: Cursor sugiere import superlib sin avisar. La PR agrega un paquete nuevo que nadie vetó.

Solución: pre-commit hook que detecta nuevas líneas en package.json/requirements.txt y exige justificación en el commit message.

#!/usr/bin/env bash
set -e
DIFF=$(git diff --cached --unified=0 -- 'package.json' 'requirements.txt' 'go.mod')
if echo "$DIFF" | grep -q '^+[^+-]'; then
    if ! git log --format=%B -n 1 HEAD | grep -q 'new-dep:'; then
        echo "❌ nuevas dependencias detectadas. Agrega 'new-dep: <motivo>' al commit message"
        exit 1
    fi
fi

GitHub Actions completo

name: secure-ai-gen
on: [pull_request]

jobs:
  semgrep:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: returntocorp/semgrep-action@v1
        with:
          config: >
            p/owasp-top-ten
            p/secrets
            p/ci
            ./.semgrep/ai-gen-rules.yml

  bearer:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: bearer/bearer-action@v2
        with:
          scan-command: "scan . --report=security --severity=critical,high"

  sonarqube:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with: { fetch-depth: 0 }
      - uses: SonarSource/sonarqube-scan-action@v3
        env:
          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
          SONAR_HOST_URL: https://sonar.numoru.com

  trivy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: aquasecurity/trivy-action@master
        with: { scan-type: fs, severity: "CRITICAL,HIGH", exit-code: "1" }

  dspy-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with: { python-version: "3.12" }
      - run: pip install dspy-ai pytest
      - name: Generate + run tests from diff
        env:
          LITELLM_MASTER_KEY: ${{ secrets.LITELLM_MASTER_KEY }}
        run: python scripts/gen_tests_from_diff.py

  promptfoo:
    if: contains(github.event.pull_request.changed_files, 'prompts/')
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm i -g promptfoo
      - run: promptfoo eval -c promptfoo.yml

Reglas adicionales para Aider/Cline/Cursor

Archivo .cursorrules (funciona también para Cline y Windsurf):

# Reglas Numoru — código generado debe cumplir:

1. Nunca hardcodear API keys, usa env vars via pydantic settings / viper / dotenv.
2. Toda llamada a un LLM debe tener timeout (<30s) y retry con backoff.
3. Los prompts construidos con input de usuario deben usar delimitadores claros (<input>...) ).
4. Las funciones con efectos externos deben aceptar operation_id: str para idempotencia.
5. SQL siempre con parámetros; nunca f-string o concat.
6. Los tests deben existir para toda función pública. Cobertura mínima 70%.
7. En Python, nunca except: pass. En Go, nunca ignora err.
8. Los logs no deben incluir PII en claro; hashea email y teléfono.

Esto sesga al modelo durante la generación, no solo después.

Runtime: detección post-deploy

Dos herramientas valen la pena:

  • Falco / Tetragon — eBPF-based runtime security. Alerta si un container hace exec a /bin/sh, abre puertos inesperados o escribe en rutas sensibles.
  • Dependency-Track — ingiere SBOMs periódicamente y avisa cuando una dependencia usada hoy tiene CVE reciente.

Falco policy mínima para aplicaciones IA:

- macro: llm_service
  condition: (proc.name in (uvicorn, gunicorn, node, go-binary))

- rule: LLM service spawning shell
  desc: "Nunca debería spawnear shell"
  condition: spawned_process and proc.pname in (uvicorn, gunicorn) and proc.name in (bash, sh, dash, zsh)
  output: "LLM service spawned shell (command=%proc.cmdline)"
  priority: CRITICAL
  tags: [ai, runtime]

Métricas que trackear

  • Findings críticos por 1000 LOC (objetivo: <0.5 en 90 días).
  • Tiempo medio entre commit y merge (añadir el pipeline añade ~5-8 min; ok).
  • Tasa de rechazo de PR (si sube a >30%, el pipeline está mal calibrado o los devs necesitan formación).
  • CVE-exposure window (días desde que una CVE se publica hasta que nuestro scan la detecta en prod).
Hallazgos críticos por 1,000 LOC — antes vs después del pipeline

Mismo equipo y herramientas IA; la diferencia es si corre el pipeline pre-commit + CI. Observado 90 días en 4 equipos clientes Numoru.

0.02.04.06.08.0Secretos en diffsPatrones SQLinjectionEndpoints sin authRiesgo promptinjectionDeps vulnerables /outdated
  • Antes del pipeline
  • Después (90 días)

Telemetría de engagements Numoru, 2024-2026.

Impacto de negocio y casos

Business & commercial impact

Por qué comprarlo como servicio

Los equipos saben que lo necesitan, pero rara vez lo construyen solos porque cada pieza (reglas Semgrep, tuning SonarQube, test-gen DSPy, reglas Falco en runtime) es un rabbit hole. Numoru lo entrega como install de 2-3 semanas más auditoría de hardening opcional. La venta es esencialmente seguro, cotizado en horas de ingeniería contra el costo asimétrico de una vulnerabilidad shipeada.

Industrias y rangos de ticket

Install + auditoría por comprador (Numoru, USD)

SaaS con Cursor / Copilot intensivo
Guardrails al vibe-coding sin frenar al equipo.
$6,000 – 14,000
One-time + $450 / mes
Fintech / healthtech
Cumplir expectativas regulatorias sobre código asistido por IA.
$18,000 – 40,000
One-time + $900 / mes
Agencias entregando código a clientes
Pipeline seguro contra liability.
$8,000 – 22,000
One-time + $500 / mes
Enterprise DevSecOps
Extender stack de seguridad con reglas IA.
$25,000 – 60,000
One-time + $1,600 / mes
Gobierno / contratistas defensa
Atestación supply-chain + provenance.
$55,000 – 150,000
Por engagement

Benchmarks públicos

Public case studyPlataforma de devs · Global · 2023-2024

GitHub — research de Copilot y productividad IA

Challenge
Cuantificar el uplift de productividad e impacto en calidad de código del pair-programming con IA.
Solution
GitHub publicó estudios sobre adopción de Copilot, incluyendo el blog de Dohmke y colaboraciones académicas sobre time-to-PR y calidad percibida.
Results
Devs usando AI code completion
~92%
De respondedores
Ganancia de productividad percibida
+55%
Self-reportado
Código aceptado al primer intento
~30%
Tasa baseline
Public case studyApp security · Global · 2024

Snyk — State of open source security 2024

Challenge
Benchmark de tasa de vulnerabilidades en código generado por IA vs humano.
Solution
Snyk escaneó miles de repos y publicó el reporte 2024 con foco en código asistido por IA.
Results
Tasa de vulnerabilidades (asistido IA)
+43%
Vs humano puro
Patrones de auth faltante
2.3× más comunes
En endpoints IA-generados
Secretos hardcoded
+28%
YoY

Caso ilustrativo — SaaS de 40 ingenieros adoptando pipeline

Illustrative caseFintech · 40 eng · $16M ARR · México + Chile

Fintech LATAM growth-stage con 40 ingenieros que usan Cursor + Claude Code intensivamente

Baseline
3 near-miss incidentes en 6 meses (.env expuesto en PR, datos cliente en logs, SSRF en admin interno). Review ad-hoc. SonarQube pre-deploy pero sin reglas IA.
Intervention
Install Numoru de 3 semanas: Semgrep + Bearer + Trivy + SonarQube + DSPy + Promptfoo + Gitleaks + Falco runtime. Training mensual al equipo (1 h). Retainer de tuning.
Projected outcome (12 mo)
Hallazgos críticos / 1k LOC
2.4 → 0.3
-88%
Near-miss incidentes
3 / 6 mo → 0
6 mo post-install
Tasa de rechazo de PR
12% → 22%
Sano, mayormente upgrades de deps
Tiempo CI agregado
+6 min
Aceptable
Costo install + retainer
$24,500
One-time + $900 / mes × 12
Costo de breach evitado
$450K+
Mediana fintech (IBM 2024)
Costo de breach anclado a IBM Cost of a Data Breach 2024 ($4.88M promedio global; fintech ~$5.9M). Deltas de incidentes de engagement data Numoru. Caso sintético.

Calculadora ROI — asegurar org de ingeniería IA-heavy

Fintech de 40 ingenieros adoptando pipeline (12 meses)

Payback: 3 months
Assumptions
LOC / semana IA-generados4,500
Tasa de issues pre-pipeline2.4 / 1k LOC
Tasa post-pipeline0.3 / 1k LOC
Prob. incidente en 12 mo40% → 6%
Costo esperado de incidente (fintech)$450,000
Costo de install$18,500
Retainer$900 / mes
Tiempo eng del incremento CI6 min / PR × 120 PRs / mes
Install (one-time)−$18,500
Retainer (12 mo × $900)−$10,800
Infra (Sonar + Semgrep Pro trial)−$720
Tiempo CI adicional (144 h × $92)−$13,248
Incidente ponderado evitado+$153,000
Valor de readiness compliance+$35,000
Contribución neta año 1+$144,732

Tiers de pricing Numoru

Starter
$6,500one-time
2 semanas. Pre-commit + CI básico.
  • Semgrep + Gitleaks + Trivy
  • SonarQube Community básico
  • Pre-commit hook + GitHub Actions
  • Walkthrough al equipo
  • Warranty 30 días
Pipeline completo
$14,500one-time + $650 / mes
3-4 sem. Stack completo + runtime.
  • Todo lo del Starter
  • Test-gen DSPy + Promptfoo
  • Bearer (PII) + supply-chain
  • Reglas Falco runtime
  • Retainer de tuning de reglas
  • Auditoría trimestral
DevSecOps enterprise
$45,000+one-time + $1,600 / mes
6-10 sem. Multi-repo + compliance.
  • Alcance multi-repo / monorepo
  • Alineamiento SOC 2 / ISO 27001
  • Catálogo custom reglas Semgrep
  • Integración Airflow + Notify.io
  • Ingeniero hardening dedicado
  • Engagement red-team anual

Anti-patrones humanos

  1. Desactivar reglas "temporalmente". Se queda. Mejor: PR con fix del false positive en las rules.
  2. Aceptar --skip-unfixed siempre. Hace el scan cosmético.
  3. No entrenar al equipo. Si el dev no entiende por qué SonarQube falla, apaga el check. Formación mensual corta.
  4. Sólo ejecutar en CI. Pre-commit ahorra ciclos CI y da feedback inmediato.
  5. Confiar en que "el modelo aprenderá". No aprende entre PRs; las reglas deben ser explícitas.

FAQ

¿Se vuelve insoportablemente lento?Pre-commit queda bajo 20s si las reglas están bien filtradas. CI completo 5-12 min. Mejor que un incidente en prod.

¿Funciona para equipos pequeños?Sí. Lo mínimo: gitleaks + semgrep + trivy + pre-commit. Cuesta ~30 min de setup.

¿Detecta prompt injection de runtime?Parcialmente. SAST atrapa concat peligroso; guardrails en runtime (NeMo, Rebuff) atrapan lo que pase. Son complementarios.

¿Qué hago cuando el PR tiene 40 findings de Semgrep?Priorizar por severidad: ERROR bloquea, WARNING se queda como tech debt con issue tagueado. En la primera semana se genera ruido; se calibra a 2-5 findings por PR.

¿Sonar Community es suficiente o necesito Enterprise? Community alcanza para equipos <15 personas. Enterprise aporta branch analysis decente y cross-repo, útil en monorepo.

Próximos pasos

Pipeline completo publicado en github.com/numoru-ia/secure-ai-codegen-template. Incluye pre-commit, GitHub Actions, .cursorrules, Semgrep rules custom y políticas Falco. Forkear + correr make bootstrap. Siguiente pieza de la serie: cómo enlazar estas métricas al dashboard de Langfuse + Grafana para visibilidad completa code-to-runtime.

La contraparte para agentes en vez de código está en los evals en CI/CD que frenan una regresión antes de producción. Y como ejemplo de código que sí conviene escribir a mano, un servidor MCP desde cero en Go.

¿Quieres resultados así para tu empresa?

Iniciar conversación
Compartir