Repository navigation
v0.2.1 is the release where vanitykeygen stops being a match logger and
becomes a fleet. The wire protocol is rebuilt around persistent state, the
server grows an embedded web dashboard with live updates, target management
becomes a real subsystem with its own type system, and the build, CI,
container, and contributor surfaces all get the polish a real project needs.
The PR review pass that landed alongside the rewrite turned a working
prototype into something safe to point at the public internet.
33 files changed, 3,865 insertions, 600 deletions.
A new client/server protocol
Underneath the surface, this is a from-scratch rewrite of how the two
binaries talk (#4). The single match-log file and ad-hoc HTTP endpoints are
replaced by a REST API backed by an embedded SQLite database (pkg/store,
pure-Go via modernc.org/sqlite so the binary stays CGO-free). Targets,
matches, and connected clients are first-class persisted entities. The
server gives every client a stable ID derived from its hostname so restarts
don't fragment fleet history, computes per-client key rates from heartbeat
deltas, and reaps idle clients in the background. The API itself is
documented in the README's API table, with ?limit= on every list endpoint
so nothing is unbounded.
Gin is gone. The server is back on net/http using Go 1.22+'s
method-prefixed ServeMux patterns, which removes the Gin dependency tree
entirely without any loss of expressiveness for the routes in question.
Embedded web dashboard
The server now ships a single-binary web dashboard at port 8080 (assets
embedded via web/embed.go). Four tabs cover the operator workflow:
Dashboard with live aggregate stats, Targets for CRUD over patterns,
Matches for browsing recorded hits and revealing key material on
demand, and Fleet for watching connected clients and their per-client
key rates. Updates stream over Server-Sent Events on /api/events, with
periodic keep-alives so reverse proxies don't time out idle streams.
The frontend is intentionally dependency-free vanilla JS — a single
web/js/app.js, no build step, no framework — and the server-side
rendering of HTML is limited to the static index.html shell.
Targets become a real subsystem
Patterns gained the structure they needed to support a multi-user setup.
Targets now have:
- Type:
word(a literal substring the server compiles into a regex
with configurable affix anchors) orregex(a raw user-supplied regex
validated at create time). - Match scope:
fingerprint,pubkey, orboth— controls which key
representation the regex is tested against, both on the client during
search and on the server during attribution. - Case modes (for word targets): any combination of
insensitive,
sensitive, andcapitalized. The server compiles these into the
smallest set of regex groups that covers the requested behavior.
Match attribution moved server-side: clients report what they found, and
the server re-tests against active targets to assign the match to the right
target ID, so multi-target deployments end up with a clean per-target
match history. Deleting a target cascades to its matches, atomically, in
a single transaction.
Build, container, and CI
The Makefile is replaced by a justfile. Recipes are grouped (build,
test, run, container), just build cross-compiles to the four
release platforms (linux/amd64, linux/arm64, darwin/amd64,
darwin/arm64) with version metadata injected via -ldflags, and macOS
builds are ad-hoc codesigned when the host can do it.
Container publishing got a real package recipe that builds and pushes a
multi-arch image to ghcr.io/nugget/vanitykeygen with proper OCI labels,
and a release recipe that attaches a manifest to the matching GitHub
release. Docker compose files for both server and client landed alongside
new VKG_LISTEN_PORT / VKG_LISTEN_ADDRESS env vars so containerized
deployments can be configured without flag-juggling.
CI runs the full local gate (fmt-check, mod-tidy-check, vet,
golangci-lint, test, build-all) on every push and PR via a small
GitHub Actions workflow. The mod-tidy-check recipe in particular keeps
go.mod honest — drift between what the code actually imports and what's
declared will fail CI.
Security hardening (PR review pass)
A second pass after the initial overhaul addressed every issue the PR
review surfaced:
- No client-supplied server-owned fields.
handlePostMatchand
handleCreateTargetnow clearID,Timestamp,CreatedAt, and
TargetIDon input. Match attribution always runs server-side. Target
enums (type,match_scope,case_modes) are validated against
allowed sets. - PUT means replace.
handleUpdateTargetno longer field-merges with
the existing row, so an empty label is now a real clear and a missing
activeno longer silently flips it to false. The web UI was already
sending complete payloads; the merge logic was a footgun without a use
case. - SSE no longer leaks key material. The
matchbroadcast now sends a
vkg.MatchSummary(id, target/client/hostname, timestamp, match
metadata, public fingerprint) instead of the fullMatch. Private key
material is only retrievable viaGET /api/matches/{id}. - No CORS wildcard. The SSE handler dropped
Access-Control-Allow-Origin: *. Same-origin only. - Atomic cascade deletes with a 404 path when the target ID doesn't
exist, instead of silently returning 204 on missing IDs. - Web XSS closed. The dashboard replaced every inline
onclick=
handler with delegatedaddEventListenerlisteners anddata-*
attributes, so untrusted IDs can no longer execute as script even if
one ever made it into the DB. - Client honesty. All four client HTTP calls (
fetchTargets,
register,sendHeartbeat,reportMatch) now checkresp.StatusCode
and surface non-2xx responses as wrapped errors. Per-seekerkeyCount
accounting is fixed so the matching key is included and the post-match
reset doesn't silently bump the count back to one.
Project surfaces
The repo gained a real contributor guide. AGENTS.md documents build,
code conventions, architecture, security, and PR workflow at the project
level; CLAUDE.md is a thin Claude-specific operator layer that points to
it. The README itself was rewritten with an architecture diagram, the full
API reference, server/client flag tables, and container/release recipes.