Security & privacy

This page exists because you should be able to check.

I spend my working life on systems where a data leak is a clinical incident, not a PR problem. It would be strange to then hand my own site to a dozen third-party scripts. Here is exactly what this site does and does not do — verifiable from view-source.

The short version

  • No cookies. No analytics. No tracking pixels. No fingerprinting.
  • No third-party code at all — not a single external script, stylesheet or font.
  • A strict Content Security Policy that blocks inline and remote scripts outright.
  • My email address is never in the HTML source, so scrapers find nothing.
  • All demo data is synthetic, generated in your browser, and never leaves it.

Zero third-party dependencies

Every byte this site loads comes from its own origin. There is no CDN, no analytics tag, no webfont request, no embedded video player, no chat widget. That is not minimalism for its own sake — it removes an entire category of risk:

  • No supply-chain attack surface. A compromised npm package or a hijacked CDN cannot reach a site that never asks either of them for anything.
  • No silent data leakage. A font request tells the font host your IP, your page and your user-agent. This site makes no such request; typography uses the fonts already on your device.
  • Nothing to patch. There is no dependency tree, so there is no CVE to chase at 3am.

Content Security Policy

Every page ships a restrictive CSP. Even if someone found a way to inject markup, the browser would refuse to run it:

default-src 'self';
script-src  'self';          <!-- no inline JS, no eval, no remote scripts -->
style-src   'self';
img-src     'self' data:;
connect-src 'self' https://api.web3forms.com;
form-action 'self' https://api.web3forms.com;
base-uri    'none';          <!-- blocks <base> hijacking -->
object-src  'none';          <!-- no Flash/Java/embed -->
upgrade-insecure-requests

There is not one onclick= attribute or inline <script> block anywhere in the source — every behaviour is attached from an external module. That is what lets the policy stay this tight.

An honest limitation

GitHub Pages serves static files and does not let me set HTTP response headers, so the policy is delivered in a <meta> tag. That covers everything above, but two header-only protections cannot be expressed this way: frame-ancestors (clickjacking) and Strict-Transport-Security. HSTS is partly covered because github.io is on the browser's preloaded HSTS list. Moving the site behind Cloudflare would close the remaining gap; for a personal portfolio with no authenticated session and nothing to steal, the residual risk is close to nil. I would rather say so than pretend otherwise.

What is stored on your device

Exactly one thing: if you use the light/dark toggle, your choice is saved in localStorage under the key theme. That is it. It is not an identifier, it is never read by anything else, and clearing site data removes it. No cookies are set, so there is no consent banner to click through — there is nothing to consent to.

The contact form

  • Submissions go to Web3Forms, which relays them to my inbox. Nothing is stored on this site, because this site has no server.
  • The access key in the page is a public submission key by design — it can only be used to send me mail. There is no secret in the client, because a static site cannot keep one.
  • Spam is filtered in layers. A hidden honeypot field and a minimum time-to-submit are checked in the browser, and the honeypot is then forwarded so Web3Forms can reject it server-side as well — that last check is the only one a bot cannot skip by ignoring this page and posting straight at the endpoint. I am deliberately honest about that: on a site with no server of its own, browser-side checks are a filter, not a wall.
  • Submissions are throttled per browser, and every field is length-capped, trimmed of control characters and validated before it is sent. The enquiry type is checked against the options actually offered, so a tampered page cannot inject its own subject line.
  • Input is length-capped and validated before submission.
  • If the form is ever unavailable, it falls back to opening your own mail client — your message still reaches me, and nothing passes through a third party at all.

The interactive demos

The Hospital E-book and Management Dashboard demos are reconstructions, not connections. They contain no real patient data and never have. Every name, vital sign, medication, lab result and revenue figure is generated in your browser from a fixed seed at page load. The demos declare connect-src 'none', so the browser itself will block them from making a network request even if one were attempted. Nothing you click, type or administer is stored or transmitted.

Other measures

  • Every external link carries rel="noopener noreferrer", so no linked page can reach back through window.opener.
  • referrer-policy: strict-origin-when-cross-origin — outbound links never leak the full URL you came from.
  • All DOM is built with textContent and createElement. There is no innerHTML in the codebase, which closes off DOM-based XSS structurally rather than by escaping.
  • My email address is assembled at runtime from fragments, so harvesting bots reading the raw HTML find nothing to take. No phone number is published anywhere on this site.
  • A security.txt is published under RFC 9116 if you find something worth reporting.

Found a problem?

I would genuinely like to know. Email me — the address is on the contact section — and I will credit you here if you would like. There is no bounty, just gratitude and a fast fix.

Last reviewed: July 2026