Hardened Portfolio Deployment
A static site is a good place to practice the basics properly: a small attack surface, strict browser policies, and a container that can't do much even if something goes wrong.
Where it started
The first version of this site worked, but it had the problems many personal sites have:
- Scripts and styles loaded from three different CDNs at runtime, with no integrity checks.
- Development files such as
package.jsonand lint configuration were in the web root and publicly downloadable. - The container ran a stock nginx image with default settings.
- A contact form that looked functional but had nowhere to send messages.
The container
- Non-root. Based on the unprivileged nginx image, so the server process has no root privileges inside the container.
- Read-only filesystem. Only a small tmpfs is writable, for nginx's runtime files.
- No capabilities. All Linux capabilities are dropped and privilege escalation is disabled.
- Only the site is copied in. Build tooling, lint configuration and deploy metadata never reach the image.
- No published host port. The container is reachable only through the reverse proxy, which handles TLS.
What the browser is told
Every response carries headers that limit what a page can do, even if something were injected into it:
- Content-Security-Policy allows scripts, styles, fonts and images only from this origin. There are no inline scripts, so no
unsafe-inline. - Strict-Transport-Security keeps browsers on HTTPS.
- X-Content-Type-Options, Referrer-Policy, Permissions-Policy and Cross-Origin-Opener-Policy close off MIME sniffing, referrer leakage, unused device APIs and cross-window access.
- The Server header doesn't include a version number.
The home page checks these headers live: it re-requests itself when it loads and displays what the server actually returned.
Disclosure
A security.txt file (RFC 9116) tells anyone who finds a problem how to report it and when that information expires.
Check it yourself
# fetch only the headers curl -sI https://web.adamschussel.com/ # dev files should not be served curl -s -o /dev/null -w "%{http_code}\n" https://web.adamschussel.com/package.json