Changelog
All notable changes to Kerstel are listed here. Versions follow Semantic Versioning. Kerstel is pre-1.0: minor releases may change behaviour.
-
0.1.5
In development2 changes
Changed
- When a key's value in your
.envdiffers from the one already in the vault,kerstel initnow asks which one stays instead of overwriting the vault's. The vault's value stays by default and under--yes, and whichever value loses is kept in the encrypted backup, sokerstel uninstallnames it before deleting the backups. A plain key already stored in this project's vault now points at that entry instead of being stored again, and so does one the vault shared by all your projects holds with the same value. A shared value that differs is left alone and the key stays with this project, so a project is never pointed at another project's account or database without asking; pass--global KEYto choose the shared value.
Fixed
- Two checkouts of one package, such as git worktrees, are now registered separately. Running
initin the second no longer replaces the first, a value the vault already holds is reused rather than stored again, andkerstel uninstallrestores both.ks movekeeps a project copy while any registered checkout still reads it. If a different package already uses the scopeinitwould derive,initstops and names that package instead of sharing its secrets by accident.
- When a key's value in your
-
0.1.4
LatestAdded
kerstel movechanges where a key lives afterinit: between this project's vault, the vault shared by all your projects, and plain text. Run it with no arguments to pick keys from a list, orks move KEY --to global|project|plaintext --yesfrom a script. The env files are backed up first, and a vault value it removes or replaces, or a plain value that loses to the vault's, is saved in the backup;uninstallnames any such value that exists nowhere else before it deletes the backups. A value with no plain-text spelling that fits its line's quoting stays in the vault, named, instead of being written back changed.initnow points you to it after it finishes, so changing your mind about a key later doesn't mean re-runninginit.
Security
- When no OS credential store answers and Kerstel falls back to keeping the vault key in
~/.kerstel/vault.key, every command that opens the vault now warns about it on stderr, not onlykerstel doctor. SettingKERSTEL_KEYCHAIN_BACKEND=fileyourself says the file is intended and turns the warning off. The command that creates a vault key also says so, and where it put it. kerstel update,--version,doctorandinstall.shrefuse aKERSTEL_RELEASES_URLorKERSTEL_DOWNLOAD_BASEthat is nothttps://, naming the variable, and a download that redirects away fromhttps://. Over plain HTTP a mirror's binary and checksum could both be swapped in transit.http://to127.0.0.1orlocalhoststill works for local mirrors.updatenow says which mirror it uses, andinstall.shrejects aKERSTEL_VERSIONthat is not a version like0.1.0.kerstel initno longer prints a credential in full because its name starts withPUBLIC_,VITE_orNEXT_PUBLIC_. Those prefixes are bundler namespaces, not a promise that the value is public, so a key whose name says it is a secret (VITE_SUPABASE_KEY,NEXT_PUBLIC_API_KEY), a value with a known token prefix (sk_,AIza,ghp_, and the like), and a URL with a password, a credential-named query parameter or a token-like path segment in it are now shown only as their length, whatever the key is called.PORT=3000and other configuration values still show as they are.
-
Added
- Wired scripts now work on deploy hosts.
kerstel initwrites a small launcher at.kerstel/exec.cjs, meant to be committed, and wires each command asnode .kerstel/exec.cjs -- <command>. On your machine the launcher runskerstel exec; on Vercel, Netlify, Railway, Fly, Heroku, a Docker build, or any other host with no Kerstel, it printskerstel: not installed here, running without it: <command>and runs the command unchanged, sonpm run buildandnpm startno longer fail withkerstel: command not found. It never picks akerstelfromnode_modules/.bin. Re-runninginitconverts scripts wired by an older Kerstel, rewrites a launcher that is out of date or edited, and warns when.gitignorehides.kerstel/.kerstel doctorgains a Launcher row and names scripts still in the old form;kerstel uninstalldeletes the launcher. The new Deploying page covers what each host runs, where the values come from, Turborepo's pass-through list, and Docker.
Changed
kerstel updateshows a progress bar while the binary downloads, with the size received over the total, and names each step: downloading, verifying the checksum, installing. Before, it printed one line and then nothing until the swap was done. Piped, it prints plain lines, as the installer does.
Fixed
kerstel initnow wires every command of a script, not only the first. Before,node copy.mjs && next devbecamekerstel exec -- node copy.mjs && next dev, sonext devstarted without the hook and read the literalkerstel://reference, and a script starting withNODE_ENV=xbroke because the assignment became the command. Now each command gets the wrapper after its leadingNAME=valuewords, commands that never run JavaScript (rm,echo,mkdir, and the like) are left alone, and a scriptinitcannot wire safely (one withcd, a redirection, a subshell, or an unbalanced quote) is skipped and named with the reason. Re-runninginitfinishes a script that was wired only in part.kerstel doctornames partly wired and skipped scripts instead of counting them as wired, andkerstel uninstallunwraps every command.- Under Bun,
Bun.envnow returns the real value of akerstel://reference, asprocess.envalready did.Bun.envis the raw environment object and cannot be wrapped, so the runtime hook resolves every reference in the environment when a Bun process starts and writes the value back;process.envkeeps resolving lazily, so a reference assigned later still works. A reference the daemon cannot resolve is left in place with one warning naming the variable, and reading it throughprocess.envfails the way it did before. Under Node nothing changes. kerstel execandkerstel runnow say what to do when the command they were given is not onPATH: they name the executable, and in a project they name the install command for its package manager (bun install,npm install,pnpm install, oryarn install), saying outright whennode_modulesis missing altogether. They exit127, as a shell does. Before, a wirednpm run devin a fresh clone failed with Bun's ownExecutable not found in $PATHand exit1.
- Wired scripts now work on deploy hosts.
-
Security
- Kerstel no longer takes its own settings from the project it is protecting. Kerstel is a Bun binary, so it loads the
.envof the directory you run it in, and Kerstel's model is that this file is committed: a repository you cloned could setKERSTEL_HOMEto a directory inside itself, force the weaker key-file credential store, or stop the daemon ever relocking. AnyKERSTEL_*that an env file there names is now ignored and named on stderr, whatever its value, so a setting you made yourself is ignored too while that line is in the file. The resolver daemon runs from~/.kerstel, so no env file reaches it. Set these in your shell or shell profile instead. - The daemon's session token no longer travels in any environment.
kerstel execnow gives a wired scriptKERSTEL_TOKEN_FILE, the path of the0600token file, instead ofKERSTEL_TOKEN, and the runtime hook reads the file when it needs it. Before, the token sat in every hooked process's environment and every child's, whereconsole.log(process.env), a crash reporter, orps -Ecould show it, and it was never rotated. The daemon now mints a fresh token every time it starts and deletes it when it stops, so no valid token exists while nothing is listening. A running app keeps working across a daemon restart. AKERSTEL_TOKENinherited from a process an older Kerstel had hooked is dropped rather than passed on. - The resolver daemon now starts with a minimal environment of its own (
PATH,HOME, locale, the Linux session bus, and Kerstel'sKERSTEL_*settings) instead of inheriting the caller's, and akerstel daemon serverun by hand re-executes itself through the same list before opening the vault. Before,BUN_OPTIONSset in your shell could run a preload inside the process that holds the vault key, and under a nestedkerstel execthe daemon inherited every already-resolved secret in its environment.kerstel doctornow warns whenBUN_OPTIONSis set. kerstel initandkerstel execrefuse, andkerstel doctorreports a problem, whennode_modules/.binin the project or any parent directory holds akerstelorks. npm and bun put that directory first onPATHwhen they run a script, so a dependency declaring such abinwould run in place of Kerstel for every wired script, with access to the vault. The message names the file and how to find the dependency that installs it.- The OS credential-store helpers (
securityon macOS,secret-toolon Linux) are now run only from a fixed list of system directories, and only when the directory and the file are owned by root and writable by nobody else, never from the caller'sPATH.npm runandbun runputnode_modules/.binfirst onPATH, so before this a dependency could ship a fakesecurityand read the vault's data key the next time a wired script opened the vault. If the tool is missing from those directories, the error says how to install it there or switch to the key file.
Changed
- The changelog page keeps the unreleased section collapsed until you expand it, and marks the newest release as the latest.
Fixed
kerstel initno longer mistakes key material for a variable name. An unquoted multi-line PEM block is left untouched as one value and named by its key; a base64 line, or a name too long to be a variable, is reported by line number and never printed; and every other line that is notKEY=valuenow counts as plaintext remaining. Before, the last line of a pasted private key became a key name that was printed, stored, and written back, while the wizard called the file safe to commit.- Kerstel never creates a new vault key while a vault sealed with one exists. Before, on Linux, a Secret Service that failed to answer for a moment (a D-Bus hiccup, an agent still starting, a session with no bus) read as "no key stored", a fresh key was minted and
secret-tool storesilently replaced the real one, and every secret and backup became unreadable. Now a lookup that fails for any reason other than "not found" counts as "a key is stored", a vault with a key check or any secrets refuses to mint at all, andinitseals its backup with the key the vault is already open with instead of fetching it a second time. kerstel initno longer treats backup copies such as.env.bak,.env.orig,.env.swp, or.env.local~as env files. Before, a stale value in.env.bakoutranked the live one in.envand went into the vault, and a Vim swap file was rewritten in place. Each skipped copy is now named, with a reminder that it may still hold plaintext, and to rename it if it is a real env file.
- Kerstel no longer takes its own settings from the project it is protecting. Kerstel is a Bun binary, so it loads the
-
Added
kerstel updateinstalls the latest release in place: it downloads the binary for your platform, verifies its checksum against the release'sSHA256SUMS, confirms the new binary reports the expected version, and then swaps it in.--checkonly tells you whether a newer release exists.kerstel --versionon a terminal says whether you're up to date, and names the newer release when there is one. Piped, it still prints only the version and never touches the network.kerstel doctorstarts with a version row: up to date, a newer release withFix: kerstel update, or a note that the release page could not be reached.
Changed
- The changelog on kerstel.dev is a timeline, one entry per release, with the 0.1.0 release video at the top.
- The installer shows a progress bar while downloading, names your platform, ticks off each step, and ends with a short note on what Kerstel does and the three commands to run next. Piped output stays plain, and
NO_COLORis respected. kerstel doctorno longer tells you to start the daemon by hand. An idle daemon is normal, since it starts on its own the first time a script needs a secret, sodoctornow reports it as information rather than a warning. Outside a project, or in one that isn't set up yet,doctorends with a short "How it works" note.
Fixed
- The
kerstel initoverview strips Unicode bidi and zero-width characters from the config values it shows, so a value can no longer reorder or hide the text around it in the terminal. Secret values were never shown. - When a session's credential store is not the one holding the vault key, the error now gives advice for the store that actually holds it. A vault keyed in a file names the key file and says to run with
KERSTEL_KEYCHAIN_BACKEND=file, instead of telling you to unlock the Keychain. kerstel uninstallremoves everykerstelandkslink that points at the binary, wherever they are onPATHor in the folder it was run from. Before, whenkerstelon yourPATHwas itself a link to the binary, both it and theksshortcut beside it were left dangling.
-
The first release: a local-first secrets manager for Node and Bun projects.
Added
- Encrypted local vault at
~/.kerstel/vault.db. Every value is sealed with AES-256-GCM and its own random nonce. - Vault data key kept in the OS credential store: the Keychain on macOS and Secret Service on Linux. Linux machines without a Secret Service provider fall back to a
0600key file, with a warning. kerstel://<scope>/<KEY>references, so.envfiles hold nothing secret and are safe to commit.kerstel set,get,ls, andrmto manage secrets.getprints plaintext only with--reveal.kerstel run -- <command>to run any command with every reference resolved up front.kerstel resolveto print one resolved value.- Resolver daemon (
kerstel daemon serve|start|stop|status) that unlocks the vault once and serves resolutions over a local socket. - Runtime hook for Node and Bun that resolves references lazily from
process.env, with no wrapper command. kerstel doctorto diagnose the vault, the credential store, the daemon, and the hook assets. Inside a project it also reports the scope, the runtime, how many scripts are wired, and how many references this vault can resolve.kerstel init, a setup wizard that moves a project's.envvalues into the vault. It sums up what will change in each file, shows the full masked diff on request, asks once before writing, and backs up the originals encrypted. It rewrites only the values, keeping comments, order, and quoting intact. Then it wires the runtime hook into yourpackage.jsonscripts, sonpm run devstaysnpm run dev.kerstel initon a cloned project that already uses Kerstel prompts for each key your vault is missing, with the echo turned off. Nothing is stored until you apply the plan.kerstel initflags for scripts and CI:--dry-run(writes nothing, not even to~/.kerstel),--yes,--scope,--global,--keep,--non-interactive, and--from-stdin.--keepor--globalnaming a key no env file defines gets a warning.kerstel initsuggests the vault for any key whose name marks a credential, such asPASSWORD,PASS,PWD,PIN,PASSPHRASE,TOKEN,SECRET, or any*_KEY, even when the value is a number likePIN=4821.kerstel exec -- <command>, which runs one command with the runtime hook wired in.kerstel initwrites it into your scripts.- kerstel.dev with getting-started, CLI, resolution, teams, and security docs, plus the changelog and the roadmap.
kerstel --version.kerstel uninstall, which restores every project's.envvalues in their original quoting, package scripts, and.gitignore, then removes~/.kerstel, the vault key, and the binary. It refuses, naming each one, if any secret would be lost, including a valueinitkept only in its encrypted backup. Afterwards it names every restored.envfile git still tracks.- Release binaries for macOS and Linux (x64 and arm64), with SHA-256 checksums.
curl -fsSL https://kerstel.dev/install.sh | bash, which verifies the checksum and installs to~/.local/binwithoutsudo.ks, a shortcut forkerstel, installed byinstall.shwhen nothing else is calledks, and removed byuninstall.kerstel doctor --verboseshows the paths and permissions behind each check.
Changed
kerstel initshows every variable and where it would go, then lets you accept the suggestions with Enter, change a few, or go one by one, with arrow-key menus. Values appear only as their length, except configuration likePORT=3000that stays in plain text.kerstel doctorgroups its checks, shows how to fix each warning and problem, and exits 1 when something is wrong.- Bare
kerstellists the commands and exits 0.
- Encrypted local vault at