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.
- 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)
Screenshots
Sensitive values (IPs, MACs, hostnames, camera feeds) are redacted.
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
| # | 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 auditclean. 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.shsets 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
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
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