H1: auth extractor re-reads the live user row — trusts the DB role (not the
JWT claim) and rejects banned users mid-session. e2e proves a demoted
host loses powers and a banned host is locked out and can't self-unban.
H2: client_ip takes the right-most XFF hop (the one Caddy appends); spoofed
left-most entries are ignored, restoring IP-based throttles.
H3: Caddy excludes /api/v1/stream from `encode` so SSE isn't buffered.
H4: upload — quota increment + row insert + hashtag links now one txn.
H5: feed keyset pagination tiebroken on (created_at, id) + composite index
(migration 010); feed_delta bounded with LIMIT.
H7: LightboxModal keys its comment load off upload.id, ending the
SSE-driven refetch storm.
H8: CSP via SvelteKit kit.csp (svelte.config.js) hardens the localStorage
token model against injection.
Migration 010 also adds the latent comment/comment_hashtag indexes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
42 lines
1.6 KiB
Caddyfile
42 lines
1.6 KiB
Caddyfile
{$DOMAIN} {
|
|
# Compress everything EXCEPT the SSE stream — gzip buffering delays
|
|
# "real-time" likes/comments until the ~30s keep-alive tick.
|
|
@compressible not path /api/v1/stream
|
|
encode @compressible zstd gzip
|
|
|
|
# Site-wide security headers (defense-in-depth). HSTS is free since Caddy
|
|
# already terminates TLS. nosniff also covers all of /media/*.
|
|
header {
|
|
Strict-Transport-Security "max-age=31536000; includeSubDomains"
|
|
X-Content-Type-Options "nosniff"
|
|
X-Frame-Options "DENY"
|
|
Referrer-Policy "strict-origin-when-cross-origin"
|
|
}
|
|
|
|
# SvelteKit frontend — static assets with long-lived cache (content-hashed filenames)
|
|
@hashed_assets path_regexp hashed /_app/immutable/.*\.[a-f0-9]{8,}\.(js|css|woff2)$
|
|
header @hashed_assets Cache-Control "public, max-age=31536000, immutable"
|
|
|
|
# Media previews and thumbnails
|
|
@previews path /media/previews/* /media/thumbnails/*
|
|
header @previews Cache-Control "public, max-age=3600"
|
|
|
|
# Original media files (private — only host can download). Force download
|
|
# rather than inline rendering as defense-in-depth against any future
|
|
# content-type confusion (previews/thumbnails are re-encoded and stay inline).
|
|
@originals path /media/originals/*
|
|
header @originals Cache-Control "private, max-age=86400"
|
|
header @originals Content-Disposition "attachment"
|
|
|
|
# API — never cache
|
|
@api path /api/*
|
|
header @api Cache-Control "no-store"
|
|
|
|
# Route API and media requests to the Rust backend
|
|
reverse_proxy /api/* app:3000
|
|
reverse_proxy /media/* app:3000
|
|
|
|
# Everything else goes to SvelteKit frontend
|
|
reverse_proxy frontend:3001
|
|
}
|