Lint / Shell (shellcheck) (push) Successful in 20s
Lint / JS (eslint) (push) Successful in 13s
Lint / No secrets in webhook configs (push) Successful in 4s
Lint / Python (ruff) (push) Successful in 5s
Lint / Python deps (pip-audit) (push) Successful in 44s
Lint / Secret scan (gitleaks) (push) Successful in 8s
Web calls broke ("nobody can join calls from the web version"): 23ad133
(cinny #210) put the security-headers snippet, including the app CSP, on
every .html response. /public/element-call/index.html then carried
frame-ancestors 'none', so the browser refused to load it inside the app
("Content-Security-Policy ... frame-ancestors 'none'"). Desktop was fine (it
loads Element Call from its own bundle). Before #210 that page had no CSP.
A `location ^~ /public/element-call/` now serves it with
cinny-security-headers-framed.conf: the same headers minus the CSP
(X-Frame-Options SAMEORIGIN still limits framing to chat.lotusguild.org),
and the same caching (HTML no-cache, hashed assets 1 year). Applied live on
LXC 106 (backup sites-available/cinny.bak-ecframe-*), nginx -t ok; verified
from outside: no CSP on the call page, and the live site frames it and it
loads ("Element Call").
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
111 lines
4.0 KiB
Nginx Configuration File
111 lines
4.0 KiB
Nginx Configuration File
server {
|
|
listen 80;
|
|
listen [::]:80;
|
|
server_name chat.lotusguild.org;
|
|
|
|
# Brotli compression (better than gzip for modern browsers)
|
|
brotli on;
|
|
brotli_static on;
|
|
brotli_comp_level 6;
|
|
brotli_types text/plain text/css application/javascript application/json
|
|
image/svg+xml application/wasm font/woff2;
|
|
|
|
root /var/www/html;
|
|
server_tokens off;
|
|
client_max_body_size 50m;
|
|
|
|
limit_req zone=chat_limit burst=60 nodelay;
|
|
limit_conn chat_conn 25;
|
|
index index.html;
|
|
|
|
# Security headers (incl. CSP) — see the snippet
|
|
include snippets/cinny-security-headers.conf;
|
|
# HSTS: TLS terminates upstream (this server is listen 80), so this reaches
|
|
# the browser only if the front proxy passes upstream response headers
|
|
# through; otherwise set it at the TLS terminator. includeSubDomains covers
|
|
# all *.lotusguild.org (all HTTPS); `preload` is inert until submitted to
|
|
# hstspreload.org.
|
|
# Permissions-Policy: allow only what the app uses (self) — calls
|
|
# (camera/microphone/display-capture), location share (geolocation), sounds
|
|
# (autoplay), Element Call (fullscreen/encrypted-media) — and deny the rest.
|
|
|
|
# Block all source map files and dotfiles from public access
|
|
location ~* \.(js|css)\.map$ {
|
|
deny all;
|
|
return 404;
|
|
}
|
|
location ~ /\. {
|
|
deny all;
|
|
return 404;
|
|
}
|
|
location = /netlify.toml {
|
|
deny all;
|
|
return 404;
|
|
}
|
|
|
|
|
|
# Service worker must never be cached so updates are picked up immediately
|
|
location = /sw.js {
|
|
include snippets/cinny-security-headers.conf;
|
|
expires -1;
|
|
add_header Cache-Control "no-cache, no-store, must-revalidate" always;
|
|
}
|
|
|
|
# Bundled Element Call: framed by the app itself, so it must not carry the
|
|
# app CSP (frame-ancestors 'none' blocked every web call). Same caching as
|
|
# below: HTML never cached, hashed assets for a year.
|
|
location ^~ /public/element-call/ {
|
|
include snippets/cinny-security-headers-framed.conf;
|
|
location ~* \.html$ {
|
|
include snippets/cinny-security-headers-framed.conf;
|
|
expires -1;
|
|
add_header Cache-Control "no-cache, no-store, must-revalidate" always;
|
|
}
|
|
location ~* \.(?:js|css|woff2?|png|svg|ico|webp|wasm)$ {
|
|
include snippets/cinny-security-headers-framed.conf;
|
|
expires 1y;
|
|
add_header Cache-Control "public, immutable" always;
|
|
}
|
|
}
|
|
|
|
# Cache content-addressed static assets aggressively
|
|
location ~* \.(?:js|css|woff2?|png|svg|ico|webp)$ {
|
|
include snippets/cinny-security-headers.conf;
|
|
expires 1y;
|
|
add_header Cache-Control "public, immutable" always;
|
|
}
|
|
|
|
# Never cache HTML or JSON (index.html, config.json, manifest.json)
|
|
location ~* \.(json|html)$ {
|
|
include snippets/cinny-security-headers.conf;
|
|
expires -1;
|
|
add_header Cache-Control "no-cache, no-store, must-revalidate" always;
|
|
}
|
|
|
|
# [Gitea #155] PWA share target. The service worker normally answers this
|
|
# POST itself; if it isn't controlling the page yet, land on /share
|
|
# (the shared files are lost, but nothing 405s).
|
|
location = /share-target {
|
|
absolute_redirect off;
|
|
return 303 /share;
|
|
}
|
|
|
|
# Auto-deploy webhook — proxied to local webhook service
|
|
location = /hooks/lotus-deploy {
|
|
proxy_pass http://127.0.0.1:9001/hooks/lotus-deploy;
|
|
proxy_set_header Host $host;
|
|
proxy_read_timeout 300;
|
|
proxy_connect_timeout 5;
|
|
}
|
|
|
|
location / {
|
|
rewrite ^/config\.json$ /config.json break;
|
|
rewrite ^/manifest\.json$ /manifest.json break;
|
|
rewrite ^/sw\.js$ /sw.js break;
|
|
rewrite ^/pdf\.worker\.min\.js$ /pdf.worker.min.js break;
|
|
rewrite ^/public/(.*)$ /public/$1 break;
|
|
rewrite ^/assets/(.*)$ /assets/$1 break;
|
|
rewrite ^(.+)$ /index.html break;
|
|
}
|
|
}
|