Files
cinny/docker-nginx.conf
T
jaredandClaude Opus 5 09562061a4 ci: lockfile check, gitleaks, docker smoke job, renovate, shipped nginx security headers
- "Verify lockfile is in sync" (git diff --exit-code package-lock.json
  after npm ci) replaces the deleted GitHub lockfile workflow (#99).
- gitleaks 8.30.1 binary scan on push + PR with a small allowlist for
  the public homeserver/registry URLs (#95).
- docker job builds the image, runs it and asserts 200 + the security
  headers; continue-on-error until the runner is confirmed to have a
  Docker daemon (#93). .dockerignore keeps the context small.
- docker-nginx.conf now sends a CSP (frame-src allowlist matching
  videoEmbed.ts), frame-ancestors 'none', Referrer-Policy and nosniff —
  shipped config, verify against the live chat.lotusguild.org headers
  before adopting in prod nginx (#95, #44 shipped-config half).
- renovate.json + weekly renovate workflow for cinny and element-call;
  needs a RENOVATE_TOKEN secret (names starting GITEA_ are reserved) and
  stays continue-on-error until it exists (#94).
- e2e job appended for the Playwright smoke test (#90), continue-on-error
  until green on the runner.

Fixes #93
Fixes #94
Fixes #95

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
2026-09-13 00:56:23 -04:00

56 lines
3.7 KiB
Plaintext

server {
listen 80;
listen [::]:80;
# ── Gitea #95 / #44 — shipped image had no security headers at all.
# `always` so these are sent on error responses too, not just 200s.
#
# Content-Security-Policy, directive by directive:
# default-src 'self' baseline: same-origin unless a directive below opens it up
# script-src 'self' 'wasm-unsafe-eval'
# app code is same-origin only; 'wasm-unsafe-eval' is required
# for the wasm modules used for E2EE crypto and audio denoise
# style-src 'self' 'unsafe-inline'
# vanilla-extract (the app's CSS-in-JS) emits inline <style>,
# so 'unsafe-inline' is required — no remote stylesheets needed
# img-src * data: blob: avatars/media/previews come from whichever homeserver or
# media repo the user points the client at — not knowable
# ahead of time — plus data: URIs and blob: for local previews
# media-src * blob: same reasoning as img-src, for audio/video attachments
# connect-src * the Matrix homeserver is user-chosen at runtime, so this
# can't be pinned to a fixed origin
# worker-src 'self' blob: service worker + blob: web workers (crypto/denoise) are
# same-origin or created from in-memory blobs, never remote
# frame-src ... the rich link-preview embeds in
# src/app/utils/videoEmbed.ts, one entry per provider:
# YouTube, Vimeo, Dailymotion, Streamable, Twitch, Spotify,
# SoundCloud, Apple Music, Tidal, Mixcloud, Deezer,
# Instagram, Reddit, Bluesky, Loom, Kick, TikTok, Steam —
# plus 'self' (no first-party iframes today, cheap to allow)
# object-src 'none' no <object>/<embed> plugin content is used anywhere
# base-uri 'self' blocks a <base> tag injection from redirecting relative URLs
# frame-ancestors 'none' this app must never be framed by another site (clickjacking)
#
# Shipped config — verify against chat.lotusguild.org's live headers before
# enabling this in the production nginx config; this file is currently only
# exercised by the CI `docker` smoke-test job, not by the live deploy path.
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'wasm-unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src * data: blob:; media-src * blob:; connect-src *; worker-src 'self' blob:; frame-src 'self' https://www.youtube-nocookie.com https://player.vimeo.com https://geo.dailymotion.com https://streamable.com https://clips.twitch.tv https://player.twitch.tv https://open.spotify.com https://w.soundcloud.com https://embed.music.apple.com https://embed.tidal.com https://www.mixcloud.com https://widget.deezer.com https://www.instagram.com https://embed.reddit.com https://embed.bsky.app https://www.loom.com https://player.kick.com https://www.tiktok.com https://store.steampowered.com; object-src 'none'; base-uri 'self'; frame-ancestors 'none'" always;
add_header Referrer-Policy "no-referrer" always;
add_header X-Content-Type-Options "nosniff" always;
location / {
root /usr/share/nginx/html;
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;
}
}