Sclerotin - Secure FastAPI demo
One endpoint, /api/v1/ping, behind Nginx in a hardened Docker Compose deployment. The endpoint is trivial on purpose: what this shows is everything around it.
The response speaks for itself
$ curl -sD - https://sclerotin.args.tech/api/v1/ping
JavaScript is off or blocked, so this page could not make the request. Run the command above: what it prints is what this block would have shown.
The response is not stored in the page: every load requests it anew and prints it as is. Nothing in it is left to defaults, and every choice is explained in the project's README.
What is in place
- A policy per kind of response
- API responses run under
default-src 'none'andframe-ancestors 'none'. This page adds four directives set to'self': for its stylesheet, its icon, the script that fills the block above and the request that script makes. Two more,base-uriandform-action, are set to'none', sincedefault-srcdoes not cover them. The stylesheet and the script are files on this server, not markup, so nothing here needsunsafe-inlineor a nonce. A relaxed policy applies only to the documentation pages, and only while they are switched on. - Headers split across both layers
- Nginx sets HSTS,
X-Frame-OptionsandReferrer-Policy; the application setsContent-Security-Policy,X-Content-Type-OptionsandPermissions-Policy. Responses Nginx produces on its own - a rate-limited 429, a 502 while the app restarts - get those three from Nginx instead, so an error page is never left bare. - No route out of the application
- The app container sits on an internal network with no route to the internet. Even with code execution through a bug, an attacker can neither fetch a payload nor send anything out.
- Containers keep only what they need
- Read-only root filesystems, every Linux capability dropped,
no-new-privileges, memory and CPU limits. Nginx gets three capabilities back; the application gets none and runs as an unprivileged user. - A quiet edge
- Requests for unknown host names and bare IPs are dropped without an answer. Rate limits apply per client address, and IPv6 clients keep their own address instead of showing up as the gateway.
- TLS without loose ends
- TLS 1.2 and 1.3 with the Mozilla intermediate ciphers, post-quantum hybrid key exchange (X25519MLKEM768) for clients that support it, session tickets off, HSTS for two years including subdomains.
What this is not
Not a framework, not a library, not a starting point for an application. The Python side is one short file and is meant to stay that way. This is a reference for the parts that usually get decided in a hurry: the proxy, the container, the network and the headers.