Docs/API y webhooks/Integración con SARIF

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

1

Tu herramienta SAST produce un fichero SARIF 2.1.0 como parte de un job de CI.

2

Un paso de CI hace POST del fichero a /api/public/v1/sarif con metadatos del commit en los headers de la solicitud.

3

SecureCodingHub parsea cada result, extrae sus etiquetas de CWE y busca cada una en nuestro mapa interno de CWE a tema.

4

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.

5

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

HeaderRequeridoSignificado
X-SourceIdentificador libre de la herramienta, por ejemplo github-codeql, snyk o semgrep-ci. Se registra para auditoría.
X-RepoSlug del repositorio (owner/name). Se registra para auditoría.
X-BranchNombre de rama. Se registra para auditoría.
X-Commit-SharecomendadoHash del commit. Usado junto con X-Repo para idempotencia de 24 horas.
X-Commit-Author-EmailrecomendadoEmail 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 entradas result parseadas.
  • 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_BRANCH

El 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/sarif

Cobertura 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-89
  • CWE-89
  • cwe: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íntomaCausa probable
200 con assignmentsCreated: 0El 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íoLos hallazgos hacen referencia a CWEs que aún no tienen tema de formación. Reenvía la lista a soporte.
400 empty_bodyA curl le faltaba --data-binary @file.sarif.
413 body_too_largeEl 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.