bd3ef269d4
Remediates the rfc-app application + deploy-config findings from the Session 0026 security audit. Cut as the "v0.25.0-security-hardening" branch (from v0.24.0); reversioned to 0.27.0 on rebase onto main since v0.26.0 (#28) shipped while this was in flight. - C1 (Critical): single sanitizeHtml.js chokepoint (DOMPurify) for every marked→innerHTML / dangerouslySetInnerHTML sink (MarkdownPreview, ProposalView x2, Editor); rel=noopener hook on target=_blank links. - H1: per-account OTC-verify lockout (migration 023, auto-applied) + per-IP throttle via new ratelimit.py; wired on otc verify/request + passcode check/verify. - M1: device_trust.lookup() single indexed-row read — cookie value is now "<row_id>.<raw_token>"; bcrypt-checks one row, not a global table scan. (Behavior change: existing device-trust cookies re-prompt once.) - M2: HTTP security headers (CSP/HSTS/XFO/XCTO/Referrer-Policy) at nginx. - M4: session cookie Secure-by-default (SESSION_COOKIE_SECURE opt-out). - M5: bounce webhook fails CLOSED (503) when secret unset, instead of open; RFC_APP_INSECURE_BOUNCE_WEBHOOK=1 dev opt-in. - L2/L3: per-IP cooldown + check-endpoint throttle. - L4: systemd sandbox knobs. L8/I1: nginx server_tokens off + TLS1.0/1.1 out. VERSION + frontend/package.json → 0.27.0; CHANGELOG documents the upgrade steps (incl. the out-of-band nginx + systemd apply, which the flotilla deploy gesture does not perform). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
74 lines
2.2 KiB
Desktop File
74 lines
2.2 KiB
Desktop File
# systemd unit for the FastAPI process.
|
|
#
|
|
# Install:
|
|
# sudo cp deploy/systemd/rfc-app.service /etc/systemd/system/
|
|
# sudo systemctl daemon-reload
|
|
# sudo systemctl enable --now rfc-app
|
|
# sudo systemctl status rfc-app
|
|
#
|
|
# Logs:
|
|
# sudo journalctl -u rfc-app -f
|
|
#
|
|
# Per §4.2 the app is intentionally single-process — one uvicorn
|
|
# worker, colocated SQLite. If the deployment ever needs more than one
|
|
# worker, the spec calls for a planned migration to Postgres first;
|
|
# raising `--workers` here would break the WAL-mode SQLite invariant.
|
|
|
|
[Unit]
|
|
Description=Wiggleverse RFC app
|
|
After=network.target
|
|
Documentation=https://git.wiggleverse.org/ben.stull/rfc-app
|
|
|
|
[Service]
|
|
Type=simple
|
|
User=rfc-app
|
|
Group=rfc-app
|
|
WorkingDirectory=/opt/rfc-app/backend
|
|
EnvironmentFile=/opt/rfc-app/backend/.env
|
|
ExecStart=/opt/rfc-app/backend/.venv/bin/uvicorn app.main:app \
|
|
--host 127.0.0.1 \
|
|
--port 8000 \
|
|
--proxy-headers \
|
|
--forwarded-allow-ips 127.0.0.1
|
|
Restart=on-failure
|
|
RestartSec=5s
|
|
|
|
# Hardening — modest defaults; tighten further if the host runs other
|
|
# services. The bot wrapper writes only to its own data dir; everything
|
|
# else is read-only.
|
|
NoNewPrivileges=true
|
|
ProtectSystem=strict
|
|
ProtectHome=true
|
|
PrivateTmp=true
|
|
ReadWritePaths=/opt/rfc-app/backend/data
|
|
|
|
# v0.25.0 security hardening (audit 0026 L4) — defense-in-depth.
|
|
# The service binds 127.0.0.1:8000 and runs plain CPython
|
|
# (FastAPI/uvicorn + sqlite + bcrypt + httpx), so it needs no
|
|
# capabilities and no exotic syscalls.
|
|
CapabilityBoundingSet=
|
|
AmbientCapabilities=
|
|
PrivateDevices=true
|
|
ProtectKernelTunables=true
|
|
ProtectKernelModules=true
|
|
ProtectKernelLogs=true
|
|
ProtectControlGroups=true
|
|
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
|
|
RestrictNamespaces=true
|
|
LockPersonality=true
|
|
# MemoryDenyWriteExecute=true blocks W^X memory — safe for stock
|
|
# CPython (no JIT) and the pure-Python/C-extension stack here, but
|
|
# would break a JIT or a C-ext that mmaps W+X. Watch the first
|
|
# restart's journal for a crash; if uvicorn fails to come up,
|
|
# comment this one line out and reload.
|
|
MemoryDenyWriteExecute=true
|
|
RestrictRealtime=true
|
|
RestrictSUIDSGID=true
|
|
SystemCallFilter=@system-service
|
|
SystemCallErrorNumber=EPERM
|
|
SystemCallArchitectures=native
|
|
UMask=0077
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|