openvibers/openvibe.events Commits

Deliveries carry only the v2 signature (shim C-60 retired). The v1 header, X-OpenVibe-Signature, was an HMAC of the body with no timestamp and so replayable, and it is no longer sent. Checked on 2026-09-28: every consumer in production verifies v2 (openvibe-sdk parseDelivery with requireV2, or verifyDeliveryV2: Live, Network, Media, Chat, Community, Games, Tools, Tips, VIP, News, Reviews, Deals, Trade and Search). The rotation overlap still signs v2 with both secrets. Tests assert there is no v1 header and verify v2. npm test 18/18.

OP OpenVibers committed 28 Sept 2026 · f55f7eb601 · 4 files, +13 −10


Changes

ddiff --git a/server/worker.js b/server/worker.js
iindex cf2f6ed..5108c18 100644
---- a/server/worker.js
++++ b/server/worker.js
@@@ -20,9 +20,12 @@
* most maxAppInflight to developer-app endpoints (so apps can never hold every slot).
*/
const crypto = require('crypto');
--const { sign, signV2 } = require('../lib/client');
++const { signV2 } = require('../lib/client');
/**
++ * Deliveries carry only the v2 signature: the v1 header (an HMAC of the body, no timestamp, so replayable) is no
++ * longer sent (shim C-60 retired 2026-09-28; every consumer verifies v2).
++ *
* The v2 header for a delivery. During a secret rotation's overlap it carries a second v2= signature made
* with the previous secret, so a consumer still configured with either secret accepts it (openvibe-sdk's
* verifyDeliveryV2 checks every v2= value).
@@@ -76,7 +79,6 @@ function createWorker({ store, config, clock = { now: () => Date.now() }, fetchI
'X-OpenVibe-Subscription-Id': sub.id,
'X-OpenVibe-Delivery-Attempt': String(attempt),
'X-OpenVibe-Hops': String(row.hops),
-- 'X-OpenVibe-Signature': sign(body, sub.secret),
'X-OpenVibe-Timestamp': String(timestamp),
'X-OpenVibe-Signature-V2': signatureV2(sub, body, timestamp, clock.now()),
traceparent: `00-${row.trace_id}-${crypto.randomBytes(8).toString('hex')}-01`,
ddiff --git a/test/apps.test.js b/test/apps.test.js
iindex 65d1e59..7a154b1 100644
---- a/test/apps.test.js
++++ b/test/apps.test.js
@@@ -4,7 +4,7 @@
const assert = require('assert');
const nodeHttp = require('http');
const { ids } = require('openvibe-contracts');
--const { verifyDelivery, verifyDeliveryV2 } = require('../lib/client');
++const { verifyDeliveryV2 } = require('../lib/client');
const apps = require('../server/apps');
const { createGuardedPost, isPublicAddress } = require('../server/egress');
const { load } = require('../server/config');
@@@ -248,8 +248,8 @@ t('delivery: signed, scoped, sandbox never meets production', async () => {
assert.deepStrictEqual(byPath('/b-project'), [], 'never another project');
assert.strictEqual(firstParty.calls.length, 0, 'first-party subscriptions never get sandbox events');
const a = seen.find(g => g.url === '/a');
-- assert.ok(verifyDelivery(a.rawBody, a.headers['x-openvibe-signature'], subA.secret), 'HMAC-signed with the subscription secret');
-- assert.ok(verifyDeliveryV2(a.rawBody, a.headers, subA.secret, { now: h.clock.now() }), 'and v2-signed (timestamped) like first-party deliveries');
++ assert.ok(verifyDeliveryV2(a.rawBody, a.headers, subA.secret, { now: h.clock.now() }), 'v2-signed (timestamped) with the subscription secret, like first-party deliveries');
++ assert.strictEqual(a.headers['x-openvibe-signature'], undefined, 'no v1 signature (C-60 retired)');
assert.strictEqual(JSON.parse(a.rawBody).event.source, apps.appSource(appA));
assert.ok(pinned.length && pinned.every(ip => ip === '127.0.0.1'), 'the socket connected to the address the guard checked');
ddiff --git a/test/delivery.test.js b/test/delivery.test.js
iindex 56407cd..b194eea 100644
---- a/test/delivery.test.js
++++ b/test/delivery.test.js
@@@ -95,7 +95,7 @@ t('SSRF: only http(s) endpoints on 127.0.0.1 or *.openvibe.* are accepted', asyn
assert.strictEqual(r.body.endpoint, 'http://127.0.0.1:9/x');
});
--t('success: signed POST, verified by verifyDelivery; trace propagated', async () => {
++t('success: signed POST (v2 only), verified by verifyDeliveryV2; trace propagated', async () => {
const stub = await subscriber();
const sub = (await subscribe(media, { topic_pattern: 'live.stream.*', endpoint: stub.url })).body;
const env = envelope('live', { trace_id: '4bf92f3577b34da6a3ce929d0e0e4736' });
@@@ -104,9 +104,10 @@ t('success: signed POST, verified by verifyDelivery; trace propagated', async ()
await h.worker.drain();
assert.strictEqual(stub.calls.length, 1);
const call = stub.calls[0];
-- assert.ok(verifyDelivery(call.rawBody, call.headers['x-openvibe-signature'], sub.secret));
-- assert.ok(!verifyDelivery(call.rawBody, call.headers['x-openvibe-signature'], 'whsec_wrong_secret_wrong_secret_wrong'));
-- assert.ok(!verifyDelivery(Buffer.concat([call.rawBody, Buffer.from(' ')]), call.headers['x-openvibe-signature'], sub.secret));
++ assert.strictEqual(call.headers['x-openvibe-signature'], undefined, 'no v1 signature any more (C-60 retired)');
++ assert.ok(verifyDeliveryV2(call.rawBody, call.headers, sub.secret, { now: h.clock.now() }));
++ assert.ok(!verifyDeliveryV2(call.rawBody, call.headers, 'whsec_wrong_secret_wrong_secret_wrong', { now: h.clock.now() }));
++ assert.ok(!verifyDeliveryV2(Buffer.concat([call.rawBody, Buffer.from(' ')]), call.headers, sub.secret, { now: h.clock.now() }));
const ts = Number(call.headers['x-openvibe-timestamp']);
assert.strictEqual(ts, Math.floor(h.clock.now() / 1000), 'X-OpenVibe-Timestamp is the attempt time in unix seconds');
assert.match(call.headers['x-openvibe-signature-v2'], new RegExp(`^t=${ts},v2=[0-9a-f]{64}$`));
ddiff --git a/test/outbox-inbox.test.js b/test/outbox-inbox.test.js
iindex 4367813..d7eccd5 100644
---- a/test/outbox-inbox.test.js
++++ b/test/outbox-inbox.test.js
@@@ -132,7 +132,7 @@ t('consumer crash mid-processing + redelivery + replay -> exactly one effect (cr
let secret = null;
let mode = 'crash-after-commit';
const stub = await subscriber((call) => {
-- if (!events.verifyDelivery(call.rawBody, call.headers['x-openvibe-signature'], secret)) return 401;
++ if (!events.verifyDeliveryV2(call.rawBody, call.headers, secret, { now: h.clock.now() })) return 401;
const ev = call.body.event;
if (mode === 'crash-before-commit') {
mode = 'ok';