MR.
CASE STUDY · META
THREAT MODEL · SELF-AUDIT

Threats.

The portfolio site, audited by its own author. Assets, adversaries, mitigations — written down so they can be argued with.
ROLE
Sole architect
PERIOD
Continuous
STATUS
Live · richtermax.com
READ
≈ 6 min
CHAPTER 01
WHY THIS PAGE EXISTS

If you sell security, you publish your threat model.

Anyone can claim to ship secure software. The proof is whether you've thought through your own surface area, written it down, and offered an honest place to send a report. That's what this page is.

The site you're reading is a static Astro build deployed on Vercel. It has no user accounts, no payment flow, no CMS. The attack surface is small — but small is not zero, and "static" is not a synonym for "safe." The same supply-chain, header, and form-pivot risks that hit any production app apply here.

What follows is the threat model: what's worth attacking, who would attack it, and what's in place to make that hard. Read with skepticism. If something looks wrong, the disclosure flow is at the bottom.

CHAPTER 02
ASSETS & ADVERSARIES

Small surface, real edges.

Four assets carry meaningful risk. Five adversary classes get first-class consideration. Everything else is out of scope, named explicitly so it stays out.

A.01 · ASSETS

What's actually worth defending.

Reputation via the served HTML. Inbound mail from the contact form (PII in transit). Build-time secrets for the Strava + GitHub data refresh. The domain itself: a takeover would route everything elsewhere.

A.02 · ADVERSARIES

Who's modeled, who's not.

In: drive-by scanners, SEO/defacement injectors, supply-chain compromise of npm dependencies, contact-form abuse for phishing pivots, DNS / hosting account takeover. Out: nation-state actors, physical access, browser zero-days. Realistic threat-modeling, not theatre.

A.03 · NON-TARGETS

What this page is not claiming.

Vercel's platform itself, GitHub, Strava, Web3Forms — all third-party services, out of scope. Anything served from richtermax.com is in scope. Browser bugs, your local machine, your DNS resolver: not in scope.

A.04 · DATA AT REST

What this site stores about you.

Nothing. No cookies set by this domain. Vercel Analytics is cookie-free and anonymized. Form submissions go directly to Web3Forms over HTTPS and land in a Proton inbox — they are never persisted in this site's storage.

"Static is not a synonym for safe. The threat model is the proof of work."
CHAPTER 03
DEFENSES IN DEPTH

Layered, named, verifiable.

Each control below sits on a real header, file, or workflow. You can verify them yourself — the source repo is linked in chapter 05, and every header is observable on the live response.

D.01 · TRANSPORT

HTTPS everywhere, HSTS preloaded.

Strict-Transport-Security: max-age=63072000; includeSubDomains; preload. Two-year max-age, subdomain coverage, eligible for browser preload lists.

D.02 · CONTENT POLICY

Strict CSP, allowlist not denylist.

default-src 'self'. Inline styles only (no inline scripts beyond the build's own). blob: permitted only on connect-src + worker-src — required by the WebGL texture loader, scoped tightly.

D.03 · ISOLATION

COOP, CORP, frame-deny.

Cross-Origin-Opener-Policy: same-origin blocks Spectre-class window probing. Cross-Origin-Resource-Policy: same-origin stops third-party embedding of static resources. X-Frame-Options: DENY rejects clickjacking entirely.

D.04 · PERMISSIONS

Browser features off by default.

Camera, microphone, geolocation, payment, USB, accelerometer, gyroscope, magnetometer — all denied via Permissions-Policy. The site has no reason to request any of them; the policy header makes that contract explicit.

D.05 · FORM ABUSE

Honeypot + Web3Forms + explicit consent.

Hidden honeypot field on the contact form catches naive bots. Web3Forms applies server-side spam scoring. A pre-submit checkbox makes consent explicit (no automated submission). No client-side API keys: the access token is non-secret by design.

D.06 · SUPPLY CHAIN

Pinned, locked, audited.

All npm dependencies pinned via package-lock.json. Three.js bundled locally — no Google CDN, no jsDelivr, no jsdelivr-shaped untrusted scripts. The Draco WASM decoder is self-hosted under /draco.

D.07 · BUILD-TIME SECRETS

Tokens never reach the browser.

Strava + GitHub data is fetched at CI build time only. The resulting JSON is committed to the repo and served as a static asset. The browser never sees a token; the site never holds one.

D.08 · INTEGRITY

Reproducible builds, observable headers.

Astro static export — every page is byte-identical given the same inputs. Vercel ships the dist/ output verbatim. Run curl -I https://www.richtermax.com to verify the live header set against the policy committed in vercel.json.

CHAPTER 04
RESPONSIBLE DISCLOSURE

Found something? Here's the path.

Email max.richter.dev@proton.me with a clear write-up. A working proof-of-concept beats a hypothesis. Initial acknowledgement within 72 hours; substantive reply within seven days. If the issue is critical and being actively exploited, mark the subject line [CRITICAL] and I'll page myself.

In scope: anything served from richtermax.com or www.richtermax.com. Out of scope: issues in third-party platforms (Vercel, GitHub, Strava, Web3Forms) — please report those to the upstream provider directly. Please don't: run automated scanners that generate hundreds of contact-form submissions, attempt to take the site offline, or pivot from any finding into other systems.

Machine-readable contact info follows RFC 9116 at /.well-known/security.txt.

security.txt · live
# https://www.richtermax.com/.well-known/security.txt
Contact: mailto:max.richter.dev@proton.me
Encryption: https://www.richtermax.com/pgp/maxrichter.asc
Expires: 2027-05-01T00:00:00.000Z
Preferred-Languages: en, de
Canonical: https://www.richtermax.com/.well-known/security.txt
Policy: https://www.richtermax.com/security
PGP · public key
# Ed25519 · valid through 2028-04-30
# Fingerprint:
2A11 41D8 7D21 6D3B 2DF6  22AB 00D4 248C 65D3 E5AF

# Download:
# https://www.richtermax.com/pgp/maxrichter.asc

# Verify on the command line:
$ curl -s https://www.richtermax.com/pgp/maxrichter.asc | gpg --show-keys
CHAPTER 05
OPEN AUDIT

The source is public. Verify, don't trust.

Everything on this page is checkable. The Vercel headers are observable on every response. The CSP lives in vercel.json. The build pipeline is in .github/workflows/. The fetch script for live data is in scripts/fetch-live-stats.mjs. If a claim above doesn't match the repo, it's a bug — please send the diff.

Hall of thanks: empty so far. First valid finding gets credited here, with a link of your choosing.

— END OF REPORT —