Skip to content
SEO Optimizer is live, Audit on page SEO and AI-search readiness. Try it free
Iniciar sesión
CI/CD Integration

Guías

Integración CI/CD

Todos los endpoints de análisis aceptan una clave de API, por lo que cualquiera de ellos puede ejecutarse como un paso del pipeline. Esta página explica qué significa la salida de cada herramienta, cómo convertirla en un gate de aprobado/rechazado, y ofrece ejemplos funcionales para cuatro plataformas de CI.

Análisis de código con la GitHub Action

La forma más rápida de analizar código en CI. Ejecuta @scanverra/cli en el runner (tu código nunca sale de él), hace fallar el job a partir de la gravedad que elijas y sube SARIF a GitHub code scanning.

on: [push, pull_request]
jobs:
  scanverra:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      security-events: write
    steps:
      - uses: actions/checkout@v4
      - uses: scanverra/scan-action@v1
        with:
          api-key: ${{ secrets.SCANVERRA_API_KEY }}
          fail-on: high
Añade tu clave de API como el secreto de repositorio SCANVERRA_API_KEY. El plan Free incluye una clave; cada análisis subido cuenta para tus análisis mensuales de Repo Scanner. Las comprobaciones por URL de abajo usan la API REST.

Base de puntuación por herramienta

Qué devuelve cada endpoint y sobre qué establecer el umbral.

HerramientaSalidaBaseGate integrado
Auditoría del sitio web4 puntuaciones 0-100: performance, seo, accessibility, bestPracticesPuntuaciones por categoría de 0 a 100. Convención: ≥90 bueno, 50-89 mejorable, <50 deficiente.No integrado — elige tu propio umbral por puntuación.
Análisis de seguridadriskScore 0-100 (más alto = más seguro)100 menos descuentos ponderados por gravedad, con tope por categoría. Bandas de calificación: A+ ≥90 … F <30.No integrado — elige tu propio umbral.
Auditoría de navegadorArrays: jsErrors, brokenLinks, formIssues, imgIssues (cada uno con severity: "error" | "warning")Sin puntuación agregada.Condiciona según el número de problemas, por ejemplo cualquier severity === "error".
Escáner de repositoriosscore 0-100 + qualityGate: "passed" | "failed"100 menos descuentos ponderados por gravedad, con tope por categoría.Integrado — falla automáticamente ante cualquier hallazgo crítico o score < 60.
Optimizador SEOscore 0-100 (SEO en la página + señales GEO/E-E-A-T)100 menos descuentos ponderados por gravedad, con tope por categoría, sobre hallazgos en la página, GEO y E-E-A-T.No integrado — define tu propio umbral.

Detener el pipeline

La mecánica de CI es la misma independientemente de la plataforma: un paso que termina con un código distinto de cero hace fallar el job. Para bloquear de verdad que un PR o merge request se fusione, añade ese job como comprobación de estado obligatoria en la protección de ramas o en la configuración de aprobación de merge requests de tu proveedor de repositorios — un paso fallido por sí solo solo se muestra en rojo, no impide el merge por sí mismo.

Pipelines de ejemplo

Las mismas cinco comprobaciones — Audit, Security Scan, Browser Audit, Repo Scan, Optimizador SEO — escritas para cuatro plataformas. Elige tu sistema de CI abajo.

name: Scanverra checks
on: [pull_request]

