openvibers/openvibe.live Commits

WHIP: build the session Location on the host the client reached us on

OP OpenVibers committed 29 Aug 2026 · f5f3133488 · 2 files, +19 −2

Encoders keep the WHIP URL they were configured with (openvibe.live,
whip.openvibe.live) long after the advertised host moves to ingest.openvibe.live.
The resource URL was built from the configured public origin, so a session started
on the old host was told to PATCH/DELETE on the new one — a host the client may not
resolve or allowlist. Use the request's own host (nginx preserves it) and fall back
to the configured origin only when there is no usable Host header.

Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_017AyeSFu7qdLdR64Zv99QNm

Changes

ddiff --git a/server/streaming/whip-handler.js b/server/streaming/whip-handler.js
iindex 369be0de..5eddd055 100644
---- a/server/streaming/whip-handler.js
++++ b/server/streaming/whip-handler.js
@@@ -46,7 +46,16 @@ function normalizeOrigin(value) {
}
function buildWhipResourceUrl(req, streamId, resourceId) {
-- const baseUrl = normalizeOrigin(config.whip?.publicUrl || config.webrtc?.publicUrl || config.baseUrl) || `${req.protocol}://${req.get('host')}`;
++ // The session resource lives on the host the client just reached us on. Encoders keep
++ // the WHIP URL they were configured with (openvibe.live, whip.openvibe.live, …) long
++ // after the advertised host changes, and a Location on a different host would send
++ // their PATCH/DELETE somewhere they may not resolve or allowlist. Only fall back to
++ // the configured public origin when the request carries no usable Host.
++ const requestOrigin = (typeof req?.get === 'function' && req.get('host'))
++ ? normalizeOrigin(`${req.protocol || 'https'}://${req.get('host')}`)
++ : null;
++ const baseUrl = requestOrigin
++ || normalizeOrigin(config.whip?.publicUrl || config.webrtc?.publicUrl || config.baseUrl);
try {
return new URL(`/whip/${streamId}/${resourceId}`, baseUrl).toString();
} catch {
ddiff --git a/test/whip-handler.test.js b/test/whip-handler.test.js
iindex 3ffab3c2..9c795552 100644
---- a/test/whip-handler.test.js
++++ b/test/whip-handler.test.js
@@@ -49,7 +49,15 @@ const req = {
};
const headers = buildWhipResponseHeaders(req, '123', 'resource-abc');
--assert.strictEqual(headers.Location, 'http://localhost:3000/whip/123/resource-abc');
++// The session resource must live on the host the encoder reached us on, so configs that
++// still point at an older hostname (openvibe.live, whip.openvibe.live) keep working after
++// the advertised WHIP host moves.
++assert.strictEqual(headers.Location, 'https://whip.example.com/whip/123/resource-abc');
++// Without a usable Host header, fall back to the configured public origin.
++assert.strictEqual(
++ buildWhipResponseHeaders({ protocol: 'https', get: () => undefined }, '123', 'resource-abc').Location,
++ 'http://localhost:3000/whip/123/resource-abc'
++);
// A browser can only read the resource URL (and our error code) if both are exposed.
assert.strictEqual(headers['Access-Control-Expose-Headers'], 'Location, X-WHIP-ERROR');
assert.ok(!Object.prototype.hasOwnProperty.call(headers, 'Link'));