v1.2.1
ken v1.2.1 — memory campaign + security & correctness hardening
The biggest release since 1.0. Three strands came together: a memory campaign that gives you real control over what ken indexes and how much RAM it holds, a full security & correctness review (two process-kill bugs and seven majors, each fixed with a regression test), and bounded, cancellable shutdown.
No breaking changes. Every public 1.0 signature is unchanged; new API and env vars are purely additive. Upgrading is a drop-in — no config edits required.
🔐 Security note: 1.2.1 is a same-day security fast-follow to 1.2.0. It bumps
golang.org/x/textto v0.39.0 to fixGO-2026-5970(an infinite loop on invalid input), whichgovulncheckflagged as reachable via the Postgres connect path and Unicode normalization. The advisory landed after 1.2.0 was tagged, so 1.2.0's binaries shipped the vulnerable version — use 1.2.1. Everything else below is the 1.2.0 feature set, unchanged.
🗂️ .kenignore — control what ken indexes
Your repo commits things you don't want searched: built JS/CSS bundles, generated migrations, vendored code. ken was gitignore-only, so on artifact-heavy monorepos it indexed roughly 2× the files it should have — inflating cold-start time and memory.
Drop a .kenignore (gitignore syntax, nested per-directory) to fix that:
web/assets/ # compiled bundles
vendor/ # third-party code
**/migrations/*.php # generated migrations
*.min.js- Union with
.gitignore— excluded if either matches; evaluated independently so a!negationin one can't re-include what the other dropped. .sembleignorehonored as a fallback — a drop-in for anyone migrating from semble.- Default-on and inert without the file — your existing
.gitignorebehavior is untouched. - Even without one, ken now auto-skips oversized files (
KEN_MAX_FILE_BYTES) and minified/generated files (KEN_MAX_AVG_LINE_BYTES).
On a committed-artifact monorepo this is the single biggest lever on index size, cold start, and RAM.
🧠 Lower, more predictable memory
For the long-lived ken-mcp server: GOGC=50 by default, an opt-in KEN_MEMLIMIT soft cap, and freed pages returned to the OS after builds and watch flushes — so idle RSS settles near the live index size instead of the build high-water. KEN_MAX_FILES rejects a hostile "millions of tiny files" repo rather than letting it OOM. A new scripts/rss_bench.sh measures it the same way external benchmarks do.
🔒 Security & correctness review
Every finding fixed and regression-tested:
top_kcould OOM-crash the whole server (Critical). An unboundedtop_kreached a giant allocation (and a filter path could overflow into a panic). Now clamped.- One source file could fatal-crash the server (Critical). The structural indexer parsed files with none of the size/stop-reason guards its sibling path had, so a pathological file could uncatchably overflow the goroutine stack. Both paths now share one guarded parser.
- DB passwords no longer leak in a malformed-DSN error (MySQL + Postgres), which also fixes passwords containing
/ @ ?. - Symlink-escape closed on the watch path — an out-of-root symlink can no longer be indexed.
- Plus: shutdown cleanup on stdin-EOF, cache eviction moved off the hot lock, watch-path enrichment parity, a lock-free-reader race in the reranker, and a batch of determinism/robustness fixes.
🔐 Verified model downloads
ken download-model and the background auto-fetch now verify each file as it streams — SHA-256 against HuggingFace's git-lfs ETag, and byte count against Content-Length / X-Linked-Size — so a truncated or swapped download is rejected before it lands. Concurrent fetches use a per-process temp file.
🛑 Graceful shutdown that can't hang
In-flight tool calls drain within KEN_MCP_SHUTDOWN_GRACE (default 5s), a second Ctrl-C force-quits, and a large in-flight index build now honors cancellation instead of blocking shutdown until it finishes.
New environment variables
| Variable | Default | Purpose |
|---|---|---|
KEN_MAX_FILE_BYTES |
2MiB |
Per-file size cap for indexing |
KEN_MAX_AVG_LINE_BYTES |
1000 |
Skip minified/generated files (0 disables) |
KEN_MAX_FILES |
1000000 |
Admission cap — reject huge/hostile repos (0 = unlimited) |
KEN_MEMLIMIT |
(unset) | Soft memory limit (overrides GOMEMLIMIT) |
KEN_MCP_SHUTDOWN_GRACE |
5s |
Bounded in-flight drain window on SIGINT/SIGTERM |
Install / upgrade
# Homebrew (macOS/Linux)
brew upgrade --cask ken # new: brew install --cask townsendmerino/tap/ken# Windows (Scoop)
scoop update ken # new: scoop bucket add townsendmerino https://github.com/townsendmerino/scoop-bucket; scoop install ken# Or with Go
go install github.com/townsendmerino/ken/cmd/ken@v1.2.1
go install github.com/townsendmerino/ken/cmd/ken-mcp@v1.2.1Nothing to change on upgrade. If you're on an artifact-heavy repo, adding a .kenignore is the one thing worth doing.
Full changelog: CHANGELOG.md · compare v1.1.1...v1.2.1