openvibers/openvibe.bench Commits

vhost as every public room's: open API with rate limits, /internal and /metrics closed, /mcp POST only

OP OpenVibers committed 16 days ago · 1dd03a85da · 1 file, +61 −53


Changes

ddiff --git a/deploy/nginx/bench.openvibe.services.conf b/deploy/nginx/bench.openvibe.services.conf
iindex 3fc6241..35b7dde 100644
---- a/deploy/nginx/bench.openvibe.services.conf
++++ b/deploy/nginx/bench.openvibe.services.conf
@@@ -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;
}
}