jobs:
  scanverra:
    runs-on: ubuntu-latest
    env:
      API_KEY: ${{ secrets.SCANVERRA_API_KEY }}
      TARGET_URL: https://staging.example.com   # your deployed preview URL
    steps:
      - name: Website Audit (perf/SEO/a11y)
        run: |
          RES=$(curl -sf -X POST https://www.scanverra.com/api/audit \
            -H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
            -d "{\"url\":\"$TARGET_URL\"}")
          echo "$RES" | jq .
          PERF=$(echo "$RES" | jq '.performanceScore')
          A11Y=$(echo "$RES" | jq '.accessibilityScore')
          if (( $(echo "$PERF < 50" | bc -l) )) || (( $(echo "$A11Y < 90" | bc -l) )); then
            echo "::error::Audit failed thresholds (perf=$PERF, a11y=$A11Y)"
            exit 1
          fi

      - name: Security Scan
        run: |
          SCAN_ID=$(curl -sf -X POST https://www.scanverra.com/api/security-scan \
            -H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
            -d "{\"url\":\"$TARGET_URL\"}" | jq -r .scanId)

          for i in $(seq 1 30); do
            RESULT=$(curl -sf "https://www.scanverra.com/api/security-scan?id=$SCAN_ID" -H "X-API-Key: $API_KEY")
            STATUS=$(echo "$RESULT" | jq -r .status)
            [ "$STATUS" != "running" ] && break
            sleep 10
          done

          RISK=$(echo "$RESULT" | jq '.riskScore')
          echo "riskScore=$RISK"
          [ "$STATUS" = "failed" ] && { echo "::error::Security scan failed to complete"; exit 1; }
          [ "$RISK" -lt 70 ] && { echo "::error::Security risk score too low ($RISK)"; exit 1; }
          true

      - name: Browser Audit (JS errors, broken links)
        run: |
          TEST_ID=$(curl -sf -X POST https://www.scanverra.com/api/browser-test \
            -H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
            -d "{\"url\":\"$TARGET_URL\"}" | jq -r .testId)

          for i in $(seq 1 30); do
            RESULT=$(curl -sf "https://www.scanverra.com/api/browser-test?id=$TEST_ID" -H "X-API-Key: $API_KEY")
            STATUS=$(echo "$RESULT" | jq -r .status)
            [ "$STATUS" != "running" ] && break
            sleep 10
          done

          ERRORS=$(echo "$RESULT" | jq '[.jsErrors[]? | select(.severity=="error")] | length')
          [ "$STATUS" = "failed" ] && { echo "::error::Browser audit failed to complete"; exit 1; }
          [ "$ERRORS" -gt 0 ] && { echo "::error::$ERRORS JS errors found"; exit 1; }
          true

      - name: Repo Scan (SAST, secrets, deps, quality gate)
        run: |
          SCAN_ID=$(curl -sf -X POST https://www.scanverra.com/api/repo/scan \
            -H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
            -d '{"owner":"your-org","repo":"your-repo","branch":"${{ github.head_ref }}"}' | jq -r .scanId)

          for i in $(seq 1 30); do
            RESULT=$(curl -sf "https://www.scanverra.com/api/repo/result?id=$SCAN_ID" -H "X-API-Key: $API_KEY")
            STATUS=$(echo "$RESULT" | jq -r .status)
            [ "$STATUS" != "running" ] && break
            sleep 10
          done

          echo "$RESULT" | jq '{score, qualityGate}'
          GATE=$(echo "$RESULT" | jq -r .qualityGate)
          [ "$GATE" = "failed" ] && { echo "::error::Repo quality gate failed"; exit 1; }
          true

      - name: SEO Optimizer (on-page, GEO, E-E-A-T)
        run: |
          SCAN_ID=$(curl -sf -X POST https://www.scanverra.com/api/seo-optimizer \
            -H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
            -d "{\"url\":\"$TARGET_URL\"}" | jq -r .scanId)

          for i in $(seq 1 30); do
            RESULT=$(curl -sf "https://www.scanverra.com/api/seo-optimizer?id=$SCAN_ID" -H "X-API-Key: $API_KEY")
            STATUS=$(echo "$RESULT" | jq -r .status)
            [ "$STATUS" != "running" ] && break
            sleep 10
          done

          SCORE=$(echo "$RESULT" | jq '.score')
          echo "seoScore=$SCORE"
          [ "$STATUS" = "failed" ] && { echo "::error::SEO scan failed to complete"; exit 1; }
          [ "$SCORE" -lt 70 ] && { echo "::error::SEO score too low ($SCORE)"; exit 1; }
          true

Dónde guardar la clave de API como secreto, según la plataforma:

  • GitHub Actions: secreto del repositorio — Settings → Secrets and variables → Actions.
  • GitLab CI: variable de CI/CD enmascarada — Settings → CI/CD → Variables.
  • Azure Pipelines: variable de pipeline secreta (icono de candado) o un Variable Group en Pipelines → Library.
  • Bitbucket Pipelines: Configuración del repositorio > Pipelines > Variables del repositorio, marcadas como protegidas.
  • Jenkins: credencial de texto secreto — Manage Jenkins → Credentials — referenciada mediante credentials(). La imagen del agente también necesita curl, jq y bc instalados; los runners ubuntu-latest alojados en GitHub y Azure ya los incluyen, la imagen alpine de GitLab no (de ahí el paso apk add).

El paso del escáner de repositorios necesita tu cuenta de GitHub/Bitbucket/Azure conectada previamente en la configuración de Scanverra — el análisis se ejecuta con el token guardado ahí, no con uno enviado por solicitud.

¿Prefieres no conectar una cuenta? @scanverra/cli analiza el código descargado directamente en el runner y solo necesita una clave de API, sin conexión a GitHub, Bitbucket o Azure.

Salida SARIF para el escaneo de código

SARIF (Static Analysis Results Interchange Format) es el formato JSON estándar con el que las herramientas de escaneo de código intercambian hallazgos. La CLI de Scanverra genera SARIF 2.1.0 con --sarif, así que los resultados aparecen donde se lea SARIF.

  • GitHub code scanning: activa sarif: true en scanverra/scan-action y los hallazgos aparecen en la pestaña Security y como anotaciones en los pull requests.
  • SonarQube y otras plataformas que importan SARIF pueden leer el mismo archivo.
  • Bitbucket y GitLab: guarda el archivo SARIF como artefacto del pipeline o usa --json para tu propio paso de informes.
- uses: actions/checkout@v4
- uses: scanverra/scan-action@v1
  with:
    api-key: ${{ secrets.SCANVERRA_API_KEY }}
    sarif: true          # uploads the report to GitHub code scanning
# The job needs: permissions: { security-events: write }
Nota sobre la cuota: una clave de API del plan Free tiene la misma limitación de cuota que una cuenta web gratuita — las ejecuciones de CI siguen contando para tu límite mensual de análisis y pueden agotarlo rápido si analizas en cada PR. Las claves de API de los planes Pro, Team y Enterprise no tienen medición. Consulta Límites y comportamiento.

Solo el escáner de repositorios devuelve un campo qualityGate listo para usar. Para las otras cuatro herramientas, decide tus propios umbrales de antemano y mantenlos consistentes entre entornos (por ejemplo, no compares la puntuación de seguridad de una URL de staging con un umbral ajustado para producción).