Stoat (Revolt fork) — Lockdown & Org Setup

Stoat (Revolt fork) — Lockdown & Org Setup (Sections 1–5)

Context: closed invite-only org, Linux VM on FreeBSD host. Current edge is Caddy on the VM
(confirmed by via: 1.1 Caddy and Caddy listening on 0.0.0.0:80/443).

Objective
Minimize attack surface, enforce HTTPS, ensure WebSockets work, and prepare Stoat for a structured Star Citizen org.

1) Decide Your Edge: Caddy-on-VM vs Apache-on-FreeBSD

Right now, your VM is acting as the edge for revolt.yaws.com because Caddy is listening on
:80 and :443 and responses show via: 1.1 Caddy.
Choose one authoritative edge to simplify security and troubleshooting.

Option A (Recommended): FreeBSD Apache is the edge

  • Internet → FreeBSD PF → Apache :443 → VM private IP (HTTP)
  • VM ports are private; only FreeBSD is publicly exposed
  • Single choke point for rate limits, ACLs, logging, WAF, etc.

Option B (Fastest): Keep Caddy as edge on the VM

  • Internet → FreeBSD PF (pass-through) → VM Caddy :443 → Stoat services
  • Hardening happens on the VM firewall + Caddy config
  • Works well if the VM is already stable and you prefer Caddy
Key rule
Whichever edge you pick, the other component should be non-public (bind to private interface or firewall-restricted).

2) If Keeping Caddy as Edge: Harden Caddy + VM Exposure

2.1 Your current routing map (confirmed)

This is the authoritative request routing for your deployment:

Caddyfile routes (as deployed)
/api*      → api:14702     (strip /api)
/ws        → events:14703  (strip /ws)  [WebSockets]
/autumn*   → autumn:14704  (strip /autumn)
/january*  → january:14705 (strip /january)
/gifbox*   → gifbox:14706  (strip /gifbox)
/          → web:5000

2.2 Enforce HTTPS-only (redirect HTTP → HTTPS)

If you want to keep port 80 open for convenience, ensure it only redirects to HTTPS.
Add a redirect matcher near the top of your site block:

Caddy HTTPS redirect snippet
revolt.yaws.com {
  @http {
    protocol http
  }
  redir @http https://{host}{uri} permanent

  # ...existing routes...
}

2.3 Reduce exposure at the VM firewall

  • Allow inbound: tcp/443 only (and optionally tcp/80 for redirect)
  • Ensure internal service ports are not reachable from WAN
  • Prefer binding services to private networks, not 0.0.0.0
Important check
In compose.yml, avoid publishing internal ports (e.g., 14702–14706) to the host. They should be internal to the Docker network.

3) If Moving Edge to FreeBSD Apache: Exact Proxy Mapping (from your Caddyfile)

If you want Apache to be the edge, the cleanest design is: Apache terminates TLS and forwards to the VM’s Caddy over plain HTTP.
Caddy continues routing /api, /ws, /autumn, /january, /gifbox, and /.

3.1 Port 80: redirect to HTTPS

Apache :80 redirect
<VirtualHost 207.158.15.93:80>
  ServerName revolt.yaws.com
  RewriteEngine On
  RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
</VirtualHost>

3.2 Port 443: proxy to VM (HTTP + WebSocket)

This uses your now-loaded proxy_wstunnel module and proxies only the /ws path as WebSocket, matching your Caddyfile.
Everything else proxies as normal HTTP to Caddy on the VM.

Apache :443 vhost (keep your Let’s Encrypt SSL lines)
<VirtualHost 207.158.15.93:443>
  ServerName revolt.yaws.com

  SSLEngine On
  # Keep your existing Let's Encrypt cert/key directives here

  ProxyPreserveHost On
  ProxyRequests Off

  RequestHeader set X-Forwarded-Proto "https"
  RequestHeader set X-Forwarded-Port "443"

  ProxyTimeout 300

  # WebSocket path (matches Caddy: route /ws → events)
  ProxyPass        /ws  ws://207.158.15.95/ws
  ProxyPassReverse /ws  ws://207.158.15.95/ws

  # Everything else (Caddy handles /api, /autumn, /january, /gifbox, /)
  ProxyPass        /    http://207.158.15.95/
  ProxyPassReverse /    http://207.158.15.95/
</VirtualHost>
Critical follow-up
If Apache becomes edge, ensure the VM’s Caddy is not public (bind to private interface or firewall so only FreeBSD can reach it).

4) Stoat “Org Mode”: Roles, Categories, and Permissions

4.1 Role hierarchy (recommended)

  1. Fleet Command
  2. Executive Officer
  3. Wing Lead
  4. Squad Lead
  5. Veteran
  6. Member
  7. Recruit
  8. Guest
  9. Bot

4.2 Permission principles (do not skip)

  • No self-assigned roles
  • No role escalation: only top leadership can grant leadership roles
  • Prefer category-level permissions to avoid drift and mistakes
  • Keep Recruit restricted (links/uploads/posting scope) until vetted

4.3 Suggested category layout

Category blueprint
Announcements (read all; post leadership)
Fleet Ops (Member+)
Squads (gated by squad roles)
Logistics / Industry
Intel (restricted)
Command (Fleet Cmd/XO only; hidden)
Recruitment Intake (Recruit + moderators)

4.4 Example permission blueprint

  • Command: only Fleet Command + XO; hidden from everyone else
  • Fleet Ops: read/post for Member+; moderation tools for Wing Lead+
  • Recruitment Intake: recruits can post; restrict links/uploads if spam risk is non-trivial

5) Abuse Controls: Closed Invite-Only with 100+ Registered Accounts

With a closed org, most risk comes from: leaked invites, account stuffing, and low-effort spam.
Build controls at both the edge (proxy/firewall) and the app layer (permissions).

5.1 Invite policy (strong default)

  1. Only admins/mods can create invites
  2. Invites should expire (24–72 hours)
  3. Invites should have limited uses (1–5 typical)
  4. Never publish a “permanent” invite

5.2 Rate limiting and login protection

  • Edge throttling: fail2ban / WAF rules for repeated login failures
  • App throttling: stricter posting/link/upload rules for Recruit and Guest
  • Disable features you don’t use (public discovery, open registration), where supported

5.3 Upload limits at the edge (Apache note)

If Apache is your edge, you can cap request body size using LimitRequestBody.
Keep Apache comments on their own line (no inline #).

Apache upload cap example
# 25 MiB upload cap
LimitRequestBody 26214400
Operational tip
Keep recruits contained: limit channels, restrict links/uploads, and promote manually after vetting. This alone prevents most “drive-by” abuse.
Next inputs (for exact invite-only config)

To enforce invite-only at the Stoat server level (no new registrations) and confirm no accidental port exposure,
paste compose.yml and Revolt.toml (redact secrets). No need to share anything in data/.