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: highBase de puntuación por herramienta
Qué devuelve cada endpoint y sobre qué establecer el umbral.
| Herramienta | Salida | Base | Gate integrado |
|---|---|---|---|
| Auditoría del sitio web | 4 puntuaciones 0-100: performance, seo, accessibility, bestPractices | Puntuaciones 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 seguridad | riskScore 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 navegador | Arrays: 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 repositorios | score 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 SEO | score 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; }
trueDó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 necesitacurl,jqybcinstalados; los runnersubuntu-latestalojados en GitHub y Azure ya los incluyen, la imagenalpinede GitLab no (de ahí el pasoapk 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.
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 }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).