Cyresora Manual, as shipped in the download
Cyresora — User Manual
A NIS2 and CRA incident-reporting register: what you became aware of, when, which reporting clocks that started, what has actually been submitted, and what is still running. The API reports version 1.0.4 [src: app/controllers/api.php:17].
About this manual
Every statement here was written by reading Cyresora's own source, and each one carries the file it came from in a bracketed src marker. Paths are relative to the folder this docs directory sits in. If a sentence disagrees with the application, the application is right and this manual has a bug.
This manual is not legal advice and does not verify either instrument. Articles, durations and dates below are reported as what the product implements — check them against the Official Journal and, for NIS2, against your own Member State's transposition, which the product deliberately does not model [src: app/src/Clock.php:38].
What Cyresora is
The clock engine is pure, offline, and never reads the system time — every function takes "now" as an argument, so tests pin real instants instead of hoping the suite does not run at midnight [src: app/src/Clock.php:3].
That is not a testing detail. It is why the deadlines in this manual could be reproduced exactly, by running the engine rather than describing it.
⭐ Four anchors, not two ladders
The obvious model — two ladders of 24 h / 72 h / final, both counted from the moment you found out — is wrong, and the source says it is wrong in the direction that produces a confident, late filing [src: app/src/Clock.php:9].
Read the instruments and there are four different anchors [src: app/src/Clock.php:11]:
| Regime | Stage | Counted from | Length |
|---|---|---|---|
| CRA — actively exploited vulnerability | early warning | becoming aware | 24 h |
| notification | becoming aware | 72 h | |
| final report | a corrective or mitigating measure being available | 14 days | |
| CRA — severe incident | early warning / notification | becoming aware | 24 h / 72 h |
| final report | submission of the incident notification | 1 month | |
| NIS2 — significant incident | early warning | becoming aware | 24 h |
| notification | becoming aware | 72 h | |
| intermediate report | a CSIRT asking for one | — | |
| final report | submission of the notification | 1 month | |
| final report after handling | the handling being completed | 1 month |
The regimes and the provision each rests on are declared in one place [src: app/src/Clock.php:70], and every stage carries the article it comes from and the instrument's own wording [src: app/src/Clock.php:87].
⭐ Three consequences the register has to carry
*A deadline can be not started yet.* The CRA's fourteen days does not begin until a corrective or mitigating measure exists. Showing a due date before then would be an invented date, so the engine returns pending and names the event that will start it [src: app/src/Clock.php:33]. In the ladder that looks like this — a Friday-evening discovery with no remedy yet, run through the engine:
early Early warning anchor=awareness due=2026-09-19 19:00:00 state=open weekend=YES
notify Vulnerability notification anchor=awareness due=2026-09-21 19:00:00 state=open weekend=no
final Final report anchor=remedy due=(none) state=pending weekend=no
The one-month clocks run from the actual submission, not from the deadline [src: app/src/Clock.php:36]. Notify at hour 40 and the final report is due a month after hour 40. Executed on a NIS2 incident notified at 23:30 on 5 May:
final Final report anchor=notification due=2026-06-05 23:30:00
the 72-hour deadline would have been 2026-05-07 08:00:00
a month from that deadline 2026-06-07 08:00:00 ← two days later than the truth
A product that anchored on the deadline rather than the filing would have shown a due date two days late. This one does not.
NIS2 is a Directive. What binds an entity is its Member State's transposition. The engine models the Directive's own stages and the register records the national authority; it does not pretend to know a national variation [src: app/src/Clock.php:38].
⭐ Two arithmetic hazards, both of them "wrong by a day" bugs
In a product whose entire value is the date, these are not tidiness [src: app/src/Clock.php:42].
Calendar months. PHP's own modify('+1 month') from 31 January returns 3 March — a deadline later than the law allows, silently. Every calendar-month deadline here clamps to the last day of the target month instead [src: app/src/Clock.php:45]. Both, side by side, from the running engine:
2026-01-31 09:00 PHP: 2026-03-03 09:00 Cyresora: 2026-02-28 09:00
2026-01-30 09:00 PHP: 2026-03-02 09:00 Cyresora: 2026-02-28 09:00
2026-03-31 12:00 PHP: 2026-05-01 12:00 Cyresora: 2026-04-30 12:00
Daylight saving. Twenty-four and seventy-two hours are absolute durations computed on UTC instants, where a clock change cannot touch them. "One month" is a calendar expression: it is computed in the business timezone and converted back, so it lands at the same wall-clock time — which is what a deadline in months means to the person who has to meet it [src: app/src/Clock.php:48]. Executed across both European changeovers, business timezone Europe/Bratislava:
one month from 2026-02-25 13:00 UTC -> 2026-03-25 13:00 UTC (14:00 local -> 14:00 local)
one month from 2026-09-25 13:00 UTC -> 2026-10-25 14:00 UTC (15:00 local -> 15:00 local)
24 h from 2026-10-24 13:00 UTC -> 2026-10-25 13:00 UTC (absolute, untouched)
72 h from 2026-10-24 13:00 UTC -> 2026-10-27 13:00 UTC (absolute, untouched)
The second month-deadline moves by an hour in UTC precisely because it did not move in local time.
⚠️ The clocks do not stop at the weekend
The source says this out loud because a buyer will assume otherwise: neither CRA Article 14 nor NIS2 Article 23 has a working-day carve-out, and a Friday evening discovery is due on Saturday evening [src: app/src/Clock.php:53].
The engine flags it rather than leaving you to notice — every stage carries a weekend flag computed in your business timezone [src: app/src/Clock.php:187], which is why the Friday example above shows weekend=YES against the 24-hour warning.
What a stage can say
Each stage reports one state [src: app/src/Clock.php:235]: pending when its anchor has not happened, open, due_soon inside the last six hours, overdue, submitted, or submitted_late when the filing came after the due instant [src: app/src/Clock.php:236]. A stage that exists only on request — NIS2's intermediate report — is not_requested until an authority asks [src: app/src/Clock.php:214].
Filing the same stage twice is refused, in the browser and through the API alike, with the same reasoning: "Filing it twice is a duplicate, not a second duty discharged" [src: app/controllers/api.php:155].
Users and roles
Four roles, not the usual three [src: app/controllers/app.php:21]. Executing the product's own permission function rather than reading the map:
audit.view viewer=Y member=Y responder=Y admin=Y
incident.edit viewer=n member=Y responder=Y admin=Y
notify.submit viewer=n member=n responder=Y admin=Y
contact.edit viewer=n member=n responder=Y admin=Y
settings.edit viewer=n member=n responder=n admin=Y
responder exists to separate two authorities that look alike. Editing an incident is ordinary work; recording that a notification was submitted to an authority is the act that stops a statutory clock, and only a responder or an admin can do it [src: app/controllers/app.php:26].
⚠️ A viewer can read the audit trail
The audit page and its CSV both require audit.view [src: app/controllers/v3.php:15], and the viewer role holds the wildcard *.view [src: app/controllers/app.php:24], which matches it — shown by the executed table above. If a read-only account is meant not to see who recorded which filing, that is not the behaviour you have. This has been raised against the product.
Backups are gated properly, by contrast: both downloads require settings.edit [src: app/controllers/api.php:521], which no role below admin holds [src: app/controllers/api.php:527].
Webhooks fire from every path
Cyresora declares three webhook events [src: app/src/Webhook.php:20]. incident.opened fires from the browser [src: app/controllers/app.php:609] and the REST API [src: app/controllers/api.php:114]; notification.recorded fires from the browser [src: app/controllers/app.php:689], the REST API [src: app/controllers/api.php:161] and the agent tool [src: app/controllers/api.php:446]; deadline.alert fires from the alert watcher [src: app/controllers/app.php:363].
Deadline alerts
The warning window. "Warn me this many hours before a deadline" (1 to 72, default 6) decides when a running stage shows as due within hours [src: app/controllers/app.php:228]. Before 1.0.4 the setting was saved but the board always used 6.
Alerts to the rota. Switched on in Settings, alerts go out at your alert points (12, 4 and 1 hours before a deadline by default) and once when a deadline passes, but not for a stage that has been overdue for more than a day [src: app/src/Clock.php:286]. The notifiers on the rota for that regime and their deputies are emailed; with nobody on the rota, the organisation's contact email is [src: app/controllers/app.php:310]. The email names the incident reference and title, the stage and its provision, the deadline and a link. A signed deadline.alert webhook fires at the same moments. Each point is latched, so the Clocks screen and the cron route alerts/run?t=<token> never send it twice [src: app/controllers/v3.php:269]. A failed send, a missing mail server or an empty rota is shown on the Clocks screen [src: app/controllers/app.php:440].
The API and agent access
Six tools [src: app/controllers/api.php:299]: four read — every running deadline worst-first, the incident list, one incident, and what applies to a given situation — and two write, opening an incident [src: app/controllers/api.php:373] and recording a notification [src: app/controllers/api.php:405].
The recording tool is gated on notify.submit [src: app/controllers/api.php:407] and refuses a stage that is not running on that incident, naming the ones that are [src: app/controllers/api.php:421].
The instructions given to an agent are the clearest short statement of what this product is for [src: app/controllers/api.php:286]:
"You cannot set a deadline. You record two facts — when the organisation became aware, and when a notification was actually submitted — and the instrument decides the rest."
And the sentence that matters most [src: app/controllers/api.php:292]:
"Never record a notification as sent unless the human has told you it was sent. That record is what stops a statutory clock and what an authority will later be shown."
What Cyresora does not do
It does not read the system clock [src: app/src/Clock.php:3] — every deadline is computed from instants you supply.
It does not invent a due date for a clock that has not started [src: app/src/Clock.php:33].
It does not anchor a one-month report on the deadline [src: app/src/Clock.php:36] — it anchors on the filing.
It does not model your Member State's transposition of NIS2 [src: app/src/Clock.php:38].
It does not pause at weekends [src: app/src/Clock.php:53].
It does not submit anything to an authority. It records that you did.