cwashingtonblue team · soc
← Projects

Cyber Lab · Case study

This Website: a Hardened, Self-Hosted Portfolio CMS

I replaced WordPress with a lightweight CMS I host myself: a private editor behind password + TOTP 2FA, an AES-256 encrypted database, a strict content security policy, a locked-down container and a one-command installer, with seven layers of defense in depth.

Role
Owner: requirements, threat model, deployment and operations (built with AI-assisted development)
Stack
Node.jsExpressSQLCipherArgon2idTOTPHelmet/CSPDockeriptablessystemdNginx Proxy Manager
This Website: a Hardened, Self-Hosted Portfolio CMS
  • 7Security layers between a visitor and the data
  • 2Factors to reach the editor (password + TOTP)
  • AES-256Database encryption at rest
  • 0Known vulnerable dependencies (npm audit)

Why I built it

My old site ran on WordPress: a public /wp-admin login that bots hammer all day, plugins to keep patched and a large attack surface for what is really a few pages. For a cybersecurity portfolio, the site itself should be evidence of how I think about security. So I replaced it with a small CMS I host on my own Proxmox cluster.

Defense in depth

Defense in depth

# Layer Controls
1 Transport HTTPS only through Nginx Proxy Manager, HSTS with preload
2 Network Host firewall (DOCKER-USER chain): only the proxy can reach the app port
3 Container Read-only filesystem, non-root user, all Linux capabilities dropped, no-new-privileges, memory and process limits
4 Edge HTTP method allowlist, URL length cap, scanner-probe blocking (e.g. /wp-login.php, /.env), rate limiting, slow-request (slowloris) timeouts
5 Browser Strict Content Security Policy with per-request nonces, no inline scripts, CSRF tokens and same-origin checks, SameSite=Strict cookies
6 Identity Editor on a private path, never linked. Argon2id passwords, TOTP 2FA with replay protection, recovery codes, lockout after repeated failures, hashed server-side sessions
7 Data SQLCipher AES-256 encrypted database, AES-256-GCM for 2FA secrets, nightly backups

Mapped to the OWASP Top 10

  • Broken access control: one admin, private path, every editor route behind a session that requires both factors.
  • Cryptographic failures: Argon2id (memory-hard) password hashing, AES-256 at rest, TLS in transit.
  • Injection: parameterized SQL everywhere, auto-escaped templates, sanitized Markdown.
  • Security misconfiguration: hardened headers by default, no default credentials, no directory listings, secrets generated at install.
  • Vulnerable components: a small dependency tree, npm audit clean. I replaced one library that had a known advisory and pinned a patched PDF reader.
  • Identification and authentication failures: two-factor auth, single-use codes, rate limiting and lockout.
  • Logging and monitoring: an audit log records sign-ins, failed attempts, blocked probes and every edit.

Privacy by design: the Resume Checker

The Resume Checker scores resumes against 15 cybersecurity career paths. Resumes are read and analyzed entirely in the visitor's browser and never uploaded. The only server call is an optional Word export of the finished text, which is rate-limited, same-origin only and not stored.

Operations

  • One-command install. A single install.sh sets up Docker, the firewall, automatic security updates, nightly backups and a weekly rebuild with patched base images.
  • Safe updates. Re-running the installer upgrades the code and never touches content, keys or settings.
  • Test before go-live. A LAN-only test mode let me verify everything before pointing the domain at it.

How I tested it

  • Sent scanner-style requests (/wp-admin, /.git/config, /phpmyadmin) and confirmed they're blocked and logged.
  • Verified rate limiting and that slow, never-finishing requests are dropped.
  • Confirmed 2FA codes can't be reused and that lockout kicks in.
  • Checked every page for Content Security Policy violations.
  • Validated the firewall rules in an isolated network namespace.

Skills demonstrated

Threat modeling · secure web architecture · authentication and cryptography · container hardening · Linux firewalling · secure SDLC and dependency management · deployment automation

What's next

  • Go live on cwsecops.com and publish SSL Labs and securityheaders.com results.
  • Optionally put the editor behind Cloudflare Access as an extra layer.

More projects

All projects
Homelab

Securxlab — Homelab SOC & Self-Healing Command Center

A self-built security operations dashboard for my homelab: SIEM with MITRE ATT&CK-mapped detections, alert triage and threat intel, plus Proxmox, UniFi and camera control, all driven by n8n automation with human-in-the-loop approvals.

Proxmox VEn8nElastic SIEMUniFi+5

Read write-up
Homelab

3-Node Proxmox Cluster

The foundation of my lab: a three-node Proxmox VE cluster running my SIEM, automation, web and home services as VMs and containers, with approval-gated patching, a self-healing watchdog and key-only SSH.

Proxmox VEKVMLXCDocker+5

Read write-up
Cyber Lab

Elastic SIEM + MISP

A dedicated Elastic SIEM for my lab: Filebeat ships host logs from every Proxmox node over TLS, automation events land in their own index, and MISP adds threat intelligence, all so I can investigate the way a SOC analyst does.

Elasticsearch 8KibanaFilebeatMISP+3

Read write-up