Janus is a lightweight Go middleware designed to stop automated bots while preserving a smooth experience for real users. It combines client-side Proof‑of‑Render (PoR) and optional Proof‑of‑Work (PoW), browser fingerprinting, TLS-like fingerprint heuristics, IP/Geo reputation, and configurable scoring to decide whether to challenge a visitor — then issues short-lived JWT cookies so verified users pass through seamlessly.
Why Janus? ✨
- Less friction than traditional CAPTCHAs — challenges are small, transparent, and often invisible to real users.
- Multi-signal detection — not a single noisy heuristic but a weighted score from headers, fingerprint, TLS/JA3-like info, geo/ip reputation, and rate limits.
- Tunable — you control difficulty, weights, and whitelists in
config.yaml.
Quick USP blurb
Janus blends usability and security: fewer CAPTCHAs for users, stronger bot resistance for your site — easy to integrate and fully configurable. 🚀
Prerequisites
- Go 1.25+
- Optional:
openssl(for self-signed certs) and Redis (for production store)
Clone, build and run (PowerShell):
Set-Location -Path "g:\2025\Janus"
go build ./cmd/janus
.\janus.exe
# or
go run ./cmd/janusNotes
- The server expects
cert.pemandkey.pemin the working directory for HTTPS incmd/janus/main.go. For local testing, generate self-signed certs withopenssl. - Drop
GeoLite2-City.mmdbin the repo root to enable geo-based checks (optional).
- A visitor requests a protected page —
JanusMiddlewareintercepts every request. - Quick checks: if request is for Janus API (
/janus/*) or sensor, serve it; if visitor has a validjanus_tokencookie, allow through. - If unverified, the middleware serves a challenge page (
assets/challenge.html) which loadsassets/sensor.js. - The browser posts a fingerprint to
POST /janus/fingerprintand requestsGET /janus/challenge. - Server issues a tiny challenge (nonce, seed, iterations, difficulty).
- Client computes a proof (PoR uses a canvas hash; PoW performs light hashing) and posts to
POST /janus/verify. - Server verifies: nonce/seed/IP/timestamp/iterations/canvas-hash and required leading zero bits in SHA256(proof).
- On success, server sets a
janus_tokenJWT cookie; future requests pass without challenge.
POST /janus/fingerprint— store client fingerprint (JSON).GET /janus/challenge— retrieve a challenge for the requesting IP.POST /janus/verify— submit proof; server validates and issuesjanus_tokenon success.GET /sensor.js— client-side sensor script.
- Create a
config.yamlthat setsdesktop_difficulty: 0andmobile_difficulty: 0to skip actual PoW while testing. - Start the server:
go run ./cmd/janus- Use a browser to visit
https://localhost:8080/(accept self-signed cert) and follow the challenge flow. - Or simulate with curl (example):
curl -k -X POST https://localhost:8080/janus/fingerprint \
-H "Content-Type: application/json" \
-d '{"client_ip":"127.0.0.1","canvas_hash":"test","isMobile":false}'
curl -k https://localhost:8080/janus/challenge
# use nonce/seed from response to craft a proof with difficulty 0 and POST to /janus/verify- Entrypoint: cmd/janus/main.go
- Middleware: internal/middleware/janus.go
- Challenge logic: internal/challenge/challenge.go
- Fingerprint types: internal/types/types.go
- Handlers: internal/handlers/handlers.go
Run basic checks locally:
go mod download
go vet ./...
go build ./...We welcome contributions! Please see CONTRIBUTING.md for how to open issues, propose changes, run tests, and follow our code style. All contributors must follow the Code of Conduct in CODE_OF_CONDUCT.md.
- Dashboard for live-suspicion scoring and metrics 📊
- Pluggable storage backends (Redis, Postgres) for scaling challenges 🗄️
- Prometheus metrics + Grafana dashboards 📈
- Optional WebSocket-based real-time challenge status channel ⚡
- Great for sites that need lower-friction bot defense than CAPTCHA (login forms, comment systems, scrapers), or as a second layer in a defense-in-depth strategy.
See LICENSE — permissive.