What's new in v1.17.2¶
Released on 28 August 2026.
The focus is the audit webview: authentication with roles, four new pages — device catalogue, destination catalogue, statistics and operations log —, a shared time-range filter, the now-visible Q/R arc and a hardening package. On the router itself a correction that affects every evaluation — the audit-trail timestamps were not in UTC —, plus schema v9 and a series of smaller fixes.
Before the update¶
Audit timestamps were not in UTC until now¶
Up to 1.17.1 the router wrote the audit-trail timestamps in the node's local
time zone instead of UTC. On a node in Europe/Zurich, all rows written
before the update in associations, dimse_operations and router_events
are therefore off by 1–2 hours — and with them everything that reads those
columns: the webview's display and time-range filter, Grafana, the retention
statements (which delete too early as a result) and any correlation with PACS
or modality logs. On a node that itself runs in UTC nothing is shifted.
Rows written from 1.17.2 on are correct; an updated database therefore has
a jump at the point of the update.
If you want to correct the existing rows, there is a one-time correction
script in share/audit-timestamp-utc-fix.sql.example. What matters:
- Before stopping the old instance — that is, before the update, not just
before running the script — let the audit spool drain (
dicom-router-ctl status→audit-backlog: 0). A row that 1.17.1 wrote into the spool is delivered by 1.17.2 with a correct timestamp; the script would wrongly shift it along. - The script takes the node's time zone as input (as an IANA name, e.g.
Europe/Zurich), runs per node and with the router stopped (instructions in the script). - It is not idempotent — a second run shifts again.
Whether to correct at all is an operational decision; the router works either way.
Schema v9: the index build briefly blocks writers¶
The migration to schema v9 is additive except for dropping a never-used column
default (behavior-neutral) — beyond that, an index on dimse_operations.rq_at
and the view device_capabilities; a running 1.17.1 node keeps writing
against a v9 database unchanged. A 1.17.1 node that
restarts against the migrated database, however, refuses to boot (downgrade
guard) — update such a node instead of restarting it. The index build, however,
takes a write lock on dimse_operations. On a large database the update
sits there for a while; the audit buffers through its spool in the meantime.
To avoid the window, create the index in advance with CREATE INDEX
CONCURRENTLY under the same name idx_dimse_rq_at — the migration then finds
it and skips it. For Grafana, a GRANT SELECT on device_capabilities is
added.
Webview: network-bound only with authentication¶
Up to 1.17.1 the webview could bind to any address and merely warned in the
log. An installation with WEBVIEW_LISTEN=0.0.0.0:8406 (or a fixed network
address) no longer starts after the update — under systemd it runs into a
restart loop because of Restart=on-failure; the log says "refusing to listen
on … without authentication". Three ways out: back to 127.0.0.1 with access
through an SSH tunnel, WEBVIEW_AUTH=file with a user file and TLS, or
WEBVIEW_AUTH=proxy behind a reverse proxy that already authenticates. The
instructions are in webview/README.md. A bind to loopback does not change.
Webview: no more iframe embedding¶
Every response carries frame-ancestors 'none' and X-Frame-Options: DENY.
If you have embedded the viewer in a portal page, you will need a link instead
of a frame from now on.
Webview: grants for the new pages¶
Device catalogue, destination catalogue and statistics read the base tables
associations, presentation_contexts,
presentation_context_transfer_syntaxes and dimse_operations directly. If
the database user webview_ro lacks SELECT on one of them, the page answers
with a database error. The complete grant block is in webview/README.md.
dicom-router-ctl: exit code 3 on refusal¶
drain, resume, reload and shutdown now exit with 3 when the router
refuses the command — previously that was a 0, and automation under set -e
read success while, say, the old configuration kept running. 1 remains "not
reachable". A drain on an already draining router and a resume on an
already ready one stay at 0, so retry loops do not break.
The audit webview¶
Authentication with roles¶
Three modes: none (default, only permitted on loopback), file with a login
form against a bcrypt user file, proxy with the identity taken from a header
of a trusted reverse proxy. On top of that the roles phi and nophi:
nophi sees patient name and patient ID masked and cannot search by them —
the masking sits in the SQL query, not merely in the display. An access log
records who searched for what and when, and optionally the webview terminates
TLS itself.
Device catalogue and destination catalogue¶
/devices shows which device calls from which IP, with which implementation,
which presentation contexts it proposes, which are accepted and which are
rejected — the latter answers, without SQL, why a device cannot send
something. The device page condenses "what goes where" into one row per SOP
class and destination, counted in associations rather than individual
contexts. /destinations is the opposite direction: the places the router
sends to — with the destination's implementation identifier, what it accepts
and what it does not, and the DIMSE status codes it returns, the difference
between "reachable" and "healthy". The two catalogues cross-link the same peer,
and both lists are sortable via the column headers.
Statistics¶
/stats shows, over the selected time range, traffic matrix, top talkers, SOP
classes, response times, DIMSE errors and rejected associations — the
operational overview for installations without Grafana. Response times list
the 95th percentile next to the mean, because neither the mean nor the maximum
answers "does this destination stall regularly".
Operations log¶
/events lists the centrally collected WARN and ERROR messages of all nodes —
including the events without an association (startup, reload failures,
database warnings) that were previously only visible via SQL. Filtering works
by time range, level, node and message text; the text search is only available
to the phi role, because the messages are masked for nophi.
Shared time range and the Q/R arc¶
One time-range filter — 24 hours, 7 days, 30 days (default), everything, or a free from–to — applies to both catalogues, the statistics and the operations log and is remembered; a time range in the URL wins, so shared links work. And the Q/R arc is visible: the detail view links the two associations of a retrieve in both directions and shows the MessageID assigned by the router, through which the C-STOREs of one leg can be matched to the C-MOVE of the other.
Convenience with and without JavaScript¶
A slim, static JavaScript layer adds copy buttons to the monospace fields (UIDs, AE titles, IPs; they require HTTPS or localhost), makes preset and selection filters submit immediately on change, and shows the browser's local time as a tooltip on every UTC timestamp. Inline scripts remain blocked by the Content-Security-Policy, and every page keeps working without JavaScript — sorting and filtering happen server-side.
On top of that, a hardening package: security headers on every response, HTTP timeouts and a graceful shutdown, a login lockout that holds up under parallel attempts, and CSRF protection for the login.
Database¶
The new index on dimse_operations.rq_at speeds up queries over narrow time
windows on the largest table considerably (measured on 1.69 million rows:
4.6 ms instead of 107 ms for one hour). The new view device_capabilities
answers "what can device X do" in one place: one row per device, destination
and SOP class with both outcomes, counted in associations — the existing views
remain for existing dashboards. And the timestamp correction itself: the write
path subtracted the zone offset twice; new rows are in UTC, and for existing
rows the section above under "Before the update" applies.
Further fixes¶
--render-haproxyto stdout stays clean: neither regular log output nor Logback status messages end up in the redirected HAProxy configuration any more.- Locale hardening: the state words of the management responses and the
command names in
commands:selectors are processed locale-independently — under a Turkish system locale both previously misfired. - The webview again accepts hostnames for
WEBVIEW_LISTENthat resolve exclusively to loopback (say, an/etc/hostsentry pointing to127.0.0.1); the error message on refusal now names the actual resolution. dicom-router-ctl drain-and-shutdownrecognises "already draining" by the precise state word and no longer treats a refusal as success.
There is also a series of smaller fixes in the webview and the scripts; the complete list is in the package's CHANGELOG.