TallmanCode Logo

Building a Local-First Secret Generator: A React, TypeScript & Trust-Model Case Study

keybrew-header.webp

Demo:

View

O1. The Challenge

Fellow developers, we all need strong secrets constantly: API keys, JWT salts, webhook tokens, Postgres passwords, the whole pile that eventually lands in .env. And the tools out there for this tend to pull in one of two directions.

Either they're throwaway generators with murky entropy and no real paste-into-project workflow, or they've rebranded themselves as "credential platforms" and quietly signed you up for sync theater you never asked for.

I wanted something that actually matched how I work day to day:

  • Generate in the browser with real Web Crypto, not a black box
  • Fill a single value, a batch, or a full multi-key pack without ceremony
  • Stay honest about what actually reaches a server
  • Ship as a real, usable product, not a polished concept mock

That's how KeyBrew came together: a local-first secret generator, and honestly, a chance for me to think hard about trust, monorepos, and what it actually takes to ship something real.

keybrew-demo-image-1.webp
keybrew-demo-image-2.webp

O2. The Solution

KeyBrew generates cryptographically strong secrets built for real project work. Client-side generation is the default, not an afterthought. Optional accounts sync your presets and preferences only, never your secrets or your history. There's no analytics running in the background either.

A monorepo from day one

Here's the thing: crypto logic duplicated between a UI and an API is crypto logic that eventually drifts. So the product is split to avoid exactly that:

  • apps/web — React + Vite generator UI (Tailwind v4, shadcn/ui)
  • apps/api — Express API for health, config, profiles, auth, and preset sync
  • packages/shared — token profiles, the CSPRNG seam, presets, strength helpers, Zod options

The shared package is the source of truth. The web app generates locally, the API handles public profile metadata and optional sync, and a tooling-only generate route exists for scripts and tests, staying off in production unless you flip it on yourself.

Entropy that travels with the package

I built one small, well-tested seam around the browser's native crypto source, and that's the only thing in the whole codebase allowed to touch randomness directly. It runs the same way in the browser and in Node during tests, so there's never a gap between what gets tested and what actually ships.

I'm intentionally not walking through the internals here. The less detail floating around about exactly how a generator produces its randomness, the better, even when the approach itself is solid. What matters for this write-up is the design principle: isolate the one thing that has to be correct, test it hard, and let everything else in the app depend on it instead of reimplementing it.

That same profile logic in packages/shared drives what renders in apps/web and what the API validates, so client and server never disagree about what a "strong" secret actually looks like.

Presets, not just character soup

Here's where things get genuinely useful. KeyBrew ships opinionated presets for the secret shapes developers actually reach for:

  • Hex secrets at common bit strengths
  • Base64url tokens sized for JWT-style use
  • UUID v4 identifiers
  • Structured API-key formats with a prefix
  • Passphrases built from a wordlist, not raw character noise

Each preset carries its own sensible default length and charset, so picking "JWT signing secret" gets you a sane starting point instead of a blank slider and a guess.

Strength you can see, not just claim

Rather than another fake "strength meter" powered by regex heuristics, KeyBrew computes real entropy from the actual generation parameters: charset size and length for character-based secrets, wordlist size and word count for passphrases. The UI shows the bit count and what that actually means in practice, not a colored bar pretending to understand your password strategy.

O3. Key Features

Multiple Generation Modes

  • Single value generation for a quick secret
  • Batch mode for generating many secrets at once
  • Multi-key packs for scaffolding a full .env in one pass

Trust-First Architecture

  • Client-side generation by default; secrets don't have to touch a server
  • Tab-scoped history, not a synced secret log
  • Optional account sync covers structure (presets, preferences), never values
  • No analytics, full stop

Shared Core

  • One CSPRNG seam used in the browser and in tests
  • One set of token profiles driving both UI and API validation
  • Zod schemas keeping options honest end to end

Developer-Shaped Presets

  • Common secret shapes ready to go: hex, base64url, UUID v4, prefixed API keys, wordlist passphrases
  • Sensible defaults per preset instead of generic sliders

O4. Technical Highlights

Why client-side generation comes first

Web Crypto's getRandomValues is a CSPRNG sitting in every modern browser already. There's no good reason to round-trip a secret through a network request when the primitive lives right where the user is typing. Server-side generation becomes an option for specific flows, not the default posture.

Why a shared package beats duplicated logic

Token profiles, presets, and strength math all live in packages/shared, so apps/web and apps/api consume the exact same definitions. When a preset's default length changes, it changes once, everywhere.

Why sync stays narrow, on purpose

Stage 6 added accounts and sync, deliberately scoped to presets and preferences. Secrets and generation history are explicitly out of scope for sync. That's what keeps the honest-by-default promise intact, instead of KeyBrew quietly becoming "yet another vault" nobody asked for.

The Result

KeyBrew is:

  • Live and usable for real .env work and day-to-day secret generation
  • Backed by a shared, tested CSPRNG and token-profile core
  • Honest about its trust boundary: local generation by default, narrow optional sync
  • Structured as a monorepo so the eventual Chrome extension (Stage 7) reuses packages/shared instead of reinventing it

Lessons Learned

  1. Decide the trust model before writing the generator. Client-side-by-default shaped nearly every architectural decision that followed.
  2. Share the core, not just the types. Putting the CSPRNG and token profiles in one package kept the UI and API from quietly drifting apart.
  3. Presets beat sliders. Naming the shape ("JWT signing secret") is more useful than exposing every knob by default and hoping for the best.
  4. Real entropy beats a strength meter. Computing bits from charset and length is honest. A colored bar driven by regex isn't.
  5. Scope sync on purpose. Deciding what never leaves the browser matters just as much as deciding what does.

Conclusion

KeyBrew is a working answer to a small but constant developer annoyance: generating a secret that's actually strong, without pulling in a full vault product to do it. Built as a React and TypeScript monorepo with a shared entropy core, it's also a case study in scoping trust honestly, from client-side generation all the way to a sync layer that stays narrow by design.

The Chrome extension stage is next, and it'll lean on that same shared package, so the trust model doesn't change just because the surface does.

Technologies Used: React, TypeScript, Vite, Express, Zod, Web Crypto API, Tailwind CSS v4, shadcn/ui Key Skills Demonstrated: Monorepo Architecture, Cryptographic Design Decisions, Shared Package Strategy, Trust-Model Product Thinking, API Design