# Plan: Security Hardening & Documentation — COMPLETE Prompted by user feedback — reluctance to run a public-facing server with write access without understanding the threat model. ## Problem There's no security documentation. Security-conscious users have to guess at the attack surface and hardening options. This blocks public adoption. ## What Was Done ### 1. Security page on the website — DONE - `docs/site/security/index.md` + pages branch `security.md` - Attack surface, token compromise, process compromise analysis - Comparison table (SSH vs Web+CGI vs Gemini vs Demarkus) - Systemd hardening guide with verification command ### 2. Systemd hardening in install script + deployment docs — DONE - `setup_systemd` generates unit with ProtectSystem=strict, ReadWritePaths, NoNewPrivileges, etc. - Conditional ProtectHome (omitted when content root is under /home) - readlink -f to canonicalize content root before writing unit - Deployment docs updated with hardened systemd example ### 3. Install script detects insecure existing config on update — DONE - `_do_update_inner` checks for missing ProtectSystem in existing unit - Interactive prompt (defaults to yes, skips in non-interactive mode) - Backs up unit before modifying, rolls back if service fails to start - Points to public security docs URL ### 4. Read-only mode (`-read-only` flag) — DONE - `DEMARKUS_READ_ONLY` env var + `-read-only` flag on server - Handler rejects PUBLISH/APPEND/ARCHIVE with `not-permitted` status - Config accepts `1`, `true`, `yes` as truthy values - Tests for handler rejection and config parsing ### 5. `demarkus-publish` CLI tool — DONE - `server/cmd/demarkus-publish/main.go` - Writes directly to versioned store on disk (same `store.Write()` as server) - Supports `-body` flag or stdin input - Detects duplicate content (no-op on unchanged) - Enables local publishing when server runs read-only ### 6. Read-only chroot install script — DONE - `install-readonly.sh` — separate script for maximum security deployments - Chroot structure: `/srv/demarkus/{bin,content,tls}` - Systemd unit with RootDirectory, ReadOnlyPaths=/, BindReadOnlyPaths=/dev/urandom - Installs `demarkus-publish` to /usr/local/bin for local publishing - SHA-256 checksum verification on downloads - Rejects dangerous root paths (/, /usr, /etc, etc.) - No auto firewall changes (user decides) ### 7. Cleanup - Removed redundant `demarkus.service` from repo root - Updated docs: reference (config table), server (read-only section), install (binary table, readonly option), deployment (security link) ## Key Design Decisions - **Shared store code**: `demarkus-publish` calls `store.Write()` directly — same code path as the server. No duplication. - **`not-permitted` not `unauthorized`**: Read-only rejection is a server policy, not an auth failure. - **Separate install script**: Read-only chroot install is a different deployment model, kept separate to avoid complicating the main install script. - **No iptables**: Replaced fragile iptables write isolation with read-only mode + chroot, which is simpler and more secure. ## Related documents - [Read auth for private networks](/plans/read-auth.md): companion access-control work shipped the same week