CyberLearn
← Back to beginner
Beginner

Canary Token Generator

Self-hosted honeytokens that alert on access

Log in to track progress
2–3hGoReactDocker

What you'll learn

  • Deception defense
  • Honeytokens
  • MySQL wire protocol
  • PDF/DOCX patching
  • Webhook + Telegram alerting
 ██████╗ █████╗ ███╗   ██╗ █████╗ ██████╗ ██╗   ██╗
██╔════╝██╔══██╗████╗  ██║██╔══██╗██╔══██╗╚██╗ ██╔╝
██║     ███████║██╔██╗ ██║███████║██████╔╝ ╚████╔╝
██║     ██╔══██║██║╚██╗██║██╔══██║██╔══██╗  ╚██╔╝
╚██████╗██║  ██║██║ ╚████║██║  ██║██║  ██║   ██║
 ╚═════╝╚═╝  ╚═╝╚═╝  ╚═══╝╚═╝  ╚═╝╚═╝  ╚═╝   ╚═╝

Cybersecurity Projects Go React PostgreSQL Redis License: AGPLv3 Docker MITRE Engage

Live Demo

Self-hosted honeytoken generator. Mints seven kinds of tripwire artifacts — invisible web bugs, booby-trapped PDF/DOCX files, fake .env and 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.

What It Does

  • Seven token types, each disguised as something an attacker would actually try to use: webbug, slowredirect, pdf, docx, envfile, kubeconfig, and a real mysql listener 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 slowredirect tokens 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, /healthz liveness, graceful shutdown with load-balancer drain delay

Quick Start

just init       # generates .env, .env.development, randomised ports, operator token
just dev-up     # launches nginx + Vite HMR + Go (Air hot-reload) + Postgres + Redis + Jaeger

Open 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

Token Types

TypeArtifactTrigger MechanismWhere You'd Plant It
webbugURL to a 1x1 JPEGAny HTTP GET on the URLHTML emails, internal wikis, "do-not-touch" docs
slowredirectURL with a delayed redirectClick-through; runs fingerprint JS before redirecting to a real destinationPhishing-bait links in honeyfile chat threads, fake admin panel URLs
pdfPatched PDFAcrobat opens, fires /AA /O /URI page-open actionpayroll-q3.pdf on a shared drive, vpn-creds.pdf on a workstation
docxPatched Word docWord/LibreOffice loads the footer, which contains a remote URIcustomer-list.docx, passwords.docx in Documents/
envfilePlain-text .envAttacker curls the fake INTERNAL_METRICS_ENDPOINT baked into the fileRepository roots, ~/.config/, container /app/ directories
kubeconfigYAML kubeconfigAttacker runs kubectl --kubeconfig=stolen.yaml ... and our server logs the bearer token~/.kube/config, ops engineer laptops, CI runner home dirs
mysqlmysql://... connection stringAttacker 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.

HTTP API

Token creation, manage view, and admin are mounted under /api/. Trigger routes live at the root so artifacts can carry short URLs.

MethodPathAuthPurpose
POST/api/tokensTurnstile + rate limitMint a new token (type, memo, alert_channel, channel config, type-specific metadata)
GET/api/tokens/typesPublicList available token types and their metadata schemas
GET/api/m/{manageId}Manage UUIDToken details + paginated event feed + dedup silence counter
DELETE/api/m/{manageId}Manage UUIDSoft-disable the token (events stop, history retained)
GET/api/admin/statsBearerTokens count, events count, breakdowns by type and alert channel
GET/api/admin/tokensBearerAll tokens (offset paginated)
POST/api/admin/tokens/{id}/disableBearerForce-disable any token
GET/healthzPublicLiveness + readiness probe (used by Docker healthchecks)
GET/c/{tokenID}PublicTrigger route. Records event, fires notification, returns artifact body (pixel, GIF, HTML interstitial, etc.)
POST/c/{tokenID}/fingerprintPublicReceives JSON fingerprint payload from the slowredirect interstitial
*/k/{tokenID}[/*]PublicKubeconfig 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.

Stack

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

Project Layout

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

Learn

This project includes step-by-step learning materials covering deception theory, token mechanics, system design, and a code walkthrough.

ModuleTopic
00 - OverviewPrerequisites, quick start, project structure
01 - ConceptsHoneytokens, deception defense, Thinkst Canary, MITRE Engage, real breaches
02 - ArchitectureSystem design, request lifecycle, schema, dedup gate, notification pipeline
03 - ImplementationCode walkthrough: generators, trigger handler, event service, MySQL protocol
04 - ChallengesExtension ideas — new token types, alert channels, evasion-resistance

License

AGPL 3.0