openvibers/openvibe.sites Commits

Sites vhosts: security headers on every response again

OP OpenVibers committed 24 Sept 2026 · cb53d8471c · 18 files, +630 −72

nginx does not merge add_header lists: a location with its own add_header drops the server's. Every
Sites location set Cache-Control that way, so X-Content-Type-Options, X-Frame-Options, Referrer-Policy
and HSTS were missing from every page, robots.txt, sitemap, manifest and status.json the 16 domains
served (seen at the origin on 2026-09-24: `/` answered with Cache-Control only).

Each location that sets a header now repeats the four security headers (one list, SECURITY_HEADERS, in
build.js); the server-level copy stays for responses no location decorates (404, 403). Checked in
nginx 1.28.3: all four headers on /, /terms, /nope (404), robots.txt, sitemap.xml, the manifest and
status.json, with the same Cache-Control values as before.

test/build.test.js fails any generated location that sets a header without the four.

Changes

ddiff --git a/build.js b/build.js
iindex bcebe39..640243e 100644
---- a/build.js
++++ b/build.js
@@@ -317,8 +317,15 @@ a:first-child{background:var(--site);border-color:var(--site);color:#0b0d10}
}
/** nginx vhost: static root, wildcard cert, www → apex, long cache for the immutable bits, real 404s. */
++const SECURITY_HEADERS = [
++ 'add_header X-Content-Type-Options nosniff always;',
++ 'add_header X-Frame-Options SAMEORIGIN always;',
++ 'add_header Referrer-Policy strict-origin-when-cross-origin always;',
++ 'add_header Strict-Transport-Security "max-age=31536000" always;',
++];
function vhost(site) {
const d = site.domain;
++ const sec = SECURITY_HEADERS.join('\n ');
// Subdomains of a zone (e.g. events.openvibe.network) use the zone's wildcard cert and have no www.
const cert = site.zone || d;
const www = site.zone ? '' : `
@@@ -355,18 +362,32 @@ server {
access_log /var/log/nginx/${d}.access.log;
error_log /var/log/nginx/${d}.error.log;
-- add_header X-Content-Type-Options nosniff always;
-- add_header X-Frame-Options SAMEORIGIN always;
-- add_header Referrer-Policy strict-origin-when-cross-origin always;
-- add_header Strict-Transport-Security "max-age=31536000" always;
++ ${SECURITY_HEADERS.join('\n ')}
gzip on;
gzip_types text/html text/plain text/css application/json application/javascript application/xml image/svg+xml application/manifest+json;
-- location = /robots.txt { add_header Cache-Control "public, max-age=3600"; }
-- location = /sitemap.xml { add_header Cache-Control "public, max-age=3600"; types { application/xml xml; } }
-- location = /manifest.webmanifest { types { application/manifest+json webmanifest; } add_header Cache-Control "public, max-age=86400"; }
-- location = /status.json { add_header Cache-Control "public, max-age=600"; add_header Access-Control-Allow-Origin "*"; }
++ # An add_header inside a location replaces the server's list (nginx does not merge them), so every
++ # location that sets its own header repeats the security headers.
++ location = /robots.txt {
++ add_header Cache-Control "public, max-age=3600";
++ ${sec}
++ }
++ location = /sitemap.xml {
++ types { application/xml xml; }
++ add_header Cache-Control "public, max-age=3600";
++ ${sec}
++ }
++ location = /manifest.webmanifest {
++ types { application/manifest+json webmanifest; }
++ add_header Cache-Control "public, max-age=86400";
++ ${sec}
++ }
++ location = /status.json {
++ add_header Cache-Control "public, max-age=600";
++ add_header Access-Control-Allow-Origin "*";
++ ${sec}
++ }
# Only the files in dist/ exist; anything else is a real 404 with a page, never the front page.
error_page 404 /404.html;
@@@ -374,6 +395,7 @@ server {
location / {
add_header Cache-Control "public, max-age=600";
++ ${sec}
try_files $uri $uri.html $uri/ =404;
}
ddiff --git a/deploy/nginx/admin.openvibe.network.conf b/deploy/nginx/admin.openvibe.network.conf
iindex bd8e5ac..eaf35a7 100644
---- a/deploy/nginx/admin.openvibe.network.conf
++++ b/deploy/nginx/admin.openvibe.network.conf
@@@ -29,10 +29,39 @@ server {
gzip on;
gzip_types text/html text/plain text/css application/json application/javascript application/xml image/svg+xml application/manifest+json;
-- location = /robots.txt { add_header Cache-Control "public, max-age=3600"; }
-- location = /sitemap.xml { add_header Cache-Control "public, max-age=3600"; types { application/xml xml; } }
-- location = /manifest.webmanifest { types { application/manifest+json webmanifest; } add_header Cache-Control "public, max-age=86400"; }
-- location = /status.json { add_header Cache-Control "public, max-age=600"; add_header Access-Control-Allow-Origin "*"; }
++ # An add_header inside a location replaces the server's list (nginx does not merge them), so every
++ # location that sets its own header repeats the security headers.
++ location = /robots.txt {
++ add_header Cache-Control "public, max-age=3600";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
++ location = /sitemap.xml {
++ types { application/xml xml; }
++ add_header Cache-Control "public, max-age=3600";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
++ location = /manifest.webmanifest {
++ types { application/manifest+json webmanifest; }
++ add_header Cache-Control "public, max-age=86400";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
++ location = /status.json {
++ add_header Cache-Control "public, max-age=600";
++ add_header Access-Control-Allow-Origin "*";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
# Only the files in dist/ exist; anything else is a real 404 with a page, never the front page.
error_page 404 /404.html;
@@@ -40,6 +69,10 @@ server {
location / {
add_header Cache-Control "public, max-age=600";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
try_files $uri $uri.html $uri/ =404;
}
ddiff --git a/deploy/nginx/ai.openvibe.network.conf b/deploy/nginx/ai.openvibe.network.conf
iindex 00acd8a..1237fc7 100644
---- a/deploy/nginx/ai.openvibe.network.conf
++++ b/deploy/nginx/ai.openvibe.network.conf
@@@ -29,10 +29,39 @@ server {
gzip on;
gzip_types text/html text/plain text/css application/json application/javascript application/xml image/svg+xml application/manifest+json;
-- location = /robots.txt { add_header Cache-Control "public, max-age=3600"; }
-- location = /sitemap.xml { add_header Cache-Control "public, max-age=3600"; types { application/xml xml; } }
-- location = /manifest.webmanifest { types { application/manifest+json webmanifest; } add_header Cache-Control "public, max-age=86400"; }
-- location = /status.json { add_header Cache-Control "public, max-age=600"; add_header Access-Control-Allow-Origin "*"; }
++ # An add_header inside a location replaces the server's list (nginx does not merge them), so every
++ # location that sets its own header repeats the security headers.
++ location = /robots.txt {
++ add_header Cache-Control "public, max-age=3600";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
++ location = /sitemap.xml {
++ types { application/xml xml; }
++ add_header Cache-Control "public, max-age=3600";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
++ location = /manifest.webmanifest {
++ types { application/manifest+json webmanifest; }
++ add_header Cache-Control "public, max-age=86400";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
++ location = /status.json {
++ add_header Cache-Control "public, max-age=600";
++ add_header Access-Control-Allow-Origin "*";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
# Only the files in dist/ exist; anything else is a real 404 with a page, never the front page.
error_page 404 /404.html;
@@@ -40,6 +69,10 @@ server {
location / {
add_header Cache-Control "public, max-age=600";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
try_files $uri $uri.html $uri/ =404;
}
ddiff --git a/deploy/nginx/api.openvibe.network.conf b/deploy/nginx/api.openvibe.network.conf
iindex 1819459..c71cf3a 100644
---- a/deploy/nginx/api.openvibe.network.conf
++++ b/deploy/nginx/api.openvibe.network.conf
@@@ -29,10 +29,39 @@ server {
gzip on;
gzip_types text/html text/plain text/css application/json application/javascript application/xml image/svg+xml application/manifest+json;
-- location = /robots.txt { add_header Cache-Control "public, max-age=3600"; }
-- location = /sitemap.xml { add_header Cache-Control "public, max-age=3600"; types { application/xml xml; } }
-- location = /manifest.webmanifest { types { application/manifest+json webmanifest; } add_header Cache-Control "public, max-age=86400"; }
-- location = /status.json { add_header Cache-Control "public, max-age=600"; add_header Access-Control-Allow-Origin "*"; }
++ # An add_header inside a location replaces the server's list (nginx does not merge them), so every
++ # location that sets its own header repeats the security headers.
++ location = /robots.txt {
++ add_header Cache-Control "public, max-age=3600";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
++ location = /sitemap.xml {
++ types { application/xml xml; }
++ add_header Cache-Control "public, max-age=3600";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
++ location = /manifest.webmanifest {
++ types { application/manifest+json webmanifest; }
++ add_header Cache-Control "public, max-age=86400";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
++ location = /status.json {
++ add_header Cache-Control "public, max-age=600";
++ add_header Access-Control-Allow-Origin "*";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
# Only the files in dist/ exist; anything else is a real 404 with a page, never the front page.
error_page 404 /404.html;
@@@ -40,6 +69,10 @@ server {
location / {
add_header Cache-Control "public, max-age=600";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
try_files $uri $uri.html $uri/ =404;
}
ddiff --git a/deploy/nginx/auth.openvibe.network.conf b/deploy/nginx/auth.openvibe.network.conf
iindex 56648c8..5e73d2e 100644
---- a/deploy/nginx/auth.openvibe.network.conf
++++ b/deploy/nginx/auth.openvibe.network.conf
@@@ -29,10 +29,39 @@ server {
gzip on;
gzip_types text/html text/plain text/css application/json application/javascript application/xml image/svg+xml application/manifest+json;
-- location = /robots.txt { add_header Cache-Control "public, max-age=3600"; }
-- location = /sitemap.xml { add_header Cache-Control "public, max-age=3600"; types { application/xml xml; } }
-- location = /manifest.webmanifest { types { application/manifest+json webmanifest; } add_header Cache-Control "public, max-age=86400"; }
-- location = /status.json { add_header Cache-Control "public, max-age=600"; add_header Access-Control-Allow-Origin "*"; }
++ # An add_header inside a location replaces the server's list (nginx does not merge them), so every
++ # location that sets its own header repeats the security headers.
++ location = /robots.txt {
++ add_header Cache-Control "public, max-age=3600";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
++ location = /sitemap.xml {
++ types { application/xml xml; }
++ add_header Cache-Control "public, max-age=3600";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
++ location = /manifest.webmanifest {
++ types { application/manifest+json webmanifest; }
++ add_header Cache-Control "public, max-age=86400";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
++ location = /status.json {
++ add_header Cache-Control "public, max-age=600";
++ add_header Access-Control-Allow-Origin "*";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
# Only the files in dist/ exist; anything else is a real 404 with a page, never the front page.
error_page 404 /404.html;
@@@ -40,6 +69,10 @@ server {
location / {
add_header Cache-Control "public, max-age=600";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
try_files $uri $uri.html $uri/ =404;
}
ddiff --git a/deploy/nginx/openre.stream.conf b/deploy/nginx/openre.stream.conf
iindex 3a027f2..bf8e0f6 100644
---- a/deploy/nginx/openre.stream.conf
++++ b/deploy/nginx/openre.stream.conf
@@@ -39,10 +39,39 @@ server {
gzip on;
gzip_types text/html text/plain text/css application/json application/javascript application/xml image/svg+xml application/manifest+json;
-- location = /robots.txt { add_header Cache-Control "public, max-age=3600"; }
-- location = /sitemap.xml { add_header Cache-Control "public, max-age=3600"; types { application/xml xml; } }
-- location = /manifest.webmanifest { types { application/manifest+json webmanifest; } add_header Cache-Control "public, max-age=86400"; }
-- location = /status.json { add_header Cache-Control "public, max-age=600"; add_header Access-Control-Allow-Origin "*"; }
++ # An add_header inside a location replaces the server's list (nginx does not merge them), so every
++ # location that sets its own header repeats the security headers.
++ location = /robots.txt {
++ add_header Cache-Control "public, max-age=3600";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
++ location = /sitemap.xml {
++ types { application/xml xml; }
++ add_header Cache-Control "public, max-age=3600";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
++ location = /manifest.webmanifest {
++ types { application/manifest+json webmanifest; }
++ add_header Cache-Control "public, max-age=86400";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
++ location = /status.json {
++ add_header Cache-Control "public, max-age=600";
++ add_header Access-Control-Allow-Origin "*";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
# Only the files in dist/ exist; anything else is a real 404 with a page, never the front page.
error_page 404 /404.html;
@@@ -50,6 +79,10 @@ server {
location / {
add_header Cache-Control "public, max-age=600";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
try_files $uri $uri.html $uri/ =404;
}
ddiff --git a/deploy/nginx/openvibe.chat.conf b/deploy/nginx/openvibe.chat.conf
iindex 80614c6..898ff10 100644
---- a/deploy/nginx/openvibe.chat.conf
++++ b/deploy/nginx/openvibe.chat.conf
@@@ -39,10 +39,39 @@ server {
gzip on;
gzip_types text/html text/plain text/css application/json application/javascript application/xml image/svg+xml application/manifest+json;
-- location = /robots.txt { add_header Cache-Control "public, max-age=3600"; }
-- location = /sitemap.xml { add_header Cache-Control "public, max-age=3600"; types { application/xml xml; } }
-- location = /manifest.webmanifest { types { application/manifest+json webmanifest; } add_header Cache-Control "public, max-age=86400"; }
-- location = /status.json { add_header Cache-Control "public, max-age=600"; add_header Access-Control-Allow-Origin "*"; }
++ # An add_header inside a location replaces the server's list (nginx does not merge them), so every
++ # location that sets its own header repeats the security headers.
++ location = /robots.txt {
++ add_header Cache-Control "public, max-age=3600";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
++ location = /sitemap.xml {
++ types { application/xml xml; }
++ add_header Cache-Control "public, max-age=3600";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
++ location = /manifest.webmanifest {
++ types { application/manifest+json webmanifest; }
++ add_header Cache-Control "public, max-age=86400";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
++ location = /status.json {
++ add_header Cache-Control "public, max-age=600";
++ add_header Access-Control-Allow-Origin "*";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
# Only the files in dist/ exist; anything else is a real 404 with a page, never the front page.
error_page 404 /404.html;
@@@ -50,6 +79,10 @@ server {
location / {
add_header Cache-Control "public, max-age=600";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
try_files $uri $uri.html $uri/ =404;
}
ddiff --git a/deploy/nginx/openvibe.coupons.conf b/deploy/nginx/openvibe.coupons.conf
iindex def1e6d..9153120 100644
---- a/deploy/nginx/openvibe.coupons.conf
++++ b/deploy/nginx/openvibe.coupons.conf
@@@ -39,10 +39,39 @@ server {
gzip on;
gzip_types text/html text/plain text/css application/json application/javascript application/xml image/svg+xml application/manifest+json;
-- location = /robots.txt { add_header Cache-Control "public, max-age=3600"; }
-- location = /sitemap.xml { add_header Cache-Control "public, max-age=3600"; types { application/xml xml; } }
-- location = /manifest.webmanifest { types { application/manifest+json webmanifest; } add_header Cache-Control "public, max-age=86400"; }
-- location = /status.json { add_header Cache-Control "public, max-age=600"; add_header Access-Control-Allow-Origin "*"; }
++ # An add_header inside a location replaces the server's list (nginx does not merge them), so every
++ # location that sets its own header repeats the security headers.
++ location = /robots.txt {
++ add_header Cache-Control "public, max-age=3600";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
++ location = /sitemap.xml {
++ types { application/xml xml; }
++ add_header Cache-Control "public, max-age=3600";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
++ location = /manifest.webmanifest {
++ types { application/manifest+json webmanifest; }
++ add_header Cache-Control "public, max-age=86400";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
++ location = /status.json {
++ add_header Cache-Control "public, max-age=600";
++ add_header Access-Control-Allow-Origin "*";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
# Only the files in dist/ exist; anything else is a real 404 with a page, never the front page.
error_page 404 /404.html;
@@@ -50,6 +79,10 @@ server {
location / {
add_header Cache-Control "public, max-age=600";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
try_files $uri $uri.html $uri/ =404;
}
ddiff --git a/deploy/nginx/openvibe.deals.conf b/deploy/nginx/openvibe.deals.conf
iindex 4064c60..672f7ce 100644
---- a/deploy/nginx/openvibe.deals.conf
++++ b/deploy/nginx/openvibe.deals.conf
@@@ -39,10 +39,39 @@ server {
gzip on;
gzip_types text/html text/plain text/css application/json application/javascript application/xml image/svg+xml application/manifest+json;
-- location = /robots.txt { add_header Cache-Control "public, max-age=3600"; }
-- location = /sitemap.xml { add_header Cache-Control "public, max-age=3600"; types { application/xml xml; } }
-- location = /manifest.webmanifest { types { application/manifest+json webmanifest; } add_header Cache-Control "public, max-age=86400"; }
-- location = /status.json { add_header Cache-Control "public, max-age=600"; add_header Access-Control-Allow-Origin "*"; }
++ # An add_header inside a location replaces the server's list (nginx does not merge them), so every
++ # location that sets its own header repeats the security headers.
++ location = /robots.txt {
++ add_header Cache-Control "public, max-age=3600";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
++ location = /sitemap.xml {
++ types { application/xml xml; }
++ add_header Cache-Control "public, max-age=3600";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
++ location = /manifest.webmanifest {
++ types { application/manifest+json webmanifest; }
++ add_header Cache-Control "public, max-age=86400";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
++ location = /status.json {
++ add_header Cache-Control "public, max-age=600";
++ add_header Access-Control-Allow-Origin "*";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
# Only the files in dist/ exist; anything else is a real 404 with a page, never the front page.
error_page 404 /404.html;
@@@ -50,6 +79,10 @@ server {
location / {
add_header Cache-Control "public, max-age=600";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
try_files $uri $uri.html $uri/ =404;
}
ddiff --git a/deploy/nginx/openvibe.host.conf b/deploy/nginx/openvibe.host.conf
iindex d3ec19a..e194d51 100644
---- a/deploy/nginx/openvibe.host.conf
++++ b/deploy/nginx/openvibe.host.conf
@@@ -39,10 +39,39 @@ server {
gzip on;
gzip_types text/html text/plain text/css application/json application/javascript application/xml image/svg+xml application/manifest+json;
-- location = /robots.txt { add_header Cache-Control "public, max-age=3600"; }
-- location = /sitemap.xml { add_header Cache-Control "public, max-age=3600"; types { application/xml xml; } }
-- location = /manifest.webmanifest { types { application/manifest+json webmanifest; } add_header Cache-Control "public, max-age=86400"; }
-- location = /status.json { add_header Cache-Control "public, max-age=600"; add_header Access-Control-Allow-Origin "*"; }
++ # An add_header inside a location replaces the server's list (nginx does not merge them), so every
++ # location that sets its own header repeats the security headers.
++ location = /robots.txt {
++ add_header Cache-Control "public, max-age=3600";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
++ location = /sitemap.xml {
++ types { application/xml xml; }
++ add_header Cache-Control "public, max-age=3600";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
++ location = /manifest.webmanifest {
++ types { application/manifest+json webmanifest; }
++ add_header Cache-Control "public, max-age=86400";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
++ location = /status.json {
++ add_header Cache-Control "public, max-age=600";
++ add_header Access-Control-Allow-Origin "*";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
# Only the files in dist/ exist; anything else is a real 404 with a page, never the front page.
error_page 404 /404.html;
@@@ -50,6 +79,10 @@ server {
location / {
add_header Cache-Control "public, max-age=600";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
try_files $uri $uri.html $uri/ =404;
}
ddiff --git a/deploy/nginx/openvibe.news.conf b/deploy/nginx/openvibe.news.conf
iindex e6bd4cd..d2357e1 100644
---- a/deploy/nginx/openvibe.news.conf
++++ b/deploy/nginx/openvibe.news.conf
@@@ -39,10 +39,39 @@ server {
gzip on;
gzip_types text/html text/plain text/css application/json application/javascript application/xml image/svg+xml application/manifest+json;
-- location = /robots.txt { add_header Cache-Control "public, max-age=3600"; }
-- location = /sitemap.xml { add_header Cache-Control "public, max-age=3600"; types { application/xml xml; } }
-- location = /manifest.webmanifest { types { application/manifest+json webmanifest; } add_header Cache-Control "public, max-age=86400"; }
-- location = /status.json { add_header Cache-Control "public, max-age=600"; add_header Access-Control-Allow-Origin "*"; }
++ # An add_header inside a location replaces the server's list (nginx does not merge them), so every
++ # location that sets its own header repeats the security headers.
++ location = /robots.txt {
++ add_header Cache-Control "public, max-age=3600";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
++ location = /sitemap.xml {
++ types { application/xml xml; }
++ add_header Cache-Control "public, max-age=3600";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
++ location = /manifest.webmanifest {
++ types { application/manifest+json webmanifest; }
++ add_header Cache-Control "public, max-age=86400";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
++ location = /status.json {
++ add_header Cache-Control "public, max-age=600";
++ add_header Access-Control-Allow-Origin "*";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
# Only the files in dist/ exist; anything else is a real 404 with a page, never the front page.
error_page 404 /404.html;
@@@ -50,6 +79,10 @@ server {
location / {
add_header Cache-Control "public, max-age=600";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
try_files $uri $uri.html $uri/ =404;
}
ddiff --git a/deploy/nginx/openvibe.reviews.conf b/deploy/nginx/openvibe.reviews.conf
iindex 516499f..5b792b7 100644
---- a/deploy/nginx/openvibe.reviews.conf
++++ b/deploy/nginx/openvibe.reviews.conf
@@@ -39,10 +39,39 @@ server {
gzip on;
gzip_types text/html text/plain text/css application/json application/javascript application/xml image/svg+xml application/manifest+json;
-- location = /robots.txt { add_header Cache-Control "public, max-age=3600"; }
-- location = /sitemap.xml { add_header Cache-Control "public, max-age=3600"; types { application/xml xml; } }
-- location = /manifest.webmanifest { types { application/manifest+json webmanifest; } add_header Cache-Control "public, max-age=86400"; }
-- location = /status.json { add_header Cache-Control "public, max-age=600"; add_header Access-Control-Allow-Origin "*"; }
++ # An add_header inside a location replaces the server's list (nginx does not merge them), so every
++ # location that sets its own header repeats the security headers.
++ location = /robots.txt {
++ add_header Cache-Control "public, max-age=3600";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
++ location = /sitemap.xml {
++ types { application/xml xml; }
++ add_header Cache-Control "public, max-age=3600";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
++ location = /manifest.webmanifest {
++ types { application/manifest+json webmanifest; }
++ add_header Cache-Control "public, max-age=86400";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
++ location = /status.json {
++ add_header Cache-Control "public, max-age=600";
++ add_header Access-Control-Allow-Origin "*";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
# Only the files in dist/ exist; anything else is a real 404 with a page, never the front page.
error_page 404 /404.html;
@@@ -50,6 +79,10 @@ server {
location / {
add_header Cache-Control "public, max-age=600";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
try_files $uri $uri.html $uri/ =404;
}
ddiff --git a/deploy/nginx/openvibe.tips.conf b/deploy/nginx/openvibe.tips.conf
iindex bae8816..63c741d 100644
---- a/deploy/nginx/openvibe.tips.conf
++++ b/deploy/nginx/openvibe.tips.conf
@@@ -39,10 +39,39 @@ server {
gzip on;
gzip_types text/html text/plain text/css application/json application/javascript application/xml image/svg+xml application/manifest+json;
-- location = /robots.txt { add_header Cache-Control "public, max-age=3600"; }
-- location = /sitemap.xml { add_header Cache-Control "public, max-age=3600"; types { application/xml xml; } }
-- location = /manifest.webmanifest { types { application/manifest+json webmanifest; } add_header Cache-Control "public, max-age=86400"; }
-- location = /status.json { add_header Cache-Control "public, max-age=600"; add_header Access-Control-Allow-Origin "*"; }
++ # An add_header inside a location replaces the server's list (nginx does not merge them), so every
++ # location that sets its own header repeats the security headers.
++ location = /robots.txt {
++ add_header Cache-Control "public, max-age=3600";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
++ location = /sitemap.xml {
++ types { application/xml xml; }
++ add_header Cache-Control "public, max-age=3600";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
++ location = /manifest.webmanifest {
++ types { application/manifest+json webmanifest; }
++ add_header Cache-Control "public, max-age=86400";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
++ location = /status.json {
++ add_header Cache-Control "public, max-age=600";
++ add_header Access-Control-Allow-Origin "*";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
# Only the files in dist/ exist; anything else is a real 404 with a page, never the front page.
error_page 404 /404.html;
@@@ -50,6 +79,10 @@ server {
location / {
add_header Cache-Control "public, max-age=600";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
try_files $uri $uri.html $uri/ =404;
}
ddiff --git a/deploy/nginx/openvibe.trade.conf b/deploy/nginx/openvibe.trade.conf
iindex 642f34b..b0094b7 100644
---- a/deploy/nginx/openvibe.trade.conf
++++ b/deploy/nginx/openvibe.trade.conf
@@@ -39,10 +39,39 @@ server {
gzip on;
gzip_types text/html text/plain text/css application/json application/javascript application/xml image/svg+xml application/manifest+json;
-- location = /robots.txt { add_header Cache-Control "public, max-age=3600"; }
-- location = /sitemap.xml { add_header Cache-Control "public, max-age=3600"; types { application/xml xml; } }
-- location = /manifest.webmanifest { types { application/manifest+json webmanifest; } add_header Cache-Control "public, max-age=86400"; }
-- location = /status.json { add_header Cache-Control "public, max-age=600"; add_header Access-Control-Allow-Origin "*"; }
++ # An add_header inside a location replaces the server's list (nginx does not merge them), so every
++ # location that sets its own header repeats the security headers.
++ location = /robots.txt {
++ add_header Cache-Control "public, max-age=3600";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
++ location = /sitemap.xml {
++ types { application/xml xml; }
++ add_header Cache-Control "public, max-age=3600";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
++ location = /manifest.webmanifest {
++ types { application/manifest+json webmanifest; }
++ add_header Cache-Control "public, max-age=86400";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
++ location = /status.json {
++ add_header Cache-Control "public, max-age=600";
++ add_header Access-Control-Allow-Origin "*";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
# Only the files in dist/ exist; anything else is a real 404 with a page, never the front page.
error_page 404 /404.html;
@@@ -50,6 +79,10 @@ server {
location / {
add_header Cache-Control "public, max-age=600";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
try_files $uri $uri.html $uri/ =404;
}
ddiff --git a/deploy/nginx/openvibe.vip.conf b/deploy/nginx/openvibe.vip.conf
iindex 153c73e..4873f9f 100644
---- a/deploy/nginx/openvibe.vip.conf
++++ b/deploy/nginx/openvibe.vip.conf
@@@ -39,10 +39,39 @@ server {
gzip on;
gzip_types text/html text/plain text/css application/json application/javascript application/xml image/svg+xml application/manifest+json;
-- location = /robots.txt { add_header Cache-Control "public, max-age=3600"; }
-- location = /sitemap.xml { add_header Cache-Control "public, max-age=3600"; types { application/xml xml; } }
-- location = /manifest.webmanifest { types { application/manifest+json webmanifest; } add_header Cache-Control "public, max-age=86400"; }
-- location = /status.json { add_header Cache-Control "public, max-age=600"; add_header Access-Control-Allow-Origin "*"; }
++ # An add_header inside a location replaces the server's list (nginx does not merge them), so every
++ # location that sets its own header repeats the security headers.
++ location = /robots.txt {
++ add_header Cache-Control "public, max-age=3600";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
++ location = /sitemap.xml {
++ types { application/xml xml; }
++ add_header Cache-Control "public, max-age=3600";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
++ location = /manifest.webmanifest {
++ types { application/manifest+json webmanifest; }
++ add_header Cache-Control "public, max-age=86400";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
++ location = /status.json {
++ add_header Cache-Control "public, max-age=600";
++ add_header Access-Control-Allow-Origin "*";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
# Only the files in dist/ exist; anything else is a real 404 with a page, never the front page.
error_page 404 /404.html;
@@@ -50,6 +79,10 @@ server {
location / {
add_header Cache-Control "public, max-age=600";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
try_files $uri $uri.html $uri/ =404;
}
ddiff --git a/deploy/nginx/realtime.openvibe.network.conf b/deploy/nginx/realtime.openvibe.network.conf
iindex 44b09ed..5cecdba 100644
---- a/deploy/nginx/realtime.openvibe.network.conf
++++ b/deploy/nginx/realtime.openvibe.network.conf
@@@ -29,10 +29,39 @@ server {
gzip on;
gzip_types text/html text/plain text/css application/json application/javascript application/xml image/svg+xml application/manifest+json;
-- location = /robots.txt { add_header Cache-Control "public, max-age=3600"; }
-- location = /sitemap.xml { add_header Cache-Control "public, max-age=3600"; types { application/xml xml; } }
-- location = /manifest.webmanifest { types { application/manifest+json webmanifest; } add_header Cache-Control "public, max-age=86400"; }
-- location = /status.json { add_header Cache-Control "public, max-age=600"; add_header Access-Control-Allow-Origin "*"; }
++ # An add_header inside a location replaces the server's list (nginx does not merge them), so every
++ # location that sets its own header repeats the security headers.
++ location = /robots.txt {
++ add_header Cache-Control "public, max-age=3600";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
++ location = /sitemap.xml {
++ types { application/xml xml; }
++ add_header Cache-Control "public, max-age=3600";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
++ location = /manifest.webmanifest {
++ types { application/manifest+json webmanifest; }
++ add_header Cache-Control "public, max-age=86400";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
++ location = /status.json {
++ add_header Cache-Control "public, max-age=600";
++ add_header Access-Control-Allow-Origin "*";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
# Only the files in dist/ exist; anything else is a real 404 with a page, never the front page.
error_page 404 /404.html;
@@@ -40,6 +69,10 @@ server {
location / {
add_header Cache-Control "public, max-age=600";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
try_files $uri $uri.html $uri/ =404;
}
ddiff --git a/deploy/nginx/themes.openvibe.network.conf b/deploy/nginx/themes.openvibe.network.conf
iindex b41128f..231f7d3 100644
---- a/deploy/nginx/themes.openvibe.network.conf
++++ b/deploy/nginx/themes.openvibe.network.conf
@@@ -29,10 +29,39 @@ server {
gzip on;
gzip_types text/html text/plain text/css application/json application/javascript application/xml image/svg+xml application/manifest+json;
-- location = /robots.txt { add_header Cache-Control "public, max-age=3600"; }
-- location = /sitemap.xml { add_header Cache-Control "public, max-age=3600"; types { application/xml xml; } }
-- location = /manifest.webmanifest { types { application/manifest+json webmanifest; } add_header Cache-Control "public, max-age=86400"; }
-- location = /status.json { add_header Cache-Control "public, max-age=600"; add_header Access-Control-Allow-Origin "*"; }
++ # An add_header inside a location replaces the server's list (nginx does not merge them), so every
++ # location that sets its own header repeats the security headers.
++ location = /robots.txt {
++ add_header Cache-Control "public, max-age=3600";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
++ location = /sitemap.xml {
++ types { application/xml xml; }
++ add_header Cache-Control "public, max-age=3600";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
++ location = /manifest.webmanifest {
++ types { application/manifest+json webmanifest; }
++ add_header Cache-Control "public, max-age=86400";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
++ location = /status.json {
++ add_header Cache-Control "public, max-age=600";
++ add_header Access-Control-Allow-Origin "*";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
++ }
# Only the files in dist/ exist; anything else is a real 404 with a page, never the front page.
error_page 404 /404.html;
@@@ -40,6 +69,10 @@ server {
location / {
add_header Cache-Control "public, max-age=600";
++ add_header X-Content-Type-Options nosniff always;
++ add_header X-Frame-Options SAMEORIGIN always;
++ add_header Referrer-Policy strict-origin-when-cross-origin always;
++ add_header Strict-Transport-Security "max-age=31536000" always;
try_files $uri $uri.html $uri/ =404;
}
ddiff --git a/test/build.test.js b/test/build.test.js
iindex c5e8ab7..2eba1c5 100644
---- a/test/build.test.js
++++ b/test/build.test.js
@@@ -60,6 +60,14 @@ for (const site of catalog.sites) {
assert.ok(conf.includes('error_page 404 /404.html;'), `${d}: vhost answers 404 with /404.html`);
assert.ok(conf.includes('try_files $uri $uri.html $uri/ =404;'), `${d}: vhost try_files ends in =404`);
assert.doesNotMatch(conf, /try_files[^;]*\/index\.html;/, `${d}: vhost never falls back to the front page`);
++ // nginx drops the server-level add_header list in any location with its own add_header: each such
++ // location must repeat the security headers (they were missing from every Sites response until 2026-09-24).
++ for (const m of conf.matchAll(/location [^{]*\{((?:[^{}]|\{[^{}]*\})*)\}/g)) {
++ if (!/add_header/.test(m[1])) continue;
++ for (const h of ['X-Content-Type-Options', 'X-Frame-Options', 'Referrer-Policy', 'Strict-Transport-Security']) {
++ assert.ok(m[1].includes(`add_header ${h} `), `${d}: ${m[0].split('{')[0].trim()} repeats ${h}`);
++ }
++ }
}
// Labels come from facts (facts.json: each repository's STATUS.json + the Network registry), never a