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 browserOriginis granted only when that origin matches the one serving the request, or appears in the new optionalPUNCH_ALLOWED_ORIGINSbinding. Responses now carryVary: Origin. - WebSocket upgrades are refused with
403when a browser presents a foreignOrigin. 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-Streamis 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
ConnectionStringsResponseschema now matches the actual{ stream, role, for_peer_a, for_peer_b }shape, with explicitstreamandrolequery parameters and a sharedConnectionFormatMapdefinition - Protocol RFC §6.2 reframes the post-
registerserver response as a set of messages with no required ordering betweenpeerandsession, matching the implementation docs/protocol.mdnow usesexpiresAt(absolute Unix milliseconds) rather thanexpires, documents thelocalPortfield on thepeermessage, and adds the previously undocumentedready,start, andpeer_matchmessagesdocs/integration.mdswitches the FFmpeg example frompayloadsizetopkt_sizeto match Punch’s auto-generated strings and the connection-string test suitedocs/deployment.mdupdates the referencewrangler.tomlto the current shape —nodejs_compat_v2, inline[durable_objects]binding,new_sqlite_classesmigration,[alias]block for the Nodefsstub, and the Turnstile site-key variable- New
docs/quick-start.mdtask-oriented onboarding guide, synced intodocs.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
handleHealthandhandleStatusinSessionRoomnow enforce the same per-token stream scope ashandleRegister, 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.tscovers the full register / peer-match / ready / start cycle, admin observerpeer_matchemission, and per-token stream-scope enforcement onregisterandstatus
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
expiresAtaccounting - WebSocket keep-alive: server-side auto-pong via
setWebSocketAutoResponsecombined 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/healthfor 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 fromcrypto.getRandomValues X-Content-Type-Options: nosniffandReferrer-Policy: no-referreron all responses from the dashboard- Authentication-failure events emitted to structured logs for downstream alerting
- Cloudflare Turnstile CAPTCHA on session creation. Bypassed when
TURNSTILE_SECRETis unset to support local development
Changed
expiresAtis now stored as an absolute Unix-millisecond timestamp inSessionConfigrather than recomputed on each accesssrc/log.tsis the single source ofsession.closed; the Worker no longer duplicate-logs the event inhandleDeleteSession
Fixed
- TTL alarm is now cancelled with
deleteAlarm()beforedeleteAll(), eliminating the workerd warning about destroying an active alarm /api/healthacceptsHEADrequests, restoring compatibility with monitors that issueHEADrather thanGET