David Dashti

Colophon

How this site is built, and how it is secured. I work on product security for regulated medical software, so it seemed fair to show the same practices applied to something of my own rather than only describe them.

Everything below is checkable. The response headers are visible to anyone who looks, and the pipeline that produces them is in the public repository.

What it is built with

Frontend
Vue 3 and TypeScript, pre-rendered to static HTML at build time and served from Vercel. Pages arrive complete rather than assembling themselves in the browser.
Backend
FastAPI and PostgreSQL on Fly.io. It holds the content and serves the private admin API. The public pages are plain static files carrying the content baked into them at build time.
Content
Edited through a private admin interface, stored in Postgres, and baked into the static pages by a build. Publishing is therefore a deploy, with the same checks as any code change.

What the browser is told

Response headers are the cheapest security control there is and the easiest to leave half-configured. These are set deliberately and tested.

Content Security Policy
Scripts may load only from this origin, plus two SHA-256 hashes covering the only two inline scripts the site ships. Inline event handlers are refused outright, as are plugins, framing, and any attempt to rewrite the document base or post a form elsewhere.
Hash drift
Those two hashes are recomputed from the source on every test run. A reformatted script or a new inline block fails the build instead of silently breaking the policy in production, which is the usual way hash-based policies rot.
Transport
Strict transport security for a year, covering subdomains. The header carries the preload directive and the domain meets the preload list requirements, but it has not been submitted to that list — so today the guarantee starts at a visitor’s first request rather than before it.
The rest
Framing denied, MIME sniffing off, referrers trimmed across origins, and camera, microphone, geolocation, payment, USB and the motion sensors all switched off for the page and anything it embeds.

The private half

One person can sign in here. That makes the account worth protecting rather than not worth bothering with.

Sign-in
GitHub OAuth against a single permitted account. There is no password to phish, guess or leak.
Sessions
Cookies only — HttpOnly and SameSite, never readable by JavaScript. The working credential lasts thirty minutes; the renewal credential is replaced every time it is used.
Stolen-token handling
Replaying an already-used renewal token is treated as theft, per RFC 6819, and revokes every session for the account at once. A brief window is carved out for the harmless case of two browser tabs renewing simultaneously, so ordinary use is not mistaken for an attack.
Abuse limits
Requests are rate limited and oversized request bodies are rejected before they reach any handler.

What goes into a build

Most realistic attacks on a site like this arrive through its dependencies or its pipeline, not its pages.

Pinned actions
All fifteen third-party build steps are pinned to an exact commit, never a moving tag. A script in the pipeline re-checks every pin against the version written beside it and fails if the two disagree — because a stale comment beside a correct hash is how a pin quietly stops meaning anything.
Verified dependencies
Every Python package is installed only if its hash matches a value committed to the repository. That includes the installer itself, so the tool doing the verifying is verified too. JavaScript packages install from a committed lockfile.
Scanning
Every change gets static analysis, a vulnerability scan of the tree, a review of any dependency it adds, a supply-chain posture score, and a secret scan of the diff. The scanner binary is itself checksum-verified before it is trusted to run. Findings are triaged rather than left to accumulate.
Gates
Nothing deploys until the unit tests, the browser tests and a performance and accessibility budget have all passed. Deploying is not a separate decision from testing.

Backups, and evidence they work

An untested backup is a belief, not a control. This is the part most personal sites skip, so it is the part worth showing.

Nightly, encrypted
The database is dumped every night and encrypted to a public key before it is stored. The matching private key is held offline and never reaches the machine that runs the backup, so the backup process cannot read its own output.
Rehearsed, not assumed
A restore checklist is filed automatically every quarter. It has been carried out: the archive was decrypted, restored into a disposable database with zero errors, and its row counts matched production exactly.
When something went wrong
That rehearsal is also what caught a stored key being unusable, and the recovery — rotate the key, re-encrypt, run the whole drill again on a fresh backup — was completed the same week. Drills earn their keep on the days they fail.

Where it falls short

The policy still permits inline style attributes — the utility-CSS build emits them, so removing that allowance means changing how the styles are generated, not editing a header. Scripts carry no such exemption, which is where the risk actually sits. It is a real gap, and it is open deliberately rather than unnoticed.

Reporting something

If you find a problem here, there is an RFC 9116 security.txt with contact details. Reports are welcome, including the small ones.

Claims on this page last verified against the live site on 4 September 2026.

Back to Portfolio