# Self-Hosted Service Security Audit

A comprehensive methodology for assessing the security posture of any self-hosted web service (KasmVNC, Immich, Paperless, Mealie, etc.) behind a VPS reverse proxy.

## Audit Checklist (Run in Order)

### 1. Network Exposure
```bash
# DNS resolution — where does the domain point?
dig +short <subdomain>.<domain> @1.1.1.1

# Compare against server's public IP
curl -s ifconfig.me

# If they differ → VPS reverse proxy in front → good
# If they match → service directly exposed → verify UFW
```

### 2. TLS / Transport Security
```bash
# Full handshake info + headers
curl -sI -v https://<domain>/ 2>&1 | grep -E '(SSL|TLS|certificate|HTTP/|Server|Strict-Transport|X-Content|X-Frame|CSP|Referrer)'

# Certificate details
echo | openssl s_client -servername <domain> -connect <domain>:443 2>/dev/null | openssl x509 -noout -text | grep -E '(Subject:|Issuer:|Not Before|Not After|Subject Alternative)'
```

**Check for:**
- TLS 1.2+ only (no TLS 1.0/1.1, no SSLv3)
- Strong ciphers (AES-GCM, ChaCha20) — not RC4, 3DES, or MD5
- Let's Encrypt or similar trusted CA (not self-signed)
- HSTS header present (`Strict-Transport-Security`)
- Certificate covers the domain (Subject Alternative Name)

### 3. HTTP Security Headers
```bash
curl -sI https://<domain>/ | grep -i -E '(strict-transport-security|x-content-type-options|x-frame-options|content-security-policy|referrer-policy|permissions-policy)'
```

**Check for:**
- `Strict-Transport-Security` — HSTS, ideally `max-age>=31536000`
- `X-Content-Type-Options: nosniff` — prevents MIME sniffing
- `X-Frame-Options: DENY` or `SAMEORIGIN` — clickjacking protection
- `Content-Security-Policy` — XSS mitigation (can be tricky with SPAs/WebSockets)
- `Referrer-Policy` — controls referrer leakage
- Missing headers are a hardening opportunity, not always critical for internal services

### 4. Nginx / Reverse Proxy Config
```bash
# Inspect the nginx site config
cat /etc/nginx/sites-available/<service>

# Check for server_name — does it match the subdomain?
# Check for auth_basic — any nginx-level auth?
grep -rn "auth_basic" /etc/nginx/ 2>/dev/null

# Check for rate limiting
grep -rn "limit_req" /etc/nginx/ 2>/dev/null

# Check for proxy_hide_header — which headers are stripped?
grep "proxy_hide_header" /etc/nginx/sites-available/<service>
```

**Check for:**
- `server_name` includes ALL valid domains (DuckDNS + custom domain)
- `auth_basic` for defense-in-depth (recommended for single-user services)
- `limit_req` to prevent brute force
- `proxy_hide_header` — strips unwanted backend headers (critical for KasmVNC COEP/COOP)
- `proxy_ssl_verify off` — acceptable for localhost backends, note it's not verifying

### 5. Docker Container Security
```bash
# List containers with status
docker ps --format "table {{.Names}}\t{{.Status}}"

# Deep inspect
docker inspect <container> --format '{{.HostConfig.NetworkMode}}'          # network mode
docker inspect <container> --format '{{.HostConfig.CapDrop}}'              # dropped capabilities
docker inspect <container> --format '{{.HostConfig.CapAdd}}'               # added capabilities
docker inspect <container> --format '{{.HostConfig.SecurityOpt}}'          # security opts
docker inspect <container> --format '{{.Config.User}}'                     # running user
```

**Check for:**
- **Network mode**: `bridge` (isolated, preferred) vs `host` (full host network access — riskier)
- **Capabilities**: ideally `--cap-drop=ALL` with explicit `--cap-add` for what's needed
- **Running user**: non-root inside container (e.g., `kasm-user` is good)
- **Security opts**: `no-new-privileges:true` is a hardening bonus
- **Restart policy**: `unless-stopped` (good) vs `always` (also fine)

### 6. Container Internals
```bash
# Inside the container
docker exec <container> whoami
docker exec <container> cat /etc/os-release 2>/dev/null
docker exec <container> cat /etc/debian_version 2>/dev/null
docker exec <container> uname -r 2>/dev/null

# Application version
docker exec <container> <app> --version 2>/dev/null
```

**Check for:**
- Base OS age (Ubuntu 20.04 is past standard EOL in April 2025)
- Application version recency
- Image build date (`docker inspect <image> --format '{{.Created}}'`)
- Whether the image is regularly updated

### 7. Firewall and Host Defense
```bash
# UFW rules
sudo ufw status verbose

# iptables rules for the service port
sudo iptables -L INPUT -n --line-numbers | grep <port>

# Security monitoring
dpkg -l fail2ban crowdsec rkhunter chkrootkit 2>/dev/null | grep '^ii'
```

**Check for:**
- Default inbound policy: DROP (not ACCEPT)
- Service port allowed only from needed ranges (LAN, Tailscale VPN)
- Public internet blocked at the firewall level
- fail2ban or similar running for brute-force protection

### 8. VPS Reverse Proxy Architecture
```bash
# Trace the architecture
dig +short <subdomain>.<domain>          # → VPS IP
# VPS proxies to home server via Tailscale
ssh ubuntu@<vps-ip> 'grep proxy_pass /etc/nginx/sites-enabled/<service>'
```

**Check for:**
- DNS points to VPS, not home server IP
- VPS nginx proxies via Tailscale IP (100.x.x.x), not public IP
- UFW on home server allows port only from LAN + Tailscale ranges
- Home router has NO port forward for this service (zero open ports)

### 9. Risk Summary Matrix

| Layer | Weakness | Severity | Mitigation |
|-------|----------|----------|------------|
| Network | Public exposure via DNS | Low if behind VPS | UFW, VPS proxy |
| Transport | Missing HSTS | Low | `add_header` in nginx |
| Auth | Single auth layer | Medium | Add nginx `auth_basic` |
| Container | Host networking | High | Switch to bridge + iptables |
| Container | Default capabilities | Medium | `--cap-drop=ALL` + explicit adds |
| Container | Old base OS | Medium | Regular image pulls |
| App | No rate limiting | Low | `limit_req` in nginx |

## Interpretation Guide

- **Low severity** = hardening opportunity, not urgent. Benefits of fixing may not justify risk of breaking the service.
- **Medium severity** = address when convenient. Adds defense-in-depth.
- **High severity** = actively exploitable if attacker gains any toehold. Prioritize.

The firewall + VPS architecture is almost always the strongest defense. Even a poorly-configured container behind a proper firewall is hard to reach from the internet. Focus hardening effort where it reduces blast radius once an attacker is inside the network.
