openvibers/openvibe.network Commits

nginx: client address headers only from $remote_addr (realip). X-Forwarded-For was appended to and CF-Connecting-IP passed through from the client, so a request reaching the origin without Cloudflare (DNS-only host or the bare IP) chose the address the service bans and rate-limits by; the generator emits the same

OP OpenVibers committed 23 Sept 2026 · 9eeab69991 · 3 files, +21 −12


Changes

ddiff --git a/deploy/nginx/openvibe.network.conf b/deploy/nginx/openvibe.network.conf
iindex 492f362d..b9a7bccc 100644
---- a/deploy/nginx/openvibe.network.conf
++++ b/deploy/nginx/openvibe.network.conf
@@@ -63,7 +63,8 @@ server {
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
-- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
++ proxy_set_header X-Forwarded-For $remote_addr;
++ proxy_set_header CF-Connecting-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
}
@@@ -76,7 +77,8 @@ server {
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
-- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
++ proxy_set_header X-Forwarded-For $remote_addr;
++ proxy_set_header CF-Connecting-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
}
@@@ -84,6 +86,9 @@ server {
proxy_pass http://127.0.0.1:4000;
proxy_http_version 1.1;
proxy_set_header Host $host;
++ proxy_set_header X-Real-IP $remote_addr;
++ proxy_set_header X-Forwarded-For $remote_addr;
++ proxy_set_header CF-Connecting-IP $remote_addr;
# Shared browser modules are loaded by every OpenVibe site from here. The app sets
# Cache-Control: five minutes (+60s stale) by default, a year for a ?v= content-hash URL.
}
@@@ -95,7 +100,8 @@ server {
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
-- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
++ proxy_set_header X-Forwarded-For $remote_addr;
++ proxy_set_header CF-Connecting-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
}
@@@ -108,7 +114,8 @@ server {
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
-- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
++ proxy_set_header X-Forwarded-For $remote_addr;
++ proxy_set_header CF-Connecting-IP $remote_addr;
}
# ── Static / SPA ────────────────────────────────────────
@@@ -117,7 +124,8 @@ server {
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
-- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
++ proxy_set_header X-Forwarded-For $remote_addr;
++ proxy_set_header CF-Connecting-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
}
ddiff --git a/server/deploy/nginx-generator.js b/server/deploy/nginx-generator.js
iindex 3efcb02e..674458c9 100644
---- a/server/deploy/nginx-generator.js
++++ b/server/deploy/nginx-generator.js
@@@ -161,7 +161,6 @@ const DEFAULT_SERVICE_MAP = {
],
defaultLocationExtras: {
websocket: true,
-- cfConnectingIp: true,
headers: {
'Permissions-Policy': 'camera=*, microphone=*, display-capture=*',
},
@@@ -372,8 +371,13 @@ function generateProxyBlock(port, loc = {}) {
lines.push(`proxy_pass http://127.0.0.1:${port};`);
lines.push('proxy_http_version 1.1;');
lines.push('proxy_set_header Host $host;');
++ // Client address headers come only from $remote_addr (realip: the visitor when the peer is a
++ // Cloudflare edge, else the TCP peer). Appending to, or passing through, a client-sent
++ // X-Forwarded-For / CF-Connecting-IP lets anyone reaching the origin directly (DNS-only
++ // hosts, the bare IP) choose the address upstreams ban and rate-limit by.
lines.push('proxy_set_header X-Real-IP $remote_addr;');
-- lines.push('proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;');
++ lines.push('proxy_set_header X-Forwarded-For $remote_addr;');
++ lines.push('proxy_set_header CF-Connecting-IP $remote_addr;');
lines.push('proxy_set_header X-Forwarded-Proto $scheme;');
if (loc.websocket) {
@@@ -388,10 +392,6 @@ function generateProxyBlock(port, loc = {}) {
lines.push('chunked_transfer_encoding off;');
}
-- if (loc.cfConnectingIp) {
-- lines.push('proxy_set_header CF-Connecting-IP $http_cf_connecting_ip;');
-- }
--
if (loc.readTimeout) {
lines.push(`proxy_read_timeout ${loc.readTimeout};`);
}
ddiff --git a/test/nginx-generator-live.test.js b/test/nginx-generator-live.test.js
iindex 17cc0c9e..d14686a8 100644
---- a/test/nginx-generator-live.test.js
++++ b/test/nginx-generator-live.test.js
@@@ -20,7 +20,8 @@ const must = [
['gzip for proxied text', /gzip_proxied any;/],
['upload body size', /location ~ \^\/api\/vods\/\(upload\|clips\|stream\/\) \{[^}]*client_max_body_size 500m;/],
['websocket upgrade on catch-all', /location \/ \{[^}]*proxy_set_header Upgrade \$http_upgrade;/],
-- ['Cloudflare client IP forwarded', /proxy_set_header CF-Connecting-IP \$http_cf_connecting_ip;/],
++ ['client address from realip only', /proxy_set_header CF-Connecting-IP \$remote_addr;/],
++ ['no client-sent X-Forwarded-For appended', /^(?![\s\S]*proxy_add_x_forwarded_for)/],
['access log without query strings (tokens)', /log_format live_noquery [^;]*\$uri[^;]*;[\s\S]*access_log [^;]* live_noquery;/],
['capture permissions header', /Permissions-Policy "camera=\*, microphone=\*, display-capture=\*"/],
['OpenVibe.Chat locations (glob, before /api/)', /include \/opt\/openvibe\.chat\/deploy\/nginx\/\*\.locations\.conf;[\s\S]*location \/api\/ \{/],