Safe Creative Platform

11. Recetas de integración

Configuraciones recomendadas por caso de uso, con formato de eventos de ejemplo y anclaje normativo orientativo 🔒 (no es asesoramiento jurídico). Cada receta se publica como página propia en la documentación web con ejemplos completos; aquí va el resumen de todas y, desarrollada, la primera que tiene fecha en el calendario.

  • Prueba de diligencia en gestión de vulnerabilidades e incidentes (Reglamento de Ciberresiliencia, art. 14 — notificación en plazo desde el 11-09-2026 —; NIS2, arts. 21 y 23; DORA, arts. 17-19): modalidad solo-huella, un canal por sistema protegido, un evento por resultado de la herramienta de seguridad. Desarrollada en §11.1.
  • Registros de agentes y sistemas de IA (Reglamento europeo de IA, sistemas de alto riesgo; obligación aplazada por el Digital Omnibus al 02-12-2027 y al 02-08-2028 según el anexo): modalidad contenido, un token por acción del agente, con el formato alineado con el borrador del IETF sobre registros de auditoría de agentes (§11.2).
  • Canales de denuncia (plazos probatorios con confidencialidad): modalidad solo-huella, un evento por hito del expediente.
  • Mantenimientos y controles periódicos: modalidad contenido, un evento por parte cerrado — lo que se demuestra es la diligencia sostenida.
  • Cambios en sistemas críticos (DORA y afines): modalidad solo-huella o contenido, un evento por despliegue o cambio de configuración relevante.

11.1 Receta: prueba de diligencia en gestión de vulnerabilidades e incidentes

Para quién. Fabricantes de software sujetos al Reglamento de Ciberresiliencia, entidades bajo NIS2 o DORA, e integradores o proveedores de servicios de seguridad gestionados que operan para ellos herramientas de seguridad con IA: agentes que analizan y corrigen código (Codex Security, Claude Security, GitHub Copilot Autofix, Semgrep, Snyk, CodeQL), pruebas de intrusión autónomas, centros de operaciones de seguridad automatizados. Todas producen hechos a escala y los atestan ellas mismas; el canal les añade lo que no pueden darse: fecha e integridad de un tercero que ni el fabricante, ni su proveedor de seguridad, ni el proveedor del modelo controlan.

Qué queda probado. No que el parche sea correcto ni que el análisis fuera completo — eso lo afirmas tú y tu herramienta —, sino que cada hecho se declaró en una fecha con presunción legal y que la secuencia no se ha recompuesto después: que el análisis existía antes del incidente, que la vulnerabilidad se conoció el día X, que la corrección se verificó el día Y, que la notificación a la autoridad salió dentro del plazo. El plazo legal cuenta desde que «se tiene conocimiento»: probar ese instante es probar el cumplimiento.

Configuración del canal recomendada.

  • Un canal por sistema o producto protegido.
  • Modalidad solo-huella: el detalle de una vulnerabilidad no debe salir de tu perímetro antes de su divulgación coordinada. Custodia tú los ficheros (SARIF, informes, acuses) y declara al canal su huella; si un día hay que exhibirlos, la huella sellada los ancla a su fecha.
  • Declarante: el fabricante o la entidad obligada. Si operas como integrador, designa a tu cliente.
  • Información complementaria: qué sistema, qué herramientas y qué fases se declaran, y la convención de identificadores que uses (queda sellada en la configuración, antes de los hechos).

Eventos tipo. En solo-huella el evento lleva client_ref, hash y hash_alg (§4.1); el tipo de hecho y su identificador van en el client_ref, con una convención que declaras en la información complementaria del canal.

Fase del ciclo client_ref recomendado Huella de
Descubrir scan.completed/<repositorio>/<revisión> Manifiesto del análisis o fichero SARIF
Descubrir finding.opened/<repositorio>/<id> Registro del hallazgo (identificador, gravedad, componente)
Validar finding.validated/… · finding.dismissed/… Informe de validación
Asignar finding.assigned/… Incidencia creada
Corregir fix.deployed/<repositorio>/<id>/<cambio> Confirmación del despliegue e identificador del cambio
Verificar fix.verified/<repositorio>/<id>/<revisión> Resultado de la nueva ejecución del análisis
Notificar notice.early/… · notice.full/… · report.final/… Acuse de la plataforma de notificación (ENISA, CSIRT)

Cómo conectar: las tres vías habituales. Ninguna herramienta tiene que cooperar; todas exportan en formatos estándar, disparan avisos automáticos o corren en tu integración continua.

Vía 1 — un paso de integración continua que declara la huella del análisis. Vale para cualquier herramienta que produzca SARIF. Conserva results.sarif junto a la revisión: es el original que la huella ancla.

# .github/workflows/security.yml — después del paso que genera results.sarif
- name: Declarar el análisis en Safe Creative Platform
  env:
    SC_KEY: ${{ secrets.SC_EVENTS_KEY }}        # clave del canal (live o test)
  run: |
    HASH=$(sha256sum results.sarif | cut -d' ' -f1)
    curl -sS --retry 5 --retry-all-errors -X POST https://platform.safecreative.org/v1/events \
      -H "Authorization: Bearer $SC_KEY" -H "Content-Type: application/json" \
      -d "{\"client_ref\":\"scan.completed/$GITHUB_REPOSITORY/$GITHUB_SHA\",
           \"hash\":\"$HASH\",\"hash_alg\":\"sha-256\",
           \"occurred_at\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"}"

