βββββββ ββββββ ββββ βββ ββββββ βββββββ βββ βββ
βββββββββββββββββββββ βββββββββββββββββββββββ ββββ
βββ ββββββββββββββ βββββββββββββββββββ βββββββ
βββ ββββββββββββββββββββββββββββββββββ βββββ
βββββββββββ ββββββ βββββββββ ββββββ βββ βββ
ββββββββββ ββββββ ββββββββ ββββββ βββ βββSelf-hosted honeytoken generator. Mints seven kinds of tripwire artifacts β invisible web bugs, booby-trapped PDF/DOCX files, fake
.envand kubeconfig credentials, and a real MySQL wire-protocol decoy β then alerts you on Telegram or a webhook the moment an attacker touches one.
This is a quick overview β security theory, architecture, and full walkthroughs are in the learn modules.
- Seven token types, each disguised as something an attacker would actually try to use:
webbug,slowredirect,pdf,docx,envfile,kubeconfig, and a realmysqllistener that speaks the MySQL v10 handshake - Per-token Telegram or webhook alerts the instant a token fires (HMAC-signed for webhooks)
- Async notification worker pool with per-channel timeouts and dedup gating (15-minute Redis silence window per
{token, source_ip}so a curious attacker reloading the page doesn't spam you) - GeoIP enrichment via MaxMind GeoLite2 (country, region, city, ASN, ASN org) attached to every event
- Browser fingerprint capture for
slowredirecttokens via a 3-second JS-collection interstitial before the redirect resolves - Public manage URL (UUID-gated) so you can share a single link with a teammate to view triggers without exposing operator credentials
- Operator-only admin API (constant-time bearer comparison) for global stats, token listing, and force-disable
- Cloudflare Turnstile on token creation, dual-window rate limiting (per-minute + per-hour) keyed by browser fingerprint
- Optional Cloudflare Tunnel overlay β expose the service publicly without opening a port or maintaining a TLS cert
- Defense-grade observability: OpenTelemetry traces, slog structured logs,
/healthzliveness, graceful shutdown with load-balancer drain delay
just init # generates .env, .env.development, randomised ports, operator token
just dev-up # launches nginx + Vite HMR + Go (Air hot-reload) + Postgres + Redis + JaegerOpen the URL printed by just init (typically http://localhost:22784). Mint a token, watch the manage page, then trigger it from another tab and refresh.
or the live demo at iglowinthedark.com
Tip
This project uses just as a command runner. Type just to see every available recipe grouped by area.
Install: curl -sSf https://just.systems/install.sh | bash -s -- --to ~/.local/bin
| Type | Artifact | Trigger Mechanism | Where You'd Plant It |
|---|---|---|---|
webbug |
URL to a 1x1 JPEG | Any HTTP GET on the URL | HTML emails, internal wikis, "do-not-touch" docs |
slowredirect |
URL with a delayed redirect | Click-through; runs fingerprint JS before redirecting to a real destination | Phishing-bait links in honeyfile chat threads, fake admin panel URLs |
pdf |
Patched PDF | Acrobat opens, fires /AA /O /URI page-open action |
payroll-q3.pdf on a shared drive, vpn-creds.pdf on a workstation |
docx |
Patched Word doc | Word/LibreOffice loads the footer, which contains a remote URI | customer-list.docx, passwords.docx in Documents/ |
envfile |
Plain-text .env |
Attacker curls the fake INTERNAL_METRICS_ENDPOINT baked into the file |
Repository roots, ~/.config/, container /app/ directories |
kubeconfig |
YAML kubeconfig | Attacker runs kubectl --kubeconfig=stolen.yaml ... and our server logs the bearer token |
~/.kube/config, ops engineer laptops, CI runner home dirs |
mysql |
mysql://... connection string |
Attacker connects with mysql CLI; our TCP listener replies with a real MySQL v10 handshake and an Access denied packet |
.env files, database.yml, internal wiki snippets |
The envfile generator is the densest of the bunch. It picks recipes from aws.go, db.go, github.go, and stripe.go, shuffles the resulting sections, and buries a single canary line (INTERNAL_METRICS_ENDPOINT=https://your-host/c/{tokenID}) among plausible production config. The attacker harvesting the file gets a fistful of fake secrets to chase and trips the wire as soon as one of those secrets is touched.
Token creation, manage view, and admin are mounted under /api/. Trigger routes live at the root so artifacts can carry short URLs.
| Method | Path | Auth | Purpose |
|---|---|---|---|
POST |
/api/tokens |
Turnstile + rate limit | Mint a new token (type, memo, alert_channel, channel config, type-specific metadata) |
GET |
/api/tokens/types |
Public | List available token types and their metadata schemas |
GET |
/api/m/{manageId} |
Manage UUID | Token details + paginated event feed + dedup silence counter |
DELETE |
/api/m/{manageId} |
Manage UUID | Soft-disable the token (events stop, history retained) |
GET |
/api/admin/stats |
Bearer | Tokens count, events count, breakdowns by type and alert channel |
GET |
/api/admin/tokens |
Bearer | All tokens (offset paginated) |
POST |
/api/admin/tokens/{id}/disable |
Bearer | Force-disable any token |
GET |
/healthz |
Public | Liveness + readiness probe (used by Docker healthchecks) |
GET |
/c/{tokenID} |
Public | Trigger route. Records event, fires notification, returns artifact body (pixel, GIF, HTML interstitial, etc.) |
POST |
/c/{tokenID}/fingerprint |
Public | Receives JSON fingerprint payload from the slowredirect interstitial |
* |
/k/{tokenID}[/*] |
Public | Kubeconfig trigger β matches kubectl's wildcard API paths |
The MySQL listener does not run on HTTP. It's a separate TCP server bound to a configurable address; an attacker using the connection string from the artifact will speak the MySQL wire protocol with our protocol.go handshake builder before getting denied.
Backend: Go 1.25, chi router, pgx + sqlx, goose migrations, koanf config, slog, validator/v10, OpenTelemetry, pdfcpu, MaxMind GeoLite2, miniredis (tests), testcontainers (integration)
Frontend: React 19, TypeScript, Vite, TanStack Query, Zod, Axios, Biome, Stylelint
Storage: PostgreSQL 18 (tokens, events tables; INET + JSONB columns), Redis 7 (dedup gate + rate-limit token buckets)
Infra: Docker Compose (dev: nginx + Vite HMR + Air + Postgres + Redis + Jaeger; prod: nginx + Go binary + Postgres + Redis), optional cloudflared overlay
canary-token-generator/
βββ backend/
β βββ cmd/canary/ main.go β wiring, signal handling, MySQL listener spawn, retention loop
β βββ internal/
β βββ token/ Service, repository, handler, generator interface
β β βββ generators/
β β βββ webbug/ Embedded JPEG pixel
β β βββ pixel/ Shared 1x1 GIF helper used by every "visited" response
β β βββ pdf/ Byte-exact PDF placeholder substitution (76-byte URL window)
β β βββ docx/ ZIP-aware footer rewrite
β β βββ envfile/ Recipe shuffler + canary line injection
β β β βββ recipes/ aws.go, db.go, github.go, stripe.go
β β βββ kubeconfig/ text/template renderer + wildcard /k/ handler
β β βββ mysql/ protocol.go (handshake + auth + err packets), server.go (TCP), handler.go
β β βββ slowredirect/ HTML interstitial + fingerprint POST handler
β βββ event/ Event entity, service (geo enrich β insert β dedup β notify), repository
β βββ notify/ Worker pool, queue, status writer
β β βββ webhook/ HMAC-signed POSTs with exponential backoff
β β βββ telegram/ Bot API client
β βββ middleware/ request_id, logging, recovery, realip, fingerprint, ratelimit, turnstile, operator_bearer, headers
β βββ geoip/ MaxMind MMDB lookup wrapper (nop when no DB present)
β βββ turnstile/ Cloudflare Turnstile siteverify
β βββ admin/ Stats, listing, force-disable
β βββ core/ DB pool, Redis client, migrations, telemetry, errors, validation, response envelopes
β βββ health/ /healthz handler with readiness/shutdown flags
β βββ server/ chi router shell with graceful shutdown + drain delay
βββ frontend/
β βββ src/
β βββ pages/landing/ Token creation form (type-aware metadata, Turnstile widget, artifact reveal)
β βββ pages/manage/ Token detail + event table (cursor paginated, GeoIP cells, dedup silence)
βββ infra/
β βββ nginx/ prod.nginx, dev.nginx (Vite proxy)
β βββ docker/ Dockerfiles for prod binary, Air hot-reload, Vite HMR
βββ compose.yml Production stack
βββ dev.compose.yml Dev stack with Jaeger
βββ cloudflared.compose.yml Tunnel overlay
βββ justfile Recipes grouped by frontend / backend / lint / compose / tunnel / dev / util
βββ learn/ You are here
This project includes step-by-step learning materials covering deception theory, token mechanics, system design, and a code walkthrough.
| Module | Topic |
|---|---|
| 00 - Overview | Prerequisites, quick start, project structure |
| 01 - Concepts | Honeytokens, deception defense, Thinkst Canary, MITRE Engage, real breaches |
| 02 - Architecture | System design, request lifecycle, schema, dedup gate, notification pipeline |
| 03 - Implementation | Code walkthrough: generators, trigger handler, event service, MySQL protocol |
| 04 - Challenges | Extension ideas β new token types, alert channels, evasion-resistance |
AGPL 3.0