openvibers/openvibe.live Commits

CORS: allow the openvibe.tools satellites

OP OpenVibers committed 19 Aug 2026 · 0604cc7b4e · 1 file, +24 −2

pastes.openvibe.tools hosts the paste UI and calls this API from the browser,
but the allowlist only carried openvibe.live, openvibe.network and
openvibe.games. Every request from there was rejected outright:

  [CORS] Rejected origin: "https://pastes.openvibe.tools"

which is why paste actions failed in the browser while the same requests
succeeded from curl — curl does not enforce CORS, so server-side testing looked
completely healthy.

Match the satellites by suffix rather than enumerating them, since new tool
subdomains get added over time. Parsed with URL() and checked on hostname +
https so a lookalike cannot slip through a naive endsWith on the raw origin —
verified that evil-openvibe.tools, openvibe.tools.evil.com and
http://pastes.openvibe.tools are all still blocked.

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

Changes

ddiff --git a/server/index.js b/server/index.js
iindex 7e5ef306..3351fd94 100644
---- a/server/index.js
++++ b/server/index.js
@@@ -157,6 +157,13 @@ function getAllowedOrigins() {
// openvibe.games game client calls cosmetics API cross-origin
allowed.add('https://openvibe.games');
++ // openvibe.tools + its satellites (pastes., dev., net., img., text., …) call this
++ // API from the browser — pastes.openvibe.tools hosts the paste UI and was being
++ // CORS-rejected outright, so every paste action from there failed. Subdomains are
++ // matched by suffix in isAllowedOrigin() rather than enumerated, since satellites
++ // are added over time; the apex is listed here for the exact-match fast path.
++ allowed.add('https://openvibe.tools');
++
if (config.nodeEnv !== 'production') {
[
'http://localhost:3000',
@@@ -235,11 +242,26 @@ app.use((req, res, next) => {
}
next();
});
++/**
++ * Origin check. Exact allowlist first, then a strict suffix rule for the openvibe.tools
++ * satellites. Parsed with URL() and matched on hostname + https so a lookalike such as
++ * https://evil-openvibe.tools or http://x.openvibe.tools cannot slip through a naive
++ * endsWith on the raw origin string.
++ */
++function isAllowedOrigin(origin) {
++ if (allowedOrigins.has(origin)) return true;
++ try {
++ const u = new URL(origin);
++ if (u.protocol !== 'https:') return false;
++ return u.hostname === 'openvibe.tools' || u.hostname.endsWith('.openvibe.tools');
++ } catch { return false; }
++}
++
app.use(cors({
origin(origin, callback) {
if (!origin) return callback(null, true);
-- if (allowedOrigins.has(origin)) return callback(null, true);
-- console.warn(`[CORS] Rejected origin: "${origin}" | allowed: ${[...allowedOrigins].join(', ')}`);
++ if (isAllowedOrigin(origin)) return callback(null, true);
++ console.warn(`[CORS] Rejected origin: "${origin}" | allowed: ${[...allowedOrigins].join(', ')} (+ *.openvibe.tools)`);
return callback(new Error('Origin not allowed by CORS'));
},
credentials: true,