Skip to content

Changelog

All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

[Unreleased]

Security

  • The API no longer answers every origin with Access-Control-Allow-Origin: *. A request carrying a browser Origin is granted only when that origin matches the one serving the request, or appears in the new optional PUNCH_ALLOWED_ORIGINS binding. Responses now carry Vary: Origin.
  • WebSocket upgrades are refused with 403 when a browser presents a foreign Origin. A valid session token is no longer sufficient on its own: tokens travel in URLs and QR codes, and the socket carries session control.
  • X-Peer-Stream is now always asserted from the validated token. The Worker copies client headers when forwarding to the session room, so a token carrying no stream — which every admin token is — could previously supply its own value for the room to honour.

Changed

  • CORS is granted in exactly one place, on the way out of the Worker, so no handler can drift from the policy.

Requests without an Origin header are unaffected, so every documented terminal flow (curl, wscat, encoders) continues to work unchanged, as does the bundled dashboard, which is same-origin by construction. A third-party browser application calling this API cross-origin would now be refused; set PUNCH_ALLOWED_ORIGINS to permit it.

1.0.1 - 2026-05-03

Documentation reconciliation, a small security tightening, and the addition of WebSocket end-to-end test coverage. The WebSocket signalling protocol and HTTP API remain unchanged; clients written against v1.0.0 continue to work.

Documentation

  • OpenAPI ConnectionStringsResponse schema now matches the actual { stream, role, for_peer_a, for_peer_b } shape, with explicit stream and role query parameters and a shared ConnectionFormatMap definition
  • Protocol RFC §6.2 reframes the post-register server response as a set of messages with no required ordering between peer and session, matching the implementation
  • docs/protocol.md now uses expiresAt (absolute Unix milliseconds) rather than expires, documents the localPort field on the peer message, and adds the previously undocumented ready, start, and peer_match messages
  • docs/integration.md switches the FFmpeg example from payloadsize to pkt_size to match Punch’s auto-generated strings and the connection-string test suite
  • docs/deployment.md updates the reference wrangler.toml to the current shape — nodejs_compat_v2, inline [durable_objects] binding, new_sqlite_classes migration, [alias] block for the Node fs stub, and the Turnstile site-key variable
  • New docs/quick-start.md task-oriented onboarding guide, synced into docs.thåst.se/punch/quick-start/
  • README quick-start now leads with the UI flow and documents the Turnstile reality for programmatic callers
  • README known-limitations rewrite: explicit NAT framing (port-preserving works, symmetric needs TURN), and an explicit “operator URL = session-scoped admin token” threat-model note
  • README badges and test-count brought to current state (TypeScript 6.0, 113 tests)

Security

  • handleHealth and handleStatus in SessionRoom now enforce the same per-token stream scope as handleRegister, in line with RFC §9.1. Peer-scoped tokens can no longer write health or status for streams other than the one they are bound to

Tests

  • New test/websocket.test.ts covers the full register / peer-match / ready / start cycle, admin observer peer_match emission, and per-token stream-scope enforcement on register and status

1.0.0 - 2026-05-03

Initial public release of Punch — an SRT session brokerage service that runs on Cloudflare Workers. The first stable release. Subsequent versions will follow SemVer; the WebSocket signalling protocol and HTTP API are stable from v1.0.0.

Added

  • SRT session brokerage with peer rendezvous coordinate exchange
  • WebSocket signalling for encoder/decoder discovery and passphrase distribution
  • Per-session Durable Object with TTL-based lifecycle and absolute-time expiresAt accounting
  • WebSocket keep-alive: server-side auto-pong via setWebSocketAutoResponse combined with a client ping every 30 s
  • Dead-peer sweep on a 60 s alarm cadence with a 90 s inactivity threshold
  • Structured JSON logging for security and analytics events (session.created, peer.matched, session.closed, rate.limited, auth.failed, turnstile.failed)
  • HEAD /api/health for uptime monitoring tools that probe with HEAD by default
  • QR-coded session URLs and a minimal browser dashboard for producers
  • Rate limiting on session creation and join attempts

Security

  • Per-request CSP nonce with strict-dynamic, generated from crypto.getRandomValues
  • X-Content-Type-Options: nosniff and Referrer-Policy: no-referrer on all responses from the dashboard
  • Authentication-failure events emitted to structured logs for downstream alerting
  • Cloudflare Turnstile CAPTCHA on session creation. Bypassed when TURNSTILE_SECRET is unset to support local development

Changed

  • expiresAt is now stored as an absolute Unix-millisecond timestamp in SessionConfig rather than recomputed on each access
  • src/log.ts is the single source of session.closed; the Worker no longer duplicate-logs the event in handleDeleteSession

Fixed

  • TTL alarm is now cancelled with deleteAlarm() before deleteAll(), eliminating the workerd warning about destroying an active alarm
  • /api/health accepts HEAD requests, restoring compatibility with monitors that issue HEAD rather than GET