Security

Threat model

Shiloh Broadcaster is designed for trusted LAN and VPN environments. There is no authentication on the audio relay protocol or mixer control plane. Security comes from:

  1. Network perimeter — UDP 5005 should not be exposed to the public internet. Use WireGuard for WAN access.
  2. Allow-list — only named senders can inject audio into the mixer (broadcaster ingest).
  3. Loopback isolation — the mixer control plane (UDP 19997) binds to 127.0.0.1 only and is never reachable from outside the host.

Firewall rules (UFW)

Minimal UFW ruleset for a mixer server with a LAN on 192.168.1.0/24 and a WireGuard VPN on 10.0.0.0/24:

# Default policy
ufw default deny incoming
ufw default allow outgoing

# SSH (adjust port if non-standard)
ufw allow 22/tcp

# Mixer audio + relay (LAN and VPN only)
ufw allow from 192.168.1.0/24 to any port 5005 proto udp
ufw allow from 10.0.0.0/24 to any port 5005 proto udp

# MIDI sender (LAN and VPN only)
ufw allow from 192.168.1.0/24 to any port 19999 proto udp
ufw allow from 10.0.0.0/24 to any port 19999 proto udp

# Mixer web UI (LAN and VPN; or lock to specific hosts)
ufw allow from 192.168.1.0/24 to any port 8889 proto tcp
ufw allow from 10.0.0.0/24 to any port 8889 proto tcp

# WebRTC media (ephemeral UDP range — shiloh-web-relay)
# Allow from anywhere if browser listeners come from the public internet
ufw allow 32768:65535/udp

# WireGuard (if running on this host)
ufw allow 51820/udp

ufw enable

Port 5005 exposure: Only open UDP 5005 to subnets where relay and broadcaster clients live. Do not expose it to the internet — there is no authentication and anyone who can reach the port can register as a relay session.

Port 8889 exposure: The mixer UI has no authentication. If you put it on the internet, put it behind a reverse proxy with HTTP Basic Auth or OAuth.


WireGuard for WAN access

For relays or broadcasters connecting over the internet, route them through WireGuard instead of exposing UDP 5005 publicly.

Server configuration (/etc/wireguard/wg0.conf)

[Interface]
Address    = 10.8.0.1/24
PrivateKey = <server-private-key>
ListenPort = 51820

[Peer]
# Studio broadcaster
PublicKey  = <client-public-key>
AllowedIPs = 10.8.0.2/32

[Peer]
# Remote relay (Pi in another location)
PublicKey  = <relay-public-key>
AllowedIPs = 10.8.0.3/32
sudo wg-quick up wg0
sudo systemctl enable wg-quick@wg0

Clients then use the WireGuard IP for --server: --server 10.8.0.1:5005.

Keepalive for NAT traversal

Add to each peer on the client side:

PersistentKeepalive = 25

This keeps the UDP hole open through NAT.


Allow-list: controlling who can inject audio

The mixer only accepts audio from senders named in [[ingest.sender]] in shiloh-mixer.toml. An unknown name receives REJECT_TX (code REJECT_UNKNOWN_SENDER).

[ingest]
slot_count = 4   # total audio slots; must cover all sender channels

[[ingest.sender]]
name       = "studio"    # must match --name on the broadcaster
channels   = 2
start_slot = 0

[[ingest.sender]]
name       = "laptop"
channels   = 2
start_slot = 2

To revoke access: remove the sender from the TOML and restart the mixer. Any active session from that sender will be evicted within 3 seconds (the ingest timeout).


Control plane isolation

The mixer control plane listens on 127.0.0.1:19997 (UDP). This is loopback-only by design. mixer_web and any scripting tools that send set_fader / set_mute / get_state messages must run on the same host.

Do not change the bind address to 0.0.0.0 without adding authentication. The control plane accepts unauthenticated JSON datagrams and can change all fader/mute/scene state.


MIDI listener exposure

The MIDI listener binds to 0.0.0.0:19999. Any host that can reach this port can inject MIDI events. Restrict with UFW (see above) or bind it to a specific interface in shiloh-mixer.toml if your setup supports it.


Secrets management

SECRET_KEY_BASE for mixer_web lives in a systemd drop-in that is not version-controlled:

~/.config/systemd/user/mixer_web.service.d/secret.conf

Do not commit this file. Keep it out of backups that are stored in plaintext. If the key is compromised, generate a new one:

mix phx.gen.secret
# Paste into secret.conf and restart mixer_web