Safe Creative Platform

11. Integration recipes

Recommended configurations per use case, with sample event formats and an indicative regulatory anchor šŸ”’ (not legal advice). Each recipe ships as its own page on the documentation site with complete examples; below, a summary of all of them and, in full, the first one with a date on the calendar.

  • Proof of diligence in vulnerability and incident management (Cyber Resilience Act, art. 14 — reporting deadlines from 2026-09-11 —; NIS2, arts. 21 and 23; DORA, arts. 17-19): hash-only modality, one channel per protected system, one event per security-tool result. Developed in §11.1.
  • AI agent and system logs (EU AI Act, high-risk systems; obligation postponed by the Digital Omnibus to 2027-12-02 and 2028-08-02 depending on the annex): content modality, one token per agent action, formatted in line with the IETF draft on agent audit trails (§11.2).
  • Whistleblowing channels (probative deadlines with confidentiality): hash-only modality, one event per case milestone.
  • Maintenance and periodic inspections: content modality, one event per closed work report — what you prove is sustained diligence.
  • Changes in critical systems (DORA and similar): hash-only or content modality, one event per deployment or relevant configuration change.

11.1 Recipe: proof of diligence in vulnerability and incident management

Who it is for. Software manufacturers subject to the Cyber Resilience Act, entities under NIS2 or DORA, and the integrators or managed security providers who run AI security tooling for them: agents that scan and fix code (Codex Security, Claude Security, GitHub Copilot Autofix, Semgrep, Snyk, CodeQL), autonomous penetration testing, automated security operations centres. All of them produce facts at scale and attest those facts themselves; the channel adds what they cannot give themselves: a date and integrity from a third party that neither the manufacturer, nor its security vendor, nor the model provider controls.

What is proved. Not that the patch is correct or the scan was complete — you and your tool assert that — but that each fact was declared on a date with legal presumption and that the sequence has not been recomposed afterwards: that the scan existed before the incident, that the vulnerability was known on day X, that the fix was verified on day Y, that the notification to the authority went out within the deadline. Legal deadlines run from the moment you "become aware": proving that instant is proving compliance.

Recommended channel configuration.

  • One channel per protected system or product.
  • Hash-only modality: vulnerability details must not leave your perimeter before coordinated disclosure. You keep the files (SARIF, reports, acknowledgements) and declare their digest; if you ever need to produce them, the sealed digest anchors them to their date.
  • Declarant: the manufacturer or the obligated entity. If you operate as an integrator, designate your customer.
  • Supplementary information: which system, which tools and which phases are declared, and the identifier convention you use (it is sealed in the configuration, before the facts).

Typical events. In hash-only mode an event carries client_ref, hash and hash_alg (§4.1); the type of fact and its identifier go in the client_ref, under a convention you declare in the channel's supplementary information.

Cycle phase Recommended client_ref Digest of
Discover scan.completed/<repository>/<revision> Scan manifest or SARIF file
Discover finding.opened/<repository>/<id> Finding record (identifier, severity, component)
Validate finding.validated/… Ā· finding.dismissed/… Validation report
Assign finding.assigned/… Ticket created
Fix fix.deployed/<repository>/<id>/<change> Deployment confirmation and change identifier
Verify fix.verified/<repository>/<id>/<revision> Result of the re-run scan
Notify notice.early/… Ā· notice.full/… Ā· report.final/… Acknowledgement from the reporting platform (ENISA, CSIRT)

How to connect: the three usual routes. No tool needs to cooperate; all of them export in standard formats, fire webhooks, or run in your continuous integration.

Route 1 — a continuous-integration step that declares the scan's digest. Works with any tool that produces SARIF. Keep results.sarif next to the revision: it is the original the digest anchors.

# .github/workflows/security.yml — after the step that produces results.sarif
- name: Declare the scan to Safe Creative Platform
  env:
    SC_KEY: ${{ secrets.SC_EVENTS_KEY }}        # channel key (live or 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)\"}"

