openvibers/openvibe.bench Commits
vhost as every public room's: open API with rate limits, /internal and /metrics closed, /mcp POST only
Changes
@@@ -1,23 +1,19 @@## ═══════════════════════════════════════════════════════════════--## bench.openvibe.services — Nginx Configuration (REFERENCE)++## bench.openvibe.services — Nginx configuration (REFERENCE)##--## Deployed to /etc/nginx/sites-available/bench.openvibe.services.conf — keep them in sync, and reload--## nginx after changing it. It uses the *.openvibe.services certificate the host already holds--## (/etc/letsencrypt/live/openvibe.services/), the same wildcard AI uses.++## Deployed to /etc/nginx/sites-available/bench.openvibe.services.conf (ov-vhost-install) — keep them in sync and reload++## nginx after changing it. The Node app (port 5230, systemd unit openvibe-bench) renders every page and serves++## the API; nginx terminates TLS, redirects www → apex, rate-limits sign-in, the API and the forms, and compresses++## text.##--## The Node service (port 5230, systemd unit openvibe-bench) serves:--## / the product home for a browser--## /docs the Bench API docs (server-rendered, no sign-in)--## /shared/* the home's OpenVibe Frame files; /robots.txt, /sitemap.xml, /llms.txt, /llms-full.txt--## /api/health, /api/ready--## /auth/* Network sign-in (redirect URI https://bench.openvibe.services/auth/callback, OAuth--## client `bench`)--## /mcp the Bench MCP endpoint (POST)--## /api/v1/* the service API: service-to-service with first-party service tokens. Services on this--## host call 127.0.0.1:5230 directly, so the public vhost refuses it.--## /metrics Prometheus text, answered only to direct loopback callers (never proxied)++## No proxy_cache: the app decides cacheability per response (public for the public pages and entries; private,++## no-store for anything drawn for a signed-in person). Never log request bodies here.## ═══════════════════════════════════════════════════════════════++limit_req_zone $binary_remote_addr zone=bench_auth:10m rate=10r/m;++limit_req_zone $binary_remote_addr zone=bench_api:10m rate=5r/s;++limit_req_zone $binary_remote_addr zone=bench_forms:10m rate=1r/s;++server {listen 80;listen [::]:80;@@@ -27,6 +23,7 @@ server {location / { return 301 https://bench.openvibe.services$request_uri; }}++server {listen 443 ssl;listen [::]:443 ssl;@@@ -40,66 +37,77 @@ server {access_log /var/log/nginx/bench.openvibe.services.access.log;error_log /var/log/nginx/bench.openvibe.services.error.log;-- # Task prompts, entry payloads and MCP bodies are the only request bodies Bench accepts.-- client_max_body_size 64k;++ # The API's JSON body cap is 64 KB; leave room for the headers.++ client_max_body_size 128k;add_header X-Content-Type-Options nosniff always;-- add_header Referrer-Policy strict-origin-when-cross-origin always;add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;-- location = /api/health { proxy_pass http://127.0.0.1:5230; 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; }-- location = /api/ready { proxy_pass http://127.0.0.1:5230; 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; }++ gzip on;++ gzip_proxied any;++ gzip_vary on;++ gzip_comp_level 5;++ gzip_min_length 1024;++ gzip_types text/css application/javascript text/javascript application/json application/problem+json image/svg+xml text/plain application/xml;-- # Metrics are for host-local scrapers only (http://127.0.0.1:5230/metrics).-- location ~* ^/metrics(/|$) { return 404; }++ 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;++ proxy_set_header X-Forwarded-Proto $scheme;-- # Service API: host-local only (see header).-- location /api/ { return 403; }++ # ── Network SSO session endpoints ───────────────────────++ # The navbar's session probe runs on every page view: it gets the API's budget, not sign-in's.++ location = /auth/me {++ limit_req zone=bench_api burst=30 nodelay;++ limit_req_status 429;++ proxy_pass http://127.0.0.1:5230;++ }-- # Network sign-in and the account-data loopback are handled by the app; the app refuses the loopback path.location /auth/ {++ limit_req zone=bench_auth burst=10 nodelay;++ limit_req_status 429;++ proxy_pass http://127.0.0.1:5230;++ }++++ # ── API ─────────────────────────────────────────────────++ location /api/ {++ limit_req zone=bench_api burst=20 nodelay;++ limit_req_status 429;proxy_pass http://127.0.0.1:5230;-- 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;-- proxy_set_header X-Forwarded-Proto $scheme;}++ # ── Forms and other writes: the vote, the caption generator, the report ──location = /mcp {limit_except POST { deny all; }-- proxy_pass http://127.0.0.1:5230;++ limit_req zone=bench_api burst=20 nodelay;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;++ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header X-Forwarded-Proto $scheme;++ proxy_pass http://127.0.0.1:5230;}-- location = /robots.txt { proxy_pass http://127.0.0.1:5230; 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; }-- location = /sitemap.xml { proxy_pass http://127.0.0.1:5230; 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; }-- location = /llms.txt { proxy_pass http://127.0.0.1:5230; 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; }-- location = /llms-full.txt { proxy_pass http://127.0.0.1:5230; 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; }-- location = /release.json { proxy_pass http://127.0.0.1:5230; 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; }-- location /shared/ { proxy_pass http://127.0.0.1:5230; 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; }---- location = / {++ location ~ ^/(evals|rounds/[A-Za-z0-9_-]+/vote)$ {++ limit_req zone=bench_forms burst=10 nodelay;++ limit_req_status 429;proxy_pass http://127.0.0.1:5230;-- 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;}-- # Everything else: the app answers GET/HEAD (its /docs, /favicon.ico, and its own 404 page); other methods-- # are refused here.++ # ── Prometheus metrics: host-local scrapers only (http://127.0.0.1:5230/metrics) ──++ location ~* ^/metrics(/|$) { return 404; }++++ # ── OpenVibe.Events' account delivery route: loopback only (POST /internal/events) ──++ # Events posts to http://127.0.0.1:5230/internal/events directly. Never proxied from outside (ADR-033).++ location ~* ^/internal(/|$) { return 404; }++++ # ── Pages, docs, robots.txt, sitemap, static assets ─────location / {-- limit_except GET HEAD { deny all; }proxy_pass http://127.0.0.1:5230;-- 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;-- proxy_set_header X-Forwarded-Proto $scheme;++ }++++ location ~ /\.(?!well-known) {++ deny all;}}
@@ -1,23 +1,19 @@
@@ -1,23 +1,19 @@
## ═══════════════════════════════════════════════════════════════
## ═══════════════════════════════════════════════════════════════
-## bench.openvibe.services — Nginx Configuration (REFERENCE)
+## bench.openvibe.services — Nginx configuration (REFERENCE)
##
##
-## Deployed to /etc/nginx/sites-available/bench.openvibe.services.conf — keep them in sync, and reload
-## nginx after changing it. It uses the *.openvibe.services certificate the host already holds
-## (/etc/letsencrypt/live/openvibe.services/), the same wildcard AI uses.
+## Deployed to /etc/nginx/sites-available/bench.openvibe.services.conf (ov-vhost-install) — keep them in sync and reload
+## nginx after changing it. The Node app (port 5230, systemd unit openvibe-bench) renders every page and serves
+## the API; nginx terminates TLS, redirects www → apex, rate-limits sign-in, the API and the forms, and compresses
+## text.
##
##
-## The Node service (port 5230, systemd unit openvibe-bench) serves:
-## / the product home for a browser
-## /docs the Bench API docs (server-rendered, no sign-in)
-## /shared/* the home's OpenVibe Frame files; /robots.txt, /sitemap.xml, /llms.txt, /llms-full.txt
-## /api/health, /api/ready
-## /auth/* Network sign-in (redirect URI https://bench.openvibe.services/auth/callback, OAuth
-## client `bench`)
-## /mcp the Bench MCP endpoint (POST)
-## /api/v1/* the service API: service-to-service with first-party service tokens. Services on this
-## host call 127.0.0.1:5230 directly, so the public vhost refuses it.
-## /metrics Prometheus text, answered only to direct loopback callers (never proxied)
+## No proxy_cache: the app decides cacheability per response (public for the public pages and entries; private,
+## no-store for anything drawn for a signed-in person). Never log request bodies here.
## ═══════════════════════════════════════════════════════════════
## ═══════════════════════════════════════════════════════════════
+limit_req_zone $binary_remote_addr zone=bench_auth:10m rate=10r/m;
+limit_req_zone $binary_remote_addr zone=bench_api:10m rate=5r/s;
+limit_req_zone $binary_remote_addr zone=bench_forms:10m rate=1r/s;
+
server {
server {
listen 80;
listen 80;
listen [::]:80;
listen [::]:80;
@@ -27,6 +23,7 @@ server {
@@ -27,6 +23,7 @@ server {
location / { return 301 https://bench.openvibe.services$request_uri; }
location / { return 301 https://bench.openvibe.services$request_uri; }
}
}
+
server {
server {
listen 443 ssl;
listen 443 ssl;
listen [::]:443 ssl;
listen [::]:443 ssl;
@@ -40,66 +37,77 @@ server {
@@ -40,66 +37,77 @@ server {
access_log /var/log/nginx/bench.openvibe.services.access.log;
access_log /var/log/nginx/bench.openvibe.services.access.log;
error_log /var/log/nginx/bench.openvibe.services.error.log;
error_log /var/log/nginx/bench.openvibe.services.error.log;
- # Task prompts, entry payloads and MCP bodies are the only request bodies Bench accepts.
- client_max_body_size 64k;
+ # The API's JSON body cap is 64 KB; leave room for the headers.
+ client_max_body_size 128k;
add_header X-Content-Type-Options nosniff always;
add_header X-Content-Type-Options nosniff always;
- add_header Referrer-Policy strict-origin-when-cross-origin always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
- location = /api/health { proxy_pass http://127.0.0.1:5230; 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; }
- location = /api/ready { proxy_pass http://127.0.0.1:5230; 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; }
+ gzip on;
+ gzip_proxied any;
+ gzip_vary on;
+ gzip_comp_level 5;
+ gzip_min_length 1024;
+ gzip_types text/css application/javascript text/javascript application/json application/problem+json image/svg+xml text/plain application/xml;
- # Metrics are for host-local scrapers only (http://127.0.0.1:5230/metrics).
- location ~* ^/metrics(/|$) { return 404; }
+ 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;
+ proxy_set_header X-Forwarded-Proto $scheme;
- # Service API: host-local only (see header).
- location /api/ { return 403; }
+ # ── Network SSO session endpoints ───────────────────────
+ # The navbar's session probe runs on every page view: it gets the API's budget, not sign-in's.
+ location = /auth/me {
+ limit_req zone=bench_api burst=30 nodelay;
+ limit_req_status 429;
+ proxy_pass http://127.0.0.1:5230;
+ }
- # Network sign-in and the account-data loopback are handled by the app; the app refuses the loopback path.
location /auth/ {
location /auth/ {
+ limit_req zone=bench_auth burst=10 nodelay;
+ limit_req_status 429;
+ proxy_pass http://127.0.0.1:5230;
+ }
+
+ # ── API ─────────────────────────────────────────────────
+ location /api/ {
+ limit_req zone=bench_api burst=20 nodelay;
+ limit_req_status 429;
proxy_pass http://127.0.0.1:5230;
proxy_pass http://127.0.0.1:5230;
- 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;
- proxy_set_header X-Forwarded-Proto $scheme;
}
}
+ # ── Forms and other writes: the vote, the caption generator, the report ──
location = /mcp {
location = /mcp {
limit_except POST { deny all; }
limit_except POST { deny all; }
- proxy_pass http://127.0.0.1:5230;
+ limit_req zone=bench_api burst=20 nodelay;
proxy_set_header Host $host;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Real-IP $remote_addr;
- proxy_set_header X-Forwarded-For $remote_addr;
- proxy_set_header CF-Connecting-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Proto $scheme;
+ proxy_pass http://127.0.0.1:5230;
}
}
- location = /robots.txt { proxy_pass http://127.0.0.1:5230; 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; }
- location = /sitemap.xml { proxy_pass http://127.0.0.1:5230; 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; }
- location = /llms.txt { proxy_pass http://127.0.0.1:5230; 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; }
- location = /llms-full.txt { proxy_pass http://127.0.0.1:5230; 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; }
- location = /release.json { proxy_pass http://127.0.0.1:5230; 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; }
- location /shared/ { proxy_pass http://127.0.0.1:5230; 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; }
-
- location = / {
+ location ~ ^/(evals|rounds/[A-Za-z0-9_-]+/vote)$ {
+ limit_req zone=bench_forms burst=10 nodelay;
+ limit_req_status 429;
proxy_pass http://127.0.0.1:5230;
proxy_pass http://127.0.0.1:5230;
- 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;
}
}
- # Everything else: the app answers GET/HEAD (its /docs, /favicon.ico, and its own 404 page); other methods
- # are refused here.
+ # ── Prometheus metrics: host-local scrapers only (http://127.0.0.1:5230/metrics) ──
+ location ~* ^/metrics(/|$) { return 404; }
+
+ # ── OpenVibe.Events' account delivery route: loopback only (POST /internal/events) ──
+ # Events posts to http://127.0.0.1:5230/internal/events directly. Never proxied from outside (ADR-033).
+ location ~* ^/internal(/|$) { return 404; }
+
+ # ── Pages, docs, robots.txt, sitemap, static assets ─────
location / {
location / {
- limit_except GET HEAD { deny all; }
proxy_pass http://127.0.0.1:5230;
proxy_pass http://127.0.0.1:5230;
- 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;
- proxy_set_header X-Forwarded-Proto $scheme;
+ }
+
+ location ~ /\.(?!well-known) {
+ deny all;
}
}
}
}