DX Command is a local desktop application: it runs a small web server on
127.0.0.1, talks to your radio through OmniRig, and — if you ask it to —
signs in to Logbook of the World on your behalf. This page explains how that
is protected and how to report a problem.
Please do not open a public issue for a security problem.
Use GitHub's private reporting: Security → Report a vulnerability on this repository. If that is unavailable, open a normal issue saying only that you have a security report and asking for a contact route — no details.
This is a hobby project maintained in spare time. Expect an acknowledgement within a couple of weeks rather than hours, and no guaranteed fix timeline. Please give a reasonable window before public disclosure.
Useful things to include: what an attacker can achieve, the steps to
reproduce, the version (app/__init__.py or the About line), and whether you
were running the MSI, the portable exe, or from source.
Your LoTW password. Stored encrypted at rest with Windows DPAPI, bound to
your Windows account, so config.json holds only an opaque blob that is
useless on another machine or account — which matters because the folder may
sync to OneDrive. It is never sent back to the browser, and never included in
an error message: LoTW error paths report the kind of failure rather than
quoting the request URL, because the credentials travel in that URL and would
otherwise appear percent-encoded in logs. See app/secure.py, app/dxcc/lotw.py.
The local API. Binding to localhost is not authentication — any page in
your browser can send requests to http://localhost:8073. Three defences,
in app/security.py:
- a Host allow-list, which blocks DNS-rebinding attacks
- an Origin check, so a page on another site cannot mutate anything
- a per-launch random token, embedded in the page the app serves. A cross-origin page cannot read our HTML, so it can never present the header. Required on every mutating request; read-only requests and the health probe are exempt so tooling still works.
The websocket repeats the Host and Origin checks, since HTTP middleware does not run for it.
Input. Every mutating endpoint validates a typed model with real bounds
(frequency, ports, modes, string lengths, unknown keys rejected) — see
app/models.py. Request bodies are size-capped.
The browser. A restrictive Content-Security-Policy allows only same-origin
scripts and styles, with no inline script, no remote loads, and framing
denied. Third-party data (DX cluster comments, FT8 messages, solar figures) is
escaped or built with textContent, never interpolated into innerHTML, and
solar condition words are whitelisted before becoming CSS classes.
- Unsigned binaries. Releases are not code-signed, so Windows SmartScreen will warn. SHA-256 checksums are published with every release; verify them, or run from source. This is a cost decision, not an oversight.
- Same-machine processes are trusted. Another program running as you can read the token file and the DPAPI-protected password, exactly as it could read any other application's data. Protecting against that is outside what a desktop app can do.
- No remote access. The server is deliberately bound to loopback. Do not expose it to a network without adding authentication — the token protects against browser-based attacks, not against a hostile network.
- The app can key your transmitter. Please read the disclaimer in the README: operating within your licence terms remains your responsibility.