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.0 The first release, in 22 seconds. Read the notes
  1. 0.1.5

    In development
    2 changes

    Changed

    • When a key's value in your .env differs from the one already in the vault, kerstel init now 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, so kerstel uninstall names 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 KEY to choose the shared value.

    Fixed

    • Two checkouts of one package, such as git worktrees, are now registered separately. Running init in the second no longer replaces the first, a value the vault already holds is reused rather than stored again, and kerstel uninstall restores both. ks move keeps a project copy while any registered checkout still reads it. If a different package already uses the scope init would derive, init stops and names that package instead of sharing its secrets by accident.
  2. 0.1.4

    Latest

    Added

    • kerstel move changes where a key lives after init: 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, or ks move KEY --to global|project|plaintext --yes from 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; uninstall names 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. init now points you to it after it finishes, so changing your mind about a key later doesn't mean re-running init.

    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 only kerstel doctor. Setting KERSTEL_KEYCHAIN_BACKEND=file yourself 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, doctor and install.sh refuse a KERSTEL_RELEASES_URL or KERSTEL_DOWNLOAD_BASE that is not https://, naming the variable, and a download that redirects away from https://. Over plain HTTP a mirror's binary and checksum could both be swapped in transit. http:// to 127.0.0.1 or localhost still works for local mirrors. update now says which mirror it uses, and install.sh rejects a KERSTEL_VERSION that is not a version like 0.1.0.
    • kerstel init no longer prints a credential in full because its name starts with PUBLIC_, VITE_ or NEXT_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=3000 and other configuration values still show as they are.
  3. 0.1.3

    Added

    • Wired scripts now work on deploy hosts. kerstel init writes a small launcher at .kerstel/exec.cjs, meant to be committed, and wires each command as node .kerstel/exec.cjs -- <command>. On your machine the launcher runs kerstel exec; on Vercel, Netlify, Railway, Fly, Heroku, a Docker build, or any other host with no Kerstel, it prints kerstel: not installed here, running without it: <command> and runs the command unchanged, so npm run build and npm start no longer fail with kerstel: command not found. It never picks a kerstel from node_modules/.bin. Re-running init converts scripts wired by an older Kerstel, rewrites a launcher that is out of date or edited, and warns when .gitignore hides .kerstel/. kerstel doctor gains a Launcher row and names scripts still in the old form; kerstel uninstall deletes 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 update shows 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 init now wires every command of a script, not only the first. Before, node copy.mjs && next dev became kerstel exec -- node copy.mjs && next dev, so next dev started without the hook and read the literal kerstel:// reference, and a script starting with NODE_ENV=x broke because the assignment became the command. Now each command gets the wrapper after its leading NAME=value words, commands that never run JavaScript (rm, echo, mkdir, and the like) are left alone, and a script init cannot wire safely (one with cd, a redirection, a subshell, or an unbalanced quote) is skipped and named with the reason. Re-running init finishes a script that was wired only in part. kerstel doctor names partly wired and skipped scripts instead of counting them as wired, and kerstel uninstall unwraps every command.
    • Under Bun, Bun.env now returns the real value of a kerstel:// reference, as process.env already did. Bun.env is 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.env keeps 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 through process.env fails the way it did before. Under Node nothing changes.
    • kerstel exec and kerstel run now say what to do when the command they were given is not on PATH: they name the executable, and in a project they name the install command for its package manager (bun install, npm install, pnpm install, or yarn install), saying outright when node_modules is missing altogether. They exit 127, as a shell does. Before, a wired npm run dev in a fresh clone failed with Bun's own Executable not found in $PATH and exit 1.
  4. 0.1.2

    Security

    • Kerstel no longer takes its own settings from the project it is protecting. Kerstel is a Bun binary, so it loads the .env of the directory you run it in, and Kerstel's model is that this file is committed: a repository you cloned could set KERSTEL_HOME to a directory inside itself, force the weaker key-file credential store, or stop the daemon ever relocking. Any KERSTEL_* 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 exec now gives a wired script KERSTEL_TOKEN_FILE, the path of the 0600 token file, instead of KERSTEL_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, where console.log(process.env), a crash reporter, or ps -E could 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. A KERSTEL_TOKEN inherited 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's KERSTEL_* settings) instead of inheriting the caller's, and a kerstel daemon serve run by hand re-executes itself through the same list before opening the vault. Before, BUN_OPTIONS set in your shell could run a preload inside the process that holds the vault key, and under a nested kerstel exec the daemon inherited every already-resolved secret in its environment. kerstel doctor now warns when BUN_OPTIONS is set.
    • kerstel init and kerstel exec refuse, and kerstel doctor reports a problem, when node_modules/.bin in the project or any parent directory holds a kerstel or ks. npm and bun put that directory first on PATH when they run a script, so a dependency declaring such a bin would 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 (security on macOS, secret-tool on 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's PATH. npm run and bun run put node_modules/.bin first on PATH, so before this a dependency could ship a fake security and 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 init no 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 not KEY=value now 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 store silently 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, and init seals its backup with the key the vault is already open with instead of fetching it a second time.
    • kerstel init no longer treats backup copies such as .env.bak, .env.orig, .env.swp, or .env.local~ as env files. Before, a stale value in .env.bak outranked the live one in .env and 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.
  5. 0.1.1

    Added

    • kerstel update installs the latest release in place: it downloads the binary for your platform, verifies its checksum against the release's SHA256SUMS, confirms the new binary reports the expected version, and then swaps it in. --check only tells you whether a newer release exists.
    • kerstel --version on 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 doctor starts with a version row: up to date, a newer release with Fix: 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_COLOR is respected.
    • kerstel doctor no 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, so doctor now reports it as information rather than a warning. Outside a project, or in one that isn't set up yet, doctor ends with a short "How it works" note.

    Fixed

    • The kerstel init overview 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 uninstall removes every kerstel and ks link that points at the binary, wherever they are on PATH or in the folder it was run from. Before, when kerstel on your PATH was itself a link to the binary, both it and the ks shortcut beside it were left dangling.
  6. 0.1.0

    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 0600 key file, with a warning.
    • kerstel://<scope>/<KEY> references, so .env files hold nothing secret and are safe to commit.
    • kerstel set, get, ls, and rm to manage secrets. get prints plaintext only with --reveal.
    • kerstel run -- <command> to run any command with every reference resolved up front.
    • kerstel resolve to 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 doctor to 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 .env values 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 your package.json scripts, so npm run dev stays npm run dev.
    • kerstel init on 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 init flags for scripts and CI: --dry-run (writes nothing, not even to ~/.kerstel), --yes, --scope, --global, --keep, --non-interactive, and --from-stdin. --keep or --global naming a key no env file defines gets a warning.
    • kerstel init suggests the vault for any key whose name marks a credential, such as PASSWORD, PASS, PWD, PIN, PASSPHRASE, TOKEN, SECRET, or any *_KEY, even when the value is a number like PIN=4821.
    • kerstel exec -- <command>, which runs one command with the runtime hook wired in. kerstel init writes 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 .env values 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 value init kept only in its encrypted backup. Afterwards it names every restored .env file 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/bin without sudo.
    • ks, a shortcut for kerstel, installed by install.sh when nothing else is called ks, and removed by uninstall.
    • kerstel doctor --verbose shows the paths and permissions behind each check.

    Changed

    • kerstel init shows 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 like PORT=3000 that stays in plain text.
    • kerstel doctor groups its checks, shows how to fix each warning and problem, and exits 1 when something is wrong.
    • Bare kerstel lists the commands and exits 0.