Projekte

Janus

3 min Lesezeit GitHub
NestJS Next.js Redis Terraform

Cloudflare Turnstile, reCAPTCHA, hCaptcha. Alle drei machen das Gleiche: sie entscheiden, ob ein Request von einem Menschen oder einem Bot kommt. Und alle drei haben das gleiche Problem: deine User-Daten landen auf fremden Servern, du hast keinen Einblick in die Scoring-Logik, und wenn der Dienst ausfällt, stehen deine Formulare still.

Ich wollte wissen, ob man das selbst bauen kann. Nicht als Spielzeug, sondern als echtes System, das man auf eigener Infrastruktur betreibt.

Wie Janus funktioniert

Janus kombiniert vier Signale zu einem Risk Score von 0 bis 100:

Proof-of-Work. Der Browser bekommt eine kryptografische Aufgabe, die er in einem Web Worker löst. Echte Browser schaffen das in unter einer Sekunde. Headless Chrome oder Selenium brauchen deutlich länger, weil sie auf CPU-Throttling und fehlende Optimierungen stoßen. Die Lösungszeit allein sagt schon viel.

Browser Fingerprinting. Canvas Rendering, WebGL Parameter, Audio Processing, installierte Fonts. Jeder Browser produziert ein leicht unterschiedliches Profil. Janus gleicht das Profil gegen bekannte Spoofing-Muster ab. Ein Browser, der behauptet Chrome 120 auf macOS zu sein, aber Canvas-Artefakte von Headless Chromium produziert, bekommt Punkte.

Behavioral Analysis. Mausbewegungen, Tastatureingaben, Scroll-Verhalten. Echte Menschen haben Entropy in ihren Bewegungen. Bots bewegen sich in geraden Linien oder gar nicht. Das SDK trackt diese Signale vom Moment an, in dem die Seite lädt.

TLS Fingerprinting. Nginx extrahiert den TLS Client Hello und leitet den JA3-Hash an die API weiter. HTTP-Clients wie requests, got oder Puppeteer im Default-Modus haben distinctive TLS Fingerprints, die sich von echten Browsern unterscheiden.

Jedes Signal allein ist umgehbar. Zusammen werden sie teuer zu faken.

Architektur

Das Projekt ist ein Turborepo Monorepo mit drei Teilen:

Die API (NestJS + Fastify) verwaltet Sites, generiert Challenges, verifiziert Lösungen und berechnet Risk Scores. PostgreSQL speichert Sites, API Keys und Analytics. Redis cached Challenges und schützt gegen Token Replay.

Das Dashboard (Next.js 15) ist die Admin-Oberfläche: Sites anlegen, Schwellenwerte konfigurieren (allow < 30, challenge 30-69, block >= 70), Analytics einsehen. Requests pro Tag, Pass/Fail Ratio, Risk Distribution, Top IPs.

Das SDK kompiliert zu unter 5KB gzipped. Es lädt auf der Seite, startet den Web Worker für PoW, sammelt Fingerprint und Behavioral Signals, und gibt am Ende ein Token zurück. Die Integration sieht aus wie reCAPTCHA:

<script src="https://your-janus.com/sdk.js" data-site-key="site_xxx"></script>
<form>
  <input type="email" />
  <div id="janus-container"></div>
  <button type="submit">Absenden</button>
</form>

Server-seitige Validierung ist ein POST gegen /api/v1/siteverify mit dem Token. Antwort: success, riskScore, action.

Warum self-hosted

Das Argument für Turnstile ist Convenience. Das Argument dagegen ist Kontrolle. Wenn du in einer regulierten Branche arbeitest (Gesundheit, Finanzen, öffentlicher Sektor), ist “die Daten gehen an Cloudflare” oft ein Blocker. Janus läuft auf deiner Infrastruktur. User-Daten verlassen nie dein Netzwerk.

Das Terraform-Setup deployed auf AWS: ECS Fargate für API und Dashboard, RDS für Postgres, ElastiCache für Redis, ALB mit TLS, CloudFront vor dem SDK. Ein terraform apply und die Plattform steht.

Was ich gelernt hab

Fingerprinting ist ein Wettrüsten. Jedes Signal, das du sammelst, kann gespooft werden. Der Trick ist nicht ein perfektes Signal zu finden, sondern viele imperfekte Signale zu kombinieren und die Kosten für den Angreifer zu erhöhen. Janus muss nicht unknackbar sein. Es muss teurer sein als die nächste ungeschützte Seite.

Die andere Lektion: Proof-of-Work funktioniert besser als erwartet. Die Zeitdifferenz zwischen echtem Chrome und Headless Chromium ist messbar und konsistent. Das allein filtert schon einen großen Teil der Low-Effort Bots raus.