Research
DIVD's alert took 19 minutes. Its first response took 19 hours.
The Dutch Institute for Vulnerability Disclosure, DIVD, detected its ticketing-system breach in 19 minutes but took 19 hours to begin responding. Login credentials leaked through an error, leading to host root access. Patches are available, while the scope of ticket-data exfiltration remains under investigation and determines who needs warning.

In this article
After 19 minutes, monitoring had detected the intrusion. The first response and formulation of a containment plan took 19 hours. The Dutch Institute for Vulnerability Disclosure, DIVD, published both figures in its own incident review. An alert reached its volunteer SOC, with no hour-by-hour public account of that initial wait. Executing the plan then took another nine hours, including gathering in person and carefully isolating the data center while retaining the responders' own access.
The loss extended beyond a website. Zammad handled DIVD's CSIRT email conversations, potentially holding other organizations' vulnerabilities, exposed addresses and correspondence. The intrusion occurred on September 21, was first disclosed on September 24 and remained under data investigation in October. Two processes explain its significance: how an attacker gained the server's highest privileges, and how defenders turned an alert into an action capable of stopping the intrusion.
1 An error response carried other users' login credentials
Zammad maintains WebSocket connections with browsers to deliver updates to tickets. At connection time, it retains request context, including the browser's Cookie header. A session cookie lets subsequent requests continue under an authenticated user's identity. That makes it a credential requiring protection.
In the pinned 6.5.4 commit 15c7e6d4ffd9, onopen() in lib/websocket_server.rb, lines 60–75, stores each connection's headers in the process-wide @clients registry. When an event arrives, lines 107–123 pass the complete registry to the handler. Context needed for one connection consequently brings every connection's data into the same object.
Two conveniences then meet. In lib/sessions/event/base.rb, lines 3–14, every supplied key becomes an instance variable, including the @clients registry. In lib/sessions/event.rb, lines 6–24, a client-supplied event name selects a Ruby class. If execution raises an exception, its e.message goes straight back to the client.
Base supplies shared behavior to other event classes and has no run() implementation of its own. The old dispatcher nevertheless allows it to be selected. Calling the missing method raises NoMethodError. In the affected runtime, the exception text includes the receiving object's representation, bringing other users' cookies from its properties into the response. Horizon3.ai confirmed in its October 7 reproduction that an unauthenticated connection can trigger the error. The disclosed sessions belong to authenticated users simultaneously connected to that WebSocket process. Taking an administrator's identity additionally requires an administrator session to be present in that registry.
An ordinary user's session exposes material that user may access. An administrator's session opens more powerful features. Horizon3.ai used a leaked administrator identity to reach the package installation API, write a server-rendered mail template and trigger it through password reset. Template evaluation then executed code as the zammad service account. An error-disclosure bug had supplied the starting point for host compromise. This is the researchers' publicly reproduced path. DIVD has not published a complete forensic record of the requests and templates used against its own instance.
2 A service restart handed writable files to root
Access as zammad is still below Linux's highest privilege level. The next problem was the startup arrangement in DEB/RPM packages. The vendor's CVE-2026-102490 advisory explains that services began as root and executed files owned and writable by zammad before dropping privileges. The installation directory was writable by that account too. A lower-privileged account could therefore change content that a higher-privileged process would execute at its next start.
Automatic restarts shortened the wait. When a service stopped, it restarted automatically, giving the attacker an opportunity to have changed files executed as root without waiting for an administrator's maintenance session. This issue affected DEB/RPM installations built through packager.io. Source installations and official container images were outside this package-startup vulnerability, and the vendor explicitly says its SaaS customers were unaffected by this package issue. The WebSocket disclosure supplied the foothold, and the package issue expanded it into control of the host.
AI was the most conspicuous label attached to the attack. DIVD assessed that agents were used after root access, citing the volume of activity over a short period, annotated scripts and comments resembling exchanges between agents. Public material supplies neither model-service logs nor an established model identity.
The repair boundaries are distinct as well. WebSocket hardening shipped in 7.2.0. The DEB/RPM escalation was first fixed in 7.2.2, released on October 8. As of October 13, the vendor's release listing makes 7.2.2 the current target. Installations still on 6.3.0–6.5.4 need to leave the old branch immediately. Restricting access to the service can contain exposure while an upgrade is unavailable, at the cost of blocking normal external use of those entry points. Moving to an arbitrary 7.x release leaves the local escalation in DEB/RPM packages at 7.2.1 or earlier unresolved.
An ordinary upgrade still follows the official update procedure: check dependencies and release notes, stop the service and take a backup. Upgrades spanning multiple major versions must retain the intermediate major-version steps. Check maintenance-script and migration output before validating login and ticket handling. A failed update should leave access restricted, with the old application kept off the public entry point.
The vendor says runtime changes in 7.0 and later prevent exploitation of this session-disclosure path, while 7.2.0 also replaces the dangerous code. That distinction explains DIVD's own lesson about remaining on the old branch and separates similar code from exploitability in a deployed environment. A software upgrade alone cannot reestablish trust in a host already controlled as root. It needs evidence preserved and rebuilding from a trusted image, followed by restoration of reviewed data and configuration. An affected older package is an unsafe rollback target.
3 The patch separates dispatch, context and error handling
The 7.2.0 repair, commit a97f50ab3bfa, changes all three. The new backend_for(), lines 38–56, restricts event-name syntax, requires a concrete event class, excludes abstract classes and checks for a run() implementation. The existence of a Ruby constant no longer suffices to make it dispatchable.
Next, lib/websocket_server.rb, lines 107–118, passes the current client instead of the complete clients registry. The new base class's constructor, lines 17–30, explicitly receives named parameters instead of turning every supplied key into an instance variable. A future event failure consequently has less opportunity to expose neighboring connections.
Finally, external error responses use fixed text. Internal exception details stay in server logs, while the caller receives a generic processing failure through generic_error(), lines 61–70. Removing detailed errors addresses the immediate disclosure, while reducing the context supplied to a handler limits what another failure could expose. The changes reinforce each other.
# Error field in the old response
error: e.message
# Error field after the repair
error: __('The event could not be processed.')
Acceptance should inspect response contents and preserve ordinary behavior. Upstream tests use a fake session cookie to check error responses for unknown events, abstract base classes, handlers without run() and handlers that deliberately raise. They also check normal events and database-connection cleanup. A repaired response may still include the caller's own payload. The boundary concerns internal context and credentials from other connections. After checking that boundary in an isolated copy, an administrator should complete a normal login and real-time ticket update. Historical raw error logs may themselves contain credentials and need redaction before being handed to another analyst.
4 Containment needed an access route that would survive isolation
The review identifies a concrete constraint. Responders had to gather in person and carefully isolate the Amsterdam data center while keeping themselves able to operate it. Disconnecting a network can remove both the attacker's and the administrator's access. Preparation needs a management route that survives business-network isolation, together with people authorized and able to order and implement that isolation. Sending the alert to more chat groups supplies neither capability.
Detection and containment need separate exercises. The latter should continue through authorization, responder availability and the actual interruption of the attacker's access, while verifying that maintenance access survives isolation. For volunteer-run DIVD, gathering people and arranging remote access are themselves parts of the plan. An exercise that ends when the alert arrives leaves this difficult work until an actual intrusion.
5 Other organizations' secrets remained inside the tickets
The initial outgoing vulnerability notifications were outside this ticket system. Replies and subsequent messages were stored there, including mail to [email protected] and addresses ending in @csirt.divd.nl. DIVD's data-investigation overview reports signs of partial exfiltration from CSIRT tickets, with the exact scope still under investigation. Organizations that corresponded with the team should treat those exchanges as potentially obtained, especially follow-up submissions containing vulnerable IP addresses, vulnerability details or masked credential extracts.
The same overview reports signs of partial exfiltration from the office GSuite environment, IT support, project collaboration and operational system data. Administrative material held in GSuite remains under investigation. It reports no signs of compromise in HR, Slack or source repositories. The primary store of zero-day information was outside the affected area, while copies inside Zammad may have been exposed. CVE records and the CNA process occupied a separate environment, with no indication it had been accessed. Keeping those objects distinct determines whom to notify and which material to investigate. A pending investigation item cannot automatically be counted as leaked data.
An upgrade can close the execution path while the information in tickets remains outside the organization's control. For people who exchanged mail with DIVD, the practical follow-up is to identify sensitive material they sent and watch for impersonation that includes genuine details of those conversations. A trusted organization's compromise can turn a real past exchange into material for the next deception. Restoring service leaves that consequence to address.