Route 2 — a GitHub webhook receiver. GitHub sends a code_scanning_alert webhook on every state change of an alert: created, fixed, closed, reopened. It is the cheapest hook in the whole family (it also covers alerts uploaded by Semgrep, Snyk or CodeQL). Node 20, no dependencies:

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) verify GitHub's signature first
  const expected = 'sha256=' + createHmac('sha256', GH_SECRET).update(body).digest('hex');
  const received = String(req.headers['x-hub-signature-256'] || '');
  if (expected.length !== received.length || !timingSafeEqual(Buffer.from(expected), Buffer.from(received))) {
    res.writeHead(401); res.end(); return;
  }
  const hook = JSON.parse(body);             // action: created | fixed | closed_by_user | reopened …
  archive(body, req.headers['x-github-delivery']);   // keep the JSON: it is the original the digest anchors
  // 2) declare the digest to the channel (idempotent by client_ref: the delivery id prevents duplicates)
  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.' + hook.action + '/' + hook.repository.full_name + '/' +
                  hook.alert.number + '/' + req.headers['x-github-delivery'],
      hash: createHash('sha256').update(body).digest('hex'),
      hash_alg: 'sha-256',
      occurred_at: hook.alert.updated_at || hook.alert.created_at
    })
  });
  res.writeHead(r.ok ? 204 : 500); res.end();  // on failure, GitHub redelivers
}).listen(8080);

Route 3 — a generic webhook receiver (Claude Security, SOC platforms, penetration-testing tools). Python 3.11, no dependencies. Adjust field names to your tool's schema and verify its signature if it offers one:

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

SC_KEY = os.environ["SC_EVENTS_KEY"]

class Receiver(BaseHTTPRequestHandler):
    def do_POST(self):
        raw = self.rfile.read(int(self.headers["Content-Length"]))
        hook = json.loads(raw)
        archive(raw)                                       # keep the original the digest anchors
        kind = hook.get("event", "finding.updated")        # e.g. finding.validated, fix.verified
        ref = f'{kind}/{hook.get("project", "-")}/{hook.get("id", hashlib.sha256(raw).hexdigest()[:16])}'
        event = {"client_ref": ref, "hash": hashlib.sha256(raw).hexdigest(), "hash_alg": "sha-256"}
        if "timestamp" in hook:
            event["occurred_at"] = hook["timestamp"]
        request = urllib.request.Request(
            "https://platform.safecreative.org/v1/events",
            data=json.dumps(event).encode(), method="POST",
            headers={"Authorization": f"Bearer {SC_KEY}", "Content-Type": "application/json"})
        urllib.request.urlopen(request, timeout=10)        # on error, let the tool retry
        self.send_response(204); self.end_headers()

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

The rule in §4.2 applies to all three routes: same client_ref on retries, exponential backoff, and a local outbound queue if the process cannot afford to drop declarations. Request the certification when you need it — for an inspection, a claim or a lawsuit — and the report will attest the integrity of the period's sequence; the evidence archive lets anyone match each digest against the original you keep.

Indicative regulatory anchor šŸ”’. Cyber Resilience Act, art. 14 (early warning within 24 hours, notification within 72 hours, final report within 14 days or one month; from 2026-09-11) Ā· NIS2, arts. 21 and 23 Ā· DORA, arts. 17-19 and 24 ff. None of these rules requires a third party: they require demonstrable diligence, and the moment you "became aware" is what the channel leaves proved.

11.2 Note on AI agent logs

If your case is logging agent actions, align the token with the IETF draft draft-sharif-agent-audit-trail: record_id, session_id, agent_id, action_type, outcome, record_phase and, if you keep your own chain, prev_hash. Use record_id as the client_ref. That model requires, from a certain assurance level up, that logging be done by a component independent of the agent with an external timestamp: that component is the channel, and each event's reception date (received_at) is covered by the qualified timestamp of its segment. The agent does not need to sign anything for the sequence to carry a third-party date.

{
  "client_ref": "rec_01J9ZM3T7Q8KX2",
  "payload": {
    "record_id": "rec_01J9ZM3T7Q8KX2", "session_id": "ses_44a1", "agent_id": "case-handler-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"
}