Dicommunication is a trusted-network admin workstation tool. It talks to PACS, RIS, and modalities on a clinical VLAN so an administrator can prove a link works. It is not a PACS, not a viewer, not an archive, and not a multi-user service.
Read this before pointing it at anything that holds real patient data.
Designed for: one operator, on one machine, on a network they are already trusted on — a PACS VLAN, a lab bench, or a jumphost that can reach DICOM ports.
Not designed for: exposure to the internet, shared/multi-tenant use, or a network where other users are untrusted. There is no login, no user model, and no audit trail of who did what.
If you need it reachable from more than the machine it runs on, put an authenticating reverse proxy in front of it and treat that proxy as the security boundary.
These are properties of the tool, not defects. Please do not file them as vulnerabilities — but do factor them into where you deploy it.
| Property | Why | What to do about it |
|---|---|---|
| The web UI and JSON API have no login | It is a diagnostic console for one operator, like a serial terminal | Docker publishes it on 127.0.0.1 only. Front it with an authenticating proxy to go wider. |
| DICOM and HL7 are sent in the clear | The protocols are used as the peers speak them; this tool is for reproducing what a modality does | Terminate TLS elsewhere, or stay on the clinical VLAN |
PDF to DICOM reads any path you type, and /api/tools/pdf-store/scan will list any directory |
It is a local file picker for the operator's own machine | Do not expose the UI beyond the workstation |
/api/logs returns absolute host paths |
Diagnostic output for the person at the keyboard | Same as above |
| The MWL SCP listens on all interfaces | A modality has to be able to C-FIND this workstation or the feature is pointless | It only listens once enabled in Configuration. DICOMM_DICOM_BIND pins it to one NIC. |
| Tag Editor writes patient study data back to the configured PACS, with no confirmation beyond the browser prompt | It exists specifically to correct stuck report-workflow metadata on a real archive | Only overwrites a tag already present, never invents one. Against a real Vue archive, Push has been observed to report success without the stored object actually changing — Fetch and check the current values before and after every Push. Use Seed test studies (synthetic ARNPRO^TESTBENCH patient, not a real one) to try Push before ever running it against a real study. |
| Dicom Router moves real patient studies (C-MOVE then C-STORE) to configured destination nodes on an unattended schedule, with no per-study confirmation | That's the point of a router — it forwards without anyone watching | A route rule's destinations are exactly what you configured, nothing else; there is no discovery or fan-out beyond the node list on the rule. Review a rule's destination nodes and filters before enabling it, same care as any AE you register as a C-MOVE destination elsewhere. |
| Dicom Cleaner permanently overwrites pixel data (a black rectangle) on real patient studies and C-STOREs the result back to a PACS, with only a browser confirmation prompt in front of it | It exists specifically to redact burned-in identifiers on a real archive | It only redacts the one rectangle you configure — verify the region against the actual image layout before running it on a real study, not just the synthetic default. New UID (the default) leaves the original instance on the PACS untouched, so a wrong region is recoverable; Same UID relies on the destination PACS overwriting on a duplicate SOP Instance UID C-STORE, which is not guaranteed — confirm that behavior on a test study first. Dicom Cleaner does not touch PatientName/PatientID/other tags; it is not a substitute for Dicom Anonymizer. |
Everything lives unencrypted under the data directory (~/.dicommunication,
%LOCALAPPDATA%\dicommunication on Windows, /app/data in Docker):
results.jsonkeeps the last 200 tool results, including the full body of any HL7 message you sent, the worklist rows a C-FIND returned, and the parsed text of Structured Reports you retrieved. If you send a real ADT, query a real worklist, or retrieve real reports, patient identifiers and report text are written to this file in cleartext.worklist.jsonholds whatever you typed into the local worklist.route_runs.jsonkeeps the last 500 Dicom Router run records, including the patient name, patient ID, accession number, and study date of every study a rule matched. A rule that runs against real patient data writes those identifiers to this file in cleartext on every run, whether or not the rule forwards anywhere.route_rules.json(the rule definitions themselves — source PACS, filters, schedule, destination AE titles) holds configuration, not patient data.dicommunication.logrecords what you ran, against which AE titles and endpoints.
There is no retention policy and no encryption at rest. On a machine that touches
production data, treat the data directory as containing PHI: put it on encrypted
storage, and clear it when you are done. Logs → Clear empties the log file;
deleting results.json clears the result history, and deleting route_runs.json
clears Dicom Router's. Uninstalling the Windows MSI
asks once whether to also delete the whole data directory; answering No (the
default) leaves it in place, same as before this prompt existed — MSI uninstall
only removes what it installed under Program Files, not files the running app
wrote to %LOCALAPPDATA% afterward.
The built-in Testbench C-STORE sends a synthetic patient (ARNPRO^TESTBENCH /
ARNPRO-TEST). Everything else sends exactly what you give it.
Two bind addresses look like findings and are not. Both are asserted by tests in
tests/test_packaging.py, so changing either is a conscious act:
| Port | Default | Override |
|---|---|---|
8080 web UI |
127.0.0.1 — the UI has no login |
DICOMM_HTTP_BIND |
11112 MWL SCP |
all interfaces — modalities must reach it | DICOMM_DICOM_BIND |
Startup logs which address the UI was published on, so docker compose logs
explains a UI that will not load from another machine.
Do not open a public issue. This repository does not accept bug reports or patches.
If you found a security problem, use Security → Report a vulnerability so the report stays private. Include the version from About (or GET /health), what you pointed the tool at, and the smallest reproduction you have. There is no bounty and no SLA — this is a single-maintainer tool.
Nothing here is privileged; you can reproduce the whole security review:
pip install -r requirements-dev.txt
python -m pytest # full suite
pip install pip-audit bandit
pip-audit -r requirements.txt # known advisories in declared dependencies
bandit -r app # static analysisbandit reports four medium findings that are expected: three
hardcoded_bind_all_interfaces hits (0.0.0.0 defaults an SCP needs in order to
accept associations) and one urllib.urlopen (the launcher polling its own
/health on loopback to know when the server is up).
pytest skips one ICMP test where the host does not allow raw sockets, which is
normal in a container without CAP_NET_RAW.
The latest release only. Fixes go on main and into the next tag; there are no
maintenance branches.