Security
Authentication, encryption, and access control in Punch.
Threat model
Punch operates in a specific threat landscape:
| Threat | Severity | Mitigation |
|---|---|---|
| Unauthorised session access | High | Token-based authentication |
| SRT stream interception | High | Auto-generated AES passphrases |
| Session hijacking | Medium | Short-lived tokens, role-based access |
| Denial of service | Low | Rate limiting, session TTL |
| IP address exposure | Inherent | Required for SRT — peers must know each other’s IP |
Explicit non-goal: Hiding peer IP addresses. SRT is a direct peer-to-peer protocol — both sides must know each other’s public IP to connect. Punch facilitates this exchange. If IP privacy is required, a relay (future feature) masks both peers behind the relay’s IP.
Authentication
Session tokens
All API and WebSocket requests require a bearer token. Tokens are HMAC-SHA256 signed, base64url-encoded, and prefixed with p_.
Authorization: Bearer p_eyJzZXNzaW9uIjoiY2FtMSIsInJvbGUiOiJhZG1pbiIsImV4cCI6MTc0MTIzNH0.HMAC_SIGNATUREToken payload:
{ "session": "nab-floor-cam1", "role": "admin", "stream": null, "exp": 1741234567}| Field | Description |
|---|---|
session | Session ID this token is valid for |
role | admin or peer |
stream | Stream slot restriction (null = all streams) |
exp | Expiry timestamp (Unix seconds) |
Token lifecycle
- Session creation returns an
admintoken (full session control) - Admin generates peer tokens for individual operators (scoped to specific streams)
- Tokens embed in QR codes and session URLs for frictionless sharing
- Tokens expire with the session TTL (default 30 minutes, configurable)
Roles
| Permission | admin | peer |
|---|---|---|
| Create session | Yes | No |
| Close session | Yes | No |
| Generate peer tokens | Yes | No |
| Register as peer | Yes | Own stream only |
| View all stream health | Yes | No |
| View own stream health | Yes | Yes |
| Set tally state | Yes | No |
| Receive tally state | Yes | Yes |
Token security
- Signing key is stored as a Cloudflare Workers secret (
wrangler secret put PUNCH_SECRET) - Never stored in KV or Durable Object storage — validated statelessly via HMAC
- Short-lived by default — tokens expire with the session, limiting window of abuse
- Scoped by design — a peer token for
cam-widecannot accesscam-close
Encryption
SRT stream encryption
SRT encrypts payload data using AES in CTR mode. Punch manages this transparently:
- Session creation → Punch generates a 32-character random passphrase
- Peer registration → Punch sends the passphrase to each peer via authenticated WebSocket
- SRT connection → Both peers use the passphrase for AES key derivation (PBKDF2)
- Key rotation → SRT rotates derived session keys every 2^24 packets automatically
Passphrase characteristics:
| Property | Value |
|---|---|
| Length | 32 characters |
| Character set | Alphanumeric (avoids shell quoting issues) |
| Generation | crypto.getRandomValues() with rejection-free modular sampling |
| Distribution | TLS-only — over WSS to authenticated peers in peer messages, and over HTTPS in GET /api/session/:name/connect responses to admin tokens (so the dashboard can render copy-paste FFmpeg/OBS strings) |
| Storage | Durable Object memory (persisted in DO storage for hibernation recovery) |
Signalling encryption
All Punch communication is over HTTPS/WSS (TLS 1.3, managed by Cloudflare). The signalling channel itself is encrypted in transit.
| Channel | Encryption |
|---|---|
| HTTP API | TLS 1.3 (Cloudflare edge) |
| WebSocket | WSS (TLS 1.3) |
| SRT media | AES-128/256 (passphrase from Punch) |
Key separation
The SRT passphrase and the Punch authentication token are independent credentials:
- Punch token authenticates the peer to the signalling service
- SRT passphrase encrypts the media stream between peers
- Compromising one does not compromise the other
Access control patterns
Single operator (simplest)
Producer creates session → gets admin token → uses it everywhereOne person controls both ends. Admin token used for both registration and monitoring.
Multi-operator production
Producer creates session → admin token → generates peer token for cam-1 operator → scoped to "cam-wide" stream → generates peer token for cam-2 operator → scoped to "cam-close" stream → generates peer token for MCR receiver → scoped to all streams (read-only)Each operator gets a token that grants access only to their stream. The producer retains full control via the admin token.
QR code sharing
QR codes encode the session URL with an embedded peer token:
https://punch.thåst.se/s/nab-floor-cam1?t=p_eyJ...Security consideration: Anyone with the QR code can access the session. This is by design — the QR is the credential. For sensitive productions, use short TTLs and generate new tokens per stream slot.
Rate limiting
| Endpoint | Limit | Window | Purpose |
|---|---|---|---|
POST /api/session | 10 | per minute per IP | Prevent session spam |
GET /api/session/:id | 60 | per minute per IP | Prevent polling abuse |
| WebSocket connect | 20 | per minute per IP | Prevent connection flooding |
| WebSocket messages | 100 | per second per connection | Prevent message flooding |
Rate limits are enforced at the Worker edge before requests reach the Durable Object.
Session isolation
Each session runs in its own Durable Object instance:
- No shared state between sessions
- No cross-session access — tokens are scoped to a single session
- Independent TTL — one session expiring does not affect others
- Memory isolation — Durable Object instances run in separate V8 isolates
Operational security
Secret management
# Set the signing secret (do this once)wrangler secret put PUNCH_SECRET# Enter a random 64+ character string
# Rotate the secret (invalidates all existing tokens)wrangler secret put PUNCH_SECRET# Enter a new random string# All active sessions will require new tokensLogging
Punch logs:
- Session creation and closure (session ID, timestamp)
- Peer registration (session ID, peer role — not IP addresses in logs)
- Authentication failures (IP, endpoint, timestamp)
- Rate limit violations (IP, endpoint)
Punch does not log:
- SRT passphrases
- Token values
- Media content or metadata
- Peer IP addresses (except on auth failure)
Data retention
| Data | Retention | Storage |
|---|---|---|
| Session state | Until TTL expiry | DO memory (volatile) |
| Health metrics | Until TTL expiry | DO SQLite (ephemeral) |
| SRT passphrase | Until TTL expiry | DO memory (volatile) |
| Tokens | Stateless (HMAC) | Not stored |
| Logs | Cloudflare Workers default | Cloudflare (configurable) |
When a session expires, all associated data is deleted. There is no persistent record of past sessions unless explicitly configured.
Known limitations
IP address exposure
SRT requires direct peer-to-peer communication. Both peers learn each other’s public IP address through Punch. This is inherent to the protocol and cannot be mitigated without a relay.
Token in URL
QR-code URLs contain the authentication token as a query parameter. This means:
- The token appears in browser history
- The token may be logged by intermediate proxies (but is encrypted in transit via TLS)
- Screenshot sharing may leak tokens
Mitigation: tokens are short-lived and scoped to a single session. Expired tokens are useless.
No certificate-based authentication
SRT uses pre-shared key (passphrase) authentication only. There is no certificate chain, no mutual TLS, no PKI. This is a limitation of SRT itself, not Punch. RIST addresses this with certificate support — noted for users with strict security requirements.