Integración de SARIF
Envía a SecureCodingHub la salida SARIF de cualquier herramienta de escaneo de código (CodeQL, Semgrep, Snyk, Checkmarx, Trivy, Bandit, GitLab SAST, OWASP ZAP). Cada CWE único del run se convierte en una asignación en la cuenta del autor del commit, normalmente en segundos tras el merge.
Por qué CI es el disparador correcto
Los despliegues genéricos de "todos los desarrolladores hacen el mismo curso de OWASP Top 10" tienen baja finalización y aún menos retención. La integración con CI invierte el modelo: la formación se asigna específicamente al desarrollador que introdujo el problema, específicamente sobre la clase de vulnerabilidad que necesita, con un plazo de 14 días atado al merge que la expuso. Eso es lo que los auditores llaman formación en código seguro just-in-time.
Cómo funciona de extremo a extremo
Tu herramienta SAST produce un fichero SARIF 2.1.0 como parte de un job de CI.
Un paso de CI hace POST del fichero a /api/public/v1/sarif con metadatos del commit en los headers de la solicitud.
SecureCodingHub parsea cada result, extrae sus etiquetas de CWE y busca cada una en nuestro mapa interno de CWE a tema.
Por cada tema mapeado único, se crea una asignación sobre el usuario cuyo email coincide con X-Commit-Author-Email, con un plazo de 14 días.
Se dispara un webhook sarif.ingested con el mismo payload que devuelve la respuesta síncrona.
El endpoint
POST /api/public/v1/sarif
Authorization: Bearer scs_live_…
Content-Type: application/json
X-Source: github-codeql
X-Repo: acme/payments-api
X-Branch: main
X-Commit-Sha: 9c2f4b8e0c1d…
X-Commit-Author-Email: jane.smith@acme.com
<SARIF 2.1.0 JSON body, up to 10 MB>Scope requerido: sarif:ingest.
Headers de la solicitud
| Header | Requerido | Significado |
|---|---|---|
X-Source | — | Identificador libre de la herramienta, por ejemplo github-codeql, snyk o semgrep-ci. Se registra para auditoría. |
X-Repo | — | Slug del repositorio (owner/name). Se registra para auditoría. |
X-Branch | — | Nombre de rama. Se registra para auditoría. |
X-Commit-Sha | recomendado | Hash del commit. Usado junto con X-Repo para idempotencia de 24 horas. |
X-Commit-Author-Email | recomendado | Email del autor del commit. Se resuelve a un usuario de SecureCodingHub; las asignaciones se asocian a él. Si el email no coincide con ningún usuario, el run igualmente se ingiere, pero no se crean asignaciones. |
Respuesta
{
"ingestionId": "7e4c9d09-86ca-4125-bbd8-7fae54c8f654",
"findingsCount": 2,
"uniqueCwes": ["CWE-79", "CWE-89"],
"unmappedCwes": [],
"assignmentsCreated": 12,
"notes": []
}findingsCount: número total de entradasresultparseadas.uniqueCwes: conjunto de CWEs mencionados en todos los hallazgos.unmappedCwes: CWEs que no pudimos mapear a un tema. Envíalos a soporte si quieres que añadamos cobertura.assignmentsCreated: número de nuevos registros de asignación (los duplicados existentes se omiten).notes: avisos no fatales (por ejemplo, formato inválido del email del autor del commit).
Idempotencia
Reingerir el mismo fichero SARIF es seguro. La tupla (organization, repo, commit_sha) es la clave de idempotencia: un duplicado dentro de una ventana de 24 horas devuelve 200 con el ingestionId original y assignmentsCreated: 0. Esto significa que los flujos de CI que se reejecutan sobre el mismo commit (rebase, reejecución manual, push a un PR) no acumulan asignaciones duplicadas.
Ejemplo de GitHub Actions
Añade este paso a un workflow después de que tu job de CodeQL o Semgrep haya producido results.sarif:
- name: Send SARIF to SecureCodingHub
if: always()
run: |
curl -sS -f -X POST \
-H "Authorization: Bearer $SCH_API_KEY" \
-H "Content-Type: application/json" \
-H "X-Source: github-codeql" \
-H "X-Repo: ${{ github.repository }}" \
-H "X-Branch: ${{ github.ref_name }}" \
-H "X-Commit-Sha: ${{ github.sha }}" \
-H "X-Commit-Author-Email: ${{ github.event.head_commit.author.email }}" \
--data-binary @results.sarif \
https://api.limeplate.com/api/public/v1/sarif
env:
SCH_API_KEY: ${{ secrets.SCH_API_KEY }}Guarda la clave en secrets del repositorio o de la organización. La guarda if: always() asegura que los hallazgos se envíen aunque el job de seguridad haya marcado la build como fallida, que es justo cuando más interesa que se disparen las asignaciones de formación.
Ejemplo de GitLab CI
send_sarif_to_sch:
stage: post-security
image: curlimages/curl:latest
needs: ["sast"]
script:
- >
curl -sS -f -X POST
-H "Authorization: Bearer $SCH_API_KEY"
-H "Content-Type: application/json"
-H "X-Source: gitlab-sast"
-H "X-Repo: $CI_PROJECT_PATH"
-H "X-Branch: $CI_COMMIT_BRANCH"
-H "X-Commit-Sha: $CI_COMMIT_SHA"
-H "X-Commit-Author-Email: $GITLAB_USER_EMAIL"
--data-binary @gl-sast-report.json
https://api.limeplate.com/api/public/v1/sarif
rules:
- if: $CI_COMMIT_BRANCHEl informe SAST de GitLab es compatible con SARIF: no hace falta conversión.
Ejemplo de Semgrep
semgrep ci --sarif --output results.sarif
curl -sS -f -X POST \
-H "Authorization: Bearer $SCH_API_KEY" \
-H "X-Source: semgrep" \
-H "X-Repo: $REPO" \
-H "X-Commit-Sha: $COMMIT" \
-H "X-Commit-Author-Email: $AUTHOR_EMAIL" \
--data-binary @results.sarif \
https://api.limeplate.com/api/public/v1/sarifCobertura de CWE
SecureCodingHub mapea actualmente el OWASP Top 10, el OWASP API Security Top 10, el OWASP Mobile Top 10 y el CWE Top 25 a temas de formación. Los hallazgos etiquetados con IDs CWE fuera de ese conjunto aparecen en la lista unmappedCwes de la respuesta. Formatos habituales que aceptamos en el lado de etiquetas SARIF:
external/cwe/cwe-89(estándar de CodeQL)cwe-89CWE-89cwe:89
Las etiquetas se leen tanto desde result.properties.tags como desde el tool.driver.rules[].properties.tags correspondiente.
Tope de tamaño del cuerpo
El endpoint SARIF acepta payloads de hasta 10 MB sin comprimir. La mayoría de informes SAST reales para un único repositorio quedan muy por debajo. Si tienes un informe de monorepo a escala empresarial que supera el tope, divídelo por driver de herramienta (un run SARIF por driver) y haz POST de cada parte: serán idempotentes bajo el mismo SHA de commit.
Modos de fallo
| Síntoma | Causa probable |
|---|---|
200 con assignmentsCreated: 0 | El email del autor del commit no es un usuario de tu organización, o todos los CWEs ya estaban asignados. |
200 con unmappedCwes no vacío | Los hallazgos hacen referencia a CWEs que aún no tienen tema de formación. Reenvía la lista a soporte. |
400 empty_body | A curl le faltaba --data-binary @file.sarif. |
413 body_too_large | El SARIF supera los 10 MB. Divídelo por driver. |
200 con una entrada en notes que empieza por idempotency: | La misma (repo, commit) ya se había ingerido en las últimas 24 h. La respuesta es por lo demás idéntica a una ingesta nueva con assignmentsCreated: 0: puede ignorarse. |