Search the web. Remember nothing.
Privacy-first meta-search: no tracking, no ads, no accounts, no search history — with a bot-gated API and a VPN between every query and the engines. Live at amnesia.tax, self-hostable from this repo.
Privacy · Architecture · Security · Engine coverage · Self-host
| Amnesia | Bing | DuckDuckGo | ||
|---|---|---|---|---|
| Cookies | Session-only, anonymous* | Yes | Yes | Yes |
| Search history | None | Stored | Stored | None** |
| IP logging | None | Yes | Yes | Partial |
| Ads | None | Yes | Yes | Yes |
| Tracking pixels | None | Yes | Yes | None |
* one HMAC-signed, 30-minute session cookie so the bot-check solves once, not per search — it identifies a session, never a person, and stores nothing. ** DuckDuckGo doesn't store searches but does log metadata.
Amnesia stores nothing. No accounts, no analytics, no server-side query logs. The SearXNG backend proxies every query through an encrypted VPN — search engines see the VPN exit IP, never yours.
flowchart LR
B["browser"] --> P["Cloudflare Pages<br/>static SPA · strict CSP/headers"]
B --> W["API-gate Worker<br/>Turnstile once → HMAC session cookie<br/>edge-caches autocomplete"]
W -->|"secret header,<br/>WAF-locked origin"| T["Cloudflare Tunnel"]
T --> S["SearXNG<br/>hardened container"]
S --> V["Gluetun VPN<br/>WireGuard / ProtonVPN"]
V --> E["search engines"]
- Front end — one self-contained HTML file (
src/amnesia-search.html, ~46 KB): inline CSS/JS, self-hosted fonts, no framework, no build step, no third-party requests beyond the Turnstile challenge. Category tabs, engine tags per result, debounced autocomplete, pagination, timing, OpenSearch integration, dark/light mode. - API gate — a Cloudflare Worker in front of the backend. First visit solves a Turnstile check and gets a signed session cookie; every search after that is cookie-authenticated. Fails closed if its secrets are unset. Autocomplete responses are edge-cached (6 h) so suggestions don't hammer the backend per keystroke.
- Origin lock — the backend hostname answers only to the Worker: a WAF rule 403s any request without the gate's secret header, plus zone rate-limits on
/searchfor both hosts. You can't reach SearXNG around the gate (try it:search-origin.amnesia.tax/search→ 403). - Backend — a single hardened SearXNG container (
cap_drop: ALL, read-only rootfs, no-new-privileges, memory-capped, digest-pinned) whose only egress is the VPN. No result cache, no Redis/valkey, no nginx — deliberately: fewer moving parts holding your queries is the point.
- The session-auth boundary is fuzzed in CI — ClusterFuzzLite drives the Worker's cookie sign/verify against forgery and splice attacks on every change (
fuzz/session.fuzz.js). - Result URLs are scheme-allowlisted (
safeUrl()): a poisoned engine result carrying ajavascript:/data:URL is dropped before it can become a clickable script. Verified against the live deployed site with real-browser request-interception injection tests, not just unit tests. - CodeQL + OpenSSF Scorecard + weekly canary (token health + live smoke: site 200 / gate 401 / origin 403) run continuously; actions are SHA-pinned; CI for fork-reachable workflows runs on GitHub-hosted runners so untrusted PR code never touches the deploy host.
- Strict headers/CSP ship with the site (
src/_headers); Turnstile is the only third party in the policy. - History was scrubbed before this repo went public and secrets live only in Worker bindings / CI secrets — nothing in the tree, nothing in the history.
SearXNG's catalog spans 155+ engines, and self-hosters get all of it. The hosted instance at amnesia.tax deliberately runs a curated set that actually works from behind a VPN — currently Brave, Bing, DuckDuckGo, Yandex, Presearch, Crowdview, and searchmysite for web, plus per-category engines (news, images, videos, science, dev, social, files).
Why not Google/Mojeek/Qwant? They block datacenter IP ranges wholesale — every VPN exit is a datacenter IP, so those engines refuse the hosted instance's queries no matter which exit it uses. That's the privacy-vs-coverage tradeoff made explicit: the engines that can't see you are the engines you get. Broken or perma-blocked engines are disabled rather than left to time out, which is also why searches stay fast (~1–2 s end-to-end). The live engine set is mirrored in infra/searxng/settings.yml.
Minimal (your own machine, your own IP — every engine available, no gate needed):
docker run -d --name searxng -p 8080:8080 searxng/searxng
# serve src/amnesia-search.html from any static server, pointed at your instanceThe full production shape — VPN egress, hardened container, tunnel, gated Worker — is documented in infra/ (compose file, SearXNG settings, tunnel ingress snippet, DEPLOY.md). The Worker lives in worker/ and deploys with wrangler; the site deploys to any static host (Cloudflare Pages here, via deploy.yml).
src/ the SPA: amnesia-search.html (~46 KB, self-contained) + _headers (CSP) + fonts + og
worker/ the API-gate Worker: Turnstile → HMAC session, /search /autocompleter /session /healthz
infra/ production mirror: compose, hardened SearXNG settings, tunnel ingress, DEPLOY.md
fuzz/ ClusterFuzzLite target for the session-cookie auth boundary
.github/workflows/ ci · codeql · cflite · scorecard · canary (weekly live smoke) · deploy + deploy-worker
HTML · CSS · JavaScript · SearXNG · Cloudflare Pages + Workers + Tunnel + WAF · Turnstile · Gluetun (WireGuard) · ProtonVPN
- SearXNG — the meta-search engine that powers Amnesia
- Gluetun — VPN tunnel for containerized services
- askalf — the AI operation that runs Sprayberry Labs (includes Amnesia)
MIT — askalf.org · Live at amnesia.tax
Part of Own Your Stack — own your AI infrastructure instead of renting it by the token. Built by Thomas Sprayberry.