Vía 2 — un receptor de avisos automáticos de GitHub. GitHub emite un aviso (code_scanning_alert) por cada cambio de estado de una alerta: creada, corregida, cerrada, reabierta. Es el gancho más barato de toda la familia (sirve también para las alertas que suben Semgrep, Snyk o CodeQL). Node 20, sin dependencias:

import { createServer } from 'node:http';
import { createHash, createHmac, timingSafeEqual } from 'node:crypto';

const SC_KEY = process.env.SC_EVENTS_KEY, GH_SECRET = process.env.GH_WEBHOOK_SECRET;

createServer(async (req, res) => {
  let body = ''; for await (const chunk of req) body += chunk;
  // 1) verifica la firma de GitHub antes de nada
  const firma = 'sha256=' + createHmac('sha256', GH_SECRET).update(body).digest('hex');
  const recibida = String(req.headers['x-hub-signature-256'] || '');
  if (firma.length !== recibida.length || !timingSafeEqual(Buffer.from(firma), Buffer.from(recibida))) {
    res.writeHead(401); res.end(); return;
  }
  const aviso = JSON.parse(body);            // action: created | fixed | closed_by_user | reopened …
  archivar(body, req.headers['x-github-delivery']);   // conserva el JSON: es el original que la huella ancla
  // 2) declara la huella al canal (idempotente por client_ref: el id de entrega evita duplicados)
  const r = await fetch('https://platform.safecreative.org/v1/events', {
    method: 'POST',
    headers: { 'Authorization': 'Bearer ' + SC_KEY, 'Content-Type': 'application/json' },
    body: JSON.stringify({
      client_ref: 'finding.' + aviso.action + '/' + aviso.repository.full_name + '/' +
                  aviso.alert.number + '/' + req.headers['x-github-delivery'],
      hash: createHash('sha256').update(body).digest('hex'),
      hash_alg: 'sha-256',
      occurred_at: aviso.alert.updated_at || aviso.alert.created_at
    })
  });
  res.writeHead(r.ok ? 204 : 500); res.end();  // si falla, GitHub reintenta la entrega
}).listen(8080);

Vía 3 — un receptor genérico de avisos automáticos (Claude Security, plataformas de SOC, pruebas de intrusión). Python 3.11, sin dependencias. Ajusta los nombres de campo al esquema de tu herramienta y verifica su firma si la ofrece:

import hashlib, json, os, urllib.request
from http.server import BaseHTTPRequestHandler, HTTPServer

SC_KEY = os.environ["SC_EVENTS_KEY"]

class Receptor(BaseHTTPRequestHandler):
    def do_POST(self):
        cuerpo = self.rfile.read(int(self.headers["Content-Length"]))
        aviso = json.loads(cuerpo)
        archivar(cuerpo)                                   # conserva el original que la huella ancla
        tipo = aviso.get("event", "finding.updated")       # p. ej. finding.validated, fix.verified
        ref = f'{tipo}/{aviso.get("project", "-")}/{aviso.get("id", hashlib.sha256(cuerpo).hexdigest()[:16])}'
        evento = {"client_ref": ref, "hash": hashlib.sha256(cuerpo).hexdigest(), "hash_alg": "sha-256"}
        if "timestamp" in aviso:
            evento["occurred_at"] = aviso["timestamp"]
        peticion = urllib.request.Request(
            "https://platform.safecreative.org/v1/events",
            data=json.dumps(evento).encode(), method="POST",
            headers={"Authorization": f"Bearer {SC_KEY}", "Content-Type": "application/json"})
        urllib.request.urlopen(peticion, timeout=10)       # ante error, deja que la herramienta reintente
        self.send_response(204); self.end_headers()

HTTPServer(("", 8080), Receptor).serve_forever()

En las tres vías rige la regla de §4.2: mismo client_ref en los reintentos, retroceso exponencial, y si el proceso no puede permitirse perder declaraciones, una cola local de salida. Pide la certificación cuando haga falta — ante una inspección, un siniestro o un litigio — y el informe atestará la integridad de la secuencia del periodo; el archivo de evidencias permite cotejar cada huella con el original que conservas.

Anclaje normativo orientativo 🔒. Reglamento de Ciberresiliencia, art. 14 (aviso temprano en 24 horas, notificación en 72 horas, informe final a los 14 días o al mes; desde el 11-09-2026) · NIS2, arts. 21 y 23 · DORA, arts. 17-19 y 24 y siguientes. Ninguna de estas normas exige un tercero: exigen diligencia demostrable, y el instante en que «se tuvo conocimiento» es lo que el canal deja probado.

11.2 Nota sobre los registros de agentes de IA

Si tu caso es el registro de acciones de agentes, alinea el token con el borrador del IETF draft-sharif-agent-audit-trail: record_id, session_id, agent_id, action_type, outcome, record_phase y, si mantienes tu propia cadena, prev_hash. Usa record_id como client_ref. Ese modelo exige, a partir de cierto nivel de confianza, que el registro lo haga un componente independiente del agente con sello de tiempo externo: ese componente es el canal, y la fecha de recepción de cada evento (received_at) queda cubierta por el sello de tiempo cualificado de su tramo. No hace falta que el agente firme nada para que la secuencia tenga fecha de tercero.

{
  "client_ref": "rec_01J9ZM3T7Q8KX2",
  "payload": {
    "record_id": "rec_01J9ZM3T7Q8KX2", "session_id": "ses_44a1", "agent_id": "tramitador-v3",
    "action_type": "case.update", "outcome": "success", "record_phase": "post",
    "prev_hash": "sha256:6b1f0c6e…94a7d", "target": "EXP-4411"
  },
  "occurred_at": "2026-09-04T09:41:07Z"
}