WebATEM is a LAN control surface for broadcast hardware, with the same trust model as the physical panel it replaces:
- There is no login, by design. Anyone who can reach the app's port can switch program, change keyers, upload media, and drive connected HyperDecks. This is a deliberate parity with hardware panels and with the ATEM protocol itself — the switcher accepts commands from anyone on its network with or without this app in the picture.
- The intended deployment is a trusted studio network (ideally its own VLAN with the switchers). If a device can't be trusted with your program feed, it doesn't belong on that network segment.
- Cross-origin browser attacks. The control WebSocket only accepts
browser connections from the app's own pages (same-origin, enforced
independently of
ALLOWED_HOSTS— seeconfig/websocket.py), and all state-changing HTTP endpoints require a CSRF token. A malicious website open in an operator's browser cannot drive the switchers through it. - Hostile file content. Switcher-profile XML parsing is hardened
against entity-expansion attacks (including the UTF-16 detection
bypass) and size-capped; uploaded images are re-encoded, never served
back raw (there is no public
/media/route). - Runtime surface. The container runs as a non-root user, and the frontend loads no third-party code at runtime (all assets are self-hosted — no CDN supply chain).
- No authentication or authorization — see above.
- No TLS — terminate it at a reverse proxy.
- No rate limiting.
- No size limit on a still dropped into the media pool. Deliberate: the
app is ATEM Software Control in a browser, operators do drop very large
source images, and a ceiling low enough to be a defence would be low enough
to refuse real work. Anyone who can reach the page can already cut program
and take a show to black, so filling a disk is not the interesting thing
they could do. If that matters in your deployment, cap the body at the
reverse proxy (
client_max_body_sizein nginx).
Put an authenticating TLS reverse proxy in front (basic auth or SSO), and set:
CSRF_TRUSTED_ORIGINS=https://your.hostALLOWED_HOSTS=your.host— pinning this also closes the DNS-rebinding variant of the cross-origin attackWEBSOCKET_ALLOWED_ORIGINS=your.host— only if the proxy rewrites theHostheader on the way through
Never expose the app port directly to an untrusted network.
Please use GitHub's private vulnerability reporting ("Security" tab → "Report a vulnerability") rather than a public issue. Reports that assume the threat model above are very welcome; "there is no login" is the documented design, not a finding.