gjallarhorn starts the Cyber Resilience Act's 24-hour clock the hour an exploited vulnerability touches any release you still support, and hands a named person a filing they only have to check.
The problem
Since 11 September 2026 a manufacturer that sells software in the EU has had to report an actively exploited vulnerability in its product. The Commission's page puts it in one line: "As of 11 September 2026, manufacturers are required to report actively exploited vulnerabilities and severe incidents impacting the security of products with digital elements." (https://digital-strategy.ec.europa.eu/en/policies/cra-reporting)
The first report is due fast. Article 14(2)(a) of Regulation (EU) 2024/2847 asks for "an early warning notification of an actively exploited vulnerability, without undue delay and in any event within 24 hours of the manufacturer becoming aware of it". A fuller notification follows within 72 hours, and a final report no later than 14 days after a corrective or mitigating measure is available.
And the report cannot be sent by a machine. ENISA's FAQ on its single reporting platform says: "no Application Programming Interface (API) will be provided at the initial release of the SRP, so notifications must be submitted through the platform interface." (https://www.enisa.europa.eu/topics/product-security/single-reporting-platform-srp/frequently-asked-questions) So a person files by hand, against a 24-hour clock, and first has to find out that the clock is running at all.
Who it is for
The engineer on call at a small software company with more than one supported release: the person who has to know, within the day, whether an exploited flaw is in anything the company still supports, and who then has to fill in ENISA's form field by field.
What happens, in ten steps
One rule holds the design together: the times the law sets are computed by plain code, the reading and the drafting are done by GitLab Duo agents, and the approving and the filing are done by a person.
- Watch. A scheduled pipeline's
watch:matchjob reads CISA's Known Exploited Vulnerabilities catalogue, ENISA's EUVD and OSV.dev, and matches them against the CycloneDX SBOM of every releasesupported-releases.ymllists. - Start the clock. On a new match the job writes a match report with the moment of becoming aware and the 24-hour and 72-hour deadlines, and fails the pipeline on purpose.
- Open the incident. The failed pipeline triggers the first custom flow,
gjallarhorn-clock. It opens the incident with three dated deadline items and drafts the early warning as a note, field by field in ENISA's own field names, each value with its source. - Audit. Two CI jobs,
clock-auditandpack-check, hold the incident's dates to the match report's and every pack to ENISA's required fields and length limits. - Remedy. In the same session the flow opens the merge requests: a mitigation as a configuration change where one is needed, and one fix per affected release line (the upgrade, the relocked dependencies, the new SBOM, the tests, an advisory note). Then it drafts the 72-hour notification, which names them.
- Check the remedy. Each merge request's pipeline runs the tests, the linter, the type checker and GitLab's SAST and secret detection, and the mitigation's also shows its OpenTofu plan. If a fix's pipeline fails, a mention of the flow makes it read the failing test and push one corrective commit.
- A person approves. The person reviews and merges the merge requests and tags the release. This is the approval of the outcome; no step before it waited for anyone.
- Ship. The pipeline builds the images and the SBOM, applies the configuration, deploys to Cloud Run and probes the live service's bill of materials. The tag's pipeline publishes the release with its advisory, its SBOM and a
corrective-measure.jsonthat stamps the time the measure became available. - Close out. The passed pipeline triggers the second flow,
gjallarhorn-close-out. It records the measure, dates the final report 14 days after it, and drafts the final report. - A person files. The person checks each pack in the console, files it on ENISA's platform by hand, and tells the flow so. The flow records the filing and closes the clock.
Nine stages, each a real job
| Stage | The job that does it | What you can open |
|---|---|---|
| Plan | the clock flow opens the incident | the incident and its three dated deadline items |
| Create | the flow opens the merge requests | a fix per affected line, a mitigation if needed |
| Verify | lint, typecheck, test |
every merge request's pipeline |
| Package | package:image, package:sbom |
the images and a CycloneDX bill of materials per release |
| Secure | watch:match, with GitLab's SAST and secret detection |
the red pipeline and its match report |
| Release | deploy, release:prepare, release |
the Environments page; the Releases page, the advisory as notes |
| Configure | tofu:plan, tofu:apply |
the OpenTofu plan on the mitigation's merge request |
| Monitor | probe after every deploy |
the live SBOM read back; the console's countdowns |
| Govern | clock-audit, pack-check |
the three packs, and a person's merge and filing |
Path A, Supervised, and why Supervised is the law's shape
gjallarhorn is a Path A entry: the product it watches, the pipeline and the automation were all written from 12 October 2026, inside the submission period. Its autonomy level is Supervised, which the rules define as "you approve the outcome, not the individual steps".
Supervised is not a step back from a hands-off design. It is where the law puts the person. Article 14 lays the duty on the manufacturer, and ENISA's platform takes a report only through its own interface, so someone answerable has to read what is filed and press the button. gjallarhorn does everything before that: it finds the match, starts the clock, scopes the releases, opens the fix and writes all three reports. Inside a session the agents never stop to ask. The two flows hold no tool that merges, approves, tags or releases, so the merge, the release tag and the filing stay a person's, and those three acts are the outcome being approved.
The drill, and its measured times
A real exploited vulnerability in the product's own stack cannot be ordered for a demo, so the repository carries a drill: a real advisory, already fixed upstream, is replayed as if it had just been listed, and everything after that runs for real.
In the recorded drill of 13 October 2026:
- 2 min 9 s from detection to the early warning drafted and waiting for the person's check.
- 11 min 37 s from detection to the fix ready to merge, its merge request open and its pipeline green.
- 69 min 57 s from the drill's start to the closed clock, the person's reviews, both merges, the release and the final report included.
The drill's own pages: the watcher's red pipeline (https://gitlab.com/gitlab-ai-hackathon/transcend-october-2026/43267509/gjallarhorn/-/pipelines/2942380423), the incident with its three packs (https://gitlab.com/gitlab-ai-hackathon/transcend-october-2026/43267509/gjallarhorn/-/issues/37), the mitigation's merge request (https://gitlab.com/gitlab-ai-hackathon/transcend-october-2026/43267509/gjallarhorn/-/merge_requests/11), the fix's (https://gitlab.com/gitlab-ai-hackathon/transcend-october-2026/43267509/gjallarhorn/-/merge_requests/12), the release (https://gitlab.com/gitlab-ai-hackathon/transcend-october-2026/43267509/gjallarhorn/-/releases/v1.0.1) and the closed clock in the console (https://gjallarhorn-console-380183962289.us-central1.run.app/clocks/37).
What is real and what is staged
- The bed is built to be found. The watched product, status-page, lists an older supported line, 1.0.0, whose SBOM names Starlette 1.0.0, so the watcher has something real to match. Nothing in the repository builds or runs that line. The hosted edition runs release 1.1.0 on a current, patched Starlette, with demo data only.
- The entry is replayed. The drill replays a real advisory, GHSA-86qp-5c8j-p5mr (CVE-2026-48710), fixed in Starlette 1.0.1 and on CISA's exploited catalogue since 2 September 2026, as if it had been listed at the moment of the run. The repository holds no exploit and no test that shows an attack; the advisory is named by its id and its address, nothing more.
- Nothing was filed. Every pack a drill drafts is marked "DRILL: NOT FILED",
pack-checkrefuses a filed label on a drill, and no report of any kind was sent to ENISA. - The mitigation has something to set because the drill empties it first. Before a drill a script switches off the hosted edition's host check, a hardening setting, so that the flow's mitigation has a change to make. The running release is patched either way.
- The watcher's schedule is hourly and switched off. The drills played it by hand. Left on, its first run would open a real clock on the declared 1.0.0 line, and the rules ask that nothing change during judging.
- In the recorded drill the person was played. The person's part (reading the packs, both merges, the release tag, the word that closes the clock) was carried out through GitLab's API under the entrant's own account by a coding agent he briefed, following the run order in
flows/README.md. The flows cannot tell the difference: they act on a merge and a mention, whoever makes them.
On Google Cloud
Two services run on Cloud Run, both public, both deployed by the GitLab pipeline: each deploy job moves the services to the new image digests through OpenTofu, and every job signs in to Google by exchanging its GitLab ID token through Workload Identity Federation, so no Google key is stored anywhere.
- The console, https://gjallarhorn-console-380183962289.us-central1.run.app : the open and closed clocks with their three countdowns, each pack field by field, the supported releases with their SBOMs, and the advisories. It reads GitLab's public API without a token and keeps no database.
- status-page's hosted edition, https://status-page-380183962289.us-central1.run.app : the watched product itself, release 1.1.0, with demo data.
Built with
GitLab Duo Agent Platform (two custom flows and one custom agent, their YAML in flows/ and agents/), GitLab CI/CD, the package and container registries, releases and environments; Python 3.14 with uv, FastAPI, Jinja2, httpx and pydantic; CycloneDX SBOMs; CISA's KEV catalogue, ENISA's EUVD and OSV.dev as feeds; OpenTofu, Cloud Run, Artifact Registry and Workload Identity Federation on Google Cloud; pytest, ruff and pyright as the gate.
Links
- GitLab project (public, MIT): https://gitlab.com/gitlab-ai-hackathon/transcend-october-2026/43267509/gjallarhorn
- The flows' and the agent's YAML: https://gitlab.com/gitlab-ai-hackathon/transcend-october-2026/43267509/gjallarhorn/-/tree/main/flows and https://gitlab.com/gitlab-ai-hackathon/transcend-october-2026/43267509/gjallarhorn/-/tree/main/agents
- Pipeline history: https://gitlab.com/gitlab-ai-hackathon/transcend-october-2026/43267509/gjallarhorn/-/pipelines
- The console, on Cloud Run: https://gjallarhorn-console-380183962289.us-central1.run.app
- The watched product's hosted edition, on Cloud Run: https://status-page-380183962289.us-central1.run.app
- Video: https://youtu.be/s7DTaZECecA
Log in or sign up for Devpost to join the conversation.