Address the seven findings from the security & deployment review. High: - upload: make infer authoritative — reject files it can't identify (SVG/HTML/JS) and require the detected MIME to be on an ALLOWED_MEDIA allowlist; derive the stored MIME and on-disk extension from the detected type, ignoring client filename/Content-Type. Closes the stored-XSS vector via media served on-origin. - deploy: rename docker-compose.override.yml -> docker-compose.dev.yml so the default `docker compose up -d` no longer publishes Postgres 5432 to the host; the port map is now opt-in via -f. README updated. Medium: - upload: DefaultBodyLimit::disable() -> max(576 MiB) as an HTTP-level OOM backstop; handler still enforces precise per-class size limits. - docker: run backend and frontend as non-root users. Low: - social/upload: event-scope toggle_like, list_comments, add_comment, delete_comment, edit_upload, delete_upload via find_by_id_and_event / soft_delete_in_event — cross-event IDs now resolve to 404. - Caddy: site-wide HSTS / nosniff / X-Frame-Options / Referrer-Policy, plus Content-Disposition: attachment on /media/originals/*. - .env.example: replace default Postgres password with a CHANGE_ME hint. Out of scope: localStorage JWT (root cause fixed; httpOnly cookies are a larger change tracked separately). Verified: cargo build (no new warnings), cargo test (3 passed), caddy validate, docker compose config (no 5432 published by default). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
48 lines
3.2 KiB
Plaintext
48 lines
3.2 KiB
Plaintext
# ── Domain ────────────────────────────────────────────────────────────────────
|
||
# Public domain Caddy will serve and obtain a TLS certificate for.
|
||
DOMAIN=my-event.example.com
|
||
|
||
# ── App server ────────────────────────────────────────────────────────────────
|
||
APP_PORT=3000
|
||
|
||
# ── Database ──────────────────────────────────────────────────────────────────
|
||
# Set a strong password and keep it in sync between DATABASE_URL and
|
||
# POSTGRES_PASSWORD. Generate one with: openssl rand -hex 24
|
||
DATABASE_URL=postgres://eventsnap:CHANGE_ME_use_a_strong_password@db:5432/eventsnap
|
||
POSTGRES_USER=eventsnap
|
||
POSTGRES_PASSWORD=CHANGE_ME_use_a_strong_password
|
||
POSTGRES_DB=eventsnap
|
||
|
||
# ── Authentication ────────────────────────────────────────────────────────────
|
||
# Generate with: openssl rand -hex 64
|
||
JWT_SECRET=change_me_to_a_random_64_byte_hex_string
|
||
SESSION_EXPIRY_DAYS=30
|
||
|
||
# Admin dashboard password (bcrypt hash).
|
||
# Generate with: htpasswd -bnBC 12 "" yourpassword | tr -d ':\n'
|
||
ADMIN_PASSWORD_HASH=$2y$12$placeholder_replace_me
|
||
|
||
# ── Event ─────────────────────────────────────────────────────────────────────
|
||
EVENT_NAME=Max & Maria's Wedding
|
||
EVENT_SLUG=max-maria-2026
|
||
|
||
# ── Storage ───────────────────────────────────────────────────────────────────
|
||
MEDIA_PATH=/media
|
||
|
||
# ── Upload limits ─────────────────────────────────────────────────────────────
|
||
DEFAULT_MAX_IMAGE_SIZE_MB=20
|
||
DEFAULT_MAX_VIDEO_SIZE_MB=500
|
||
|
||
# ── Rate limiting ─────────────────────────────────────────────────────────────
|
||
DEFAULT_UPLOAD_RATE_PER_HOUR=10
|
||
DEFAULT_FEED_RATE_PER_MIN=60
|
||
DEFAULT_EXPORT_RATE_PER_DAY=3
|
||
|
||
# ── Capacity ──────────────────────────────────────────────────────────────────
|
||
DEFAULT_ESTIMATED_GUEST_COUNT=100
|
||
# Fraction of total storage that triggers the "low storage" warning (0.0–1.0)
|
||
DEFAULT_QUOTA_TOLERANCE=0.75
|
||
|
||
# ── Workers ───────────────────────────────────────────────────────────────────
|
||
COMPRESSION_WORKER_CONCURRENCY=2
|