Skip to content

Releases: nugget/vanitykeygen

v0.2.1

Choose a tag to compare

@thane-developer thane-developer released this 09 May 17:00
0c3a6b4

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) or regex (a raw user-supplied regex
    validated at create time).
  • Match scope: fingerprint, pubkey, or both — 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, and capitalized. 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. handlePostMatch and
    handleCreateTarget now clear ID, Timestamp, CreatedAt, and
    TargetID on input. Match attribution always runs server-side. Target
    enums (type, match_scope, case_modes) are validated against
    allowed sets.
  • PUT means replace. handleUpdateTarget no longer field-merges with
    the existing row, so an empty label is now a real clear and a missing
    active no 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 match broadcast now sends a
    vkg.MatchSummary (id, target/client/hostname, timestamp, match
    metadata, public fingerprint) instead of the full Match. Private key
    material is only retrievable via GET /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 delegated addEventListener listeners and data-*
    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 check resp.StatusCode
    and surface non-2xx responses as wrapped errors. Per-seeker keyCount
    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.

Full Changelog

v0.2.0...v0.2.1

Streamlined API

Choose a tag to compare

@nugget nugget released this 14 Jun 04:56
cf54f86

v0.2.0 is where vanitykeygen starts to look like a real two-binary system
instead of a server with a satellite. The wire-level types live in their own
package, the HTTP layer picks up a real router, and the build pipeline gets
the polish it needs to actually ship containers.

9 files changed, 266 insertions, 93 deletions.

A shared schema package

The new pkg/vkg package is the single source of truth for the types that
flow over the wire — Key, Match, Target. Previously the server defined
them in its own package and the client carried near-duplicate copies; any
field shift had to be edited in lockstep on both sides, and JSON tags drifted
easily. With pkg/vkg, the client now imports the same struct the server
marshals, so the schema is enforced by the type system at build time.

This is a small change in line count (~30 lines moved) but a meaningful one
for the project's shape: it's the first time the codebase admits that the
client and server are peers sharing a contract, not a server with an embedded
client.

Gin for the HTTP server

The server's HTTP handlers move from raw net/http.HandlerFunc to
Gin (#2). A new pkg/server/api.go introduces
setupRouter, and the existing match-post / target-fetch handlers are
rewritten as gin.HandlerFunc. pkg/server/main.go loses about 80 lines of
manual JSON decode/encode and status-code plumbing in the process. Routes
themselves don't change shape, so existing clients keep working without any
API adjustments.

Build & deploy cleanup

The Makefile gets a thorough sweep so containers can actually ship. Separated
buildserver / buildclient targets each emit their own image under
nugget/vkg-server and nugget/vkg-client, OCI labels now carry version
metadata derived from git describe --always --long --tags --dirty, and a
new vkgstatic target produces a CGO-free binary suitable for the client
image. Local-development affordances runserver and runclient make the
inner-loop match the production layout, and editor scratch files are
properly .gitignored.

Full Changelog

v0.1.0...v0.2.0

0.1.0

0.1.0 Pre-release
Pre-release

Choose a tag to compare

@nugget nugget released this 14 Jun 01:30
383d60f

This barely scratches my itch. Star and revisit.