@vaultkeeper/cli@0.2.0
Minor Changes
-
#89
46df0b0Thanks @mike-north! - Make backend selection visible and overridable from the CLI and introspectable from the library.vaultkeeper config init --backend <type>now writes a config whose first enabled backend is<type>. Valid values are the registered backend types; an unknown value exits 2 and lists the valid types.- Any unknown flag on a
configsubcommand (config init,config show) now exits 2 with an "unknown option" error instead of being silently ignored — a typo can no longer send secrets to an unintended credential store. config initoutput now states which backend was configured and how to change it.config showreports the resolved active backend (first enabled).- New public
platformNativeBackendType()reports the OS-native credential store for the current platform (keychainon macOS,dpapion Windows,secret-toolon Linux,fileon other platforms) — the store you can opt into with--backend. - New public
VaultKeeper.activeBackendTypegetter exposes the type of the active (first enabled) backend at runtime.
-
#92
75685acThanks @mike-north! - Add a global--config-dir <path>flag /VAULTKEEPER_CONFIG_DIRenvironment variable to the CLI so every command (store,delete,exec,approve,dev-mode,doctor,config,rotate-key,revoke-key) can be pointed at an isolated config directory — the flag wins over the env var, which wins over the platform default.config initcreates the override directory as needed, andconfig showreports the path it loaded from. The library'sgetDefaultConfigDir()andloadConfig()are now public so embedders and the CLI share the same resolution logic.@vaultkeeper/cli-test-helpers'screateCliTestEnv()gains aconfigDirModeoption ('env'|'flag') and no longer manipulates the subprocess'sHOMEdirectory to achieve isolation. -
#95
4ebfa5dThanks @mike-north! - Uniform CLI exit-code taxonomy (0 success / 1 runtime failure / 2 usage error) applied everywhere: a top-level typo likevaultkeeper --bogusnow exits 2 with an error instead of silently exiting 0, and an unrecognized flag onstore,delete,exec,approve,dev-mode, ordoctornow exits 2 instead of a bare fatal error (exit 1).store(anddelete, for consistency) now reject an empty or whitespace-only--namewith exit 2 and the same error style as a missing flag, instead of persisting a near-unreachable secret or surfacing a generic runtime error. Allowed--namecharacters (letters, digits,.,_,-,/) are documented in--help.execnow validates that the secret exists before the caller-approval/TTY gate, soexec --secret <nonexistent> ...reports a clearSecretNotFoundErrorregardless of TTY, instead of being masked by the generic "requires interactive approval" message. This is backed by a new publicVaultKeeper.secretExists(name)method — a side-effect-free existence check that never touches the TOFU trust manifest.config init --helpandconfig show --helpnow print help for that subcommand instead of the parentconfighelp.exec --helpincludes a worked--callerexample. -
#94
0ca9d3fThanks @mike-north! -doctorandconfig shownow detect an invalid config file instead of silently ignoring it.doctorvalidates the config file (when present) as part of its preflight checks and reports a failingconfigcheck with the parse/validation error and file path, exiting non-zero.config showon invalid JSON now exits non-zero with the parse error (including a line/column location when available) instead of dumping the raw file with exit 0. Every config parse/validation error raised byloadConfig()— surfaced throughstore,delete,exec,config show, anddoctoralike — now includes the config file path, the parse location where available, and a remediation hint namingvaultkeeper config init.loadConfig()now falls back to platform defaults only when the config file is missing (ENOENT). A present-but-unreadable file (e.g. a permissions error) is rethrown as a typedFilesystemErrorinstead of being silently treated as "no config" — a genuinely broken config was previously invisible todoctorandconfig show.The "no config file" story is now uniform across
store,delete,exec,config show, anddoctor: each falls back to platform defaults and prints a one-line notice naming the resolved backend andvaultkeeper config init(e.g.No config file found; using platform defaults (keychain). Run 'vaultkeeper config init' to persist one.). Previouslyconfig showerrored with exit 1 on a missing config file while the other commands defaulted silently;config shownow defaults and reports it like the rest.New public
ConfigParseError(withpathandlocationfields) is thrown on invalid config JSON.ConfigValidationErrorgains an optionalconfigFilePathfield.PreflightCheckStatusgains an'invalid'value, andRunDoctorOptionsgains an optionalconfigDirfield that letsrunDoctor/VaultKeeper.doctor()load and validate the config itself. -
#121
7c8ab85Thanks @mike-north! - Scopedoctorto the active/configured backend so a fresh install no longer looks broken. Previouslydoctorrendered every non-'ok'check with a failing✗icon, including plugin-backend tools (ykman,op) that weren't configured — on the post-#98file-default install, this meant the very firstdoctorrun showed a failing check for a YubiKey/1Password tool the user never opted into.PreflightResult.checksentries are nowScopedPreflightCheck(aPreflightCheckplusrequired: boolean), reflecting whether each dependency is required for the active/configured backend(s). The CLI only renders the✗icon for checks that are both required and failing; unmet optional checks still surface, without the failure icon, in theWarningssection. Opt-in backends still get their dependency checks promoted to required when configured (e.g.--backend yubikeyrequiresykman). -
#86
be28555Thanks @mike-north! -vaultkeeper execcan now run non-interactively. A caller already recorded in the TOFU trust manifest (viaapproveor a prior approval) runs without any prompt on a TTY or not. A new explicit opt-in — the--yesflag and theVAULTKEEPER_YES=1environment variable — approves an untrusted caller for a single invocation without prompting, recording the approval the same way an interactiveywould. Without trust and without--yes, an untrusted caller on non-TTY stdin still fails, but the error now tells you exactly how to proceed (vaultkeeper approve --script <caller>or--yes).exec --helpdocuments the TTY requirement and both escape hatches, and the README gains a "Running in CI" note. A caller whose contents changed since approval is never auto-approved by--yes; it must be re-approved withvaultkeeper approve. -
#210
38fafb5Thanks @mike-north! - Add a backendpresencePerUsecapability and the ability to require it for an operation.presencePerUsemeans "every operation with a key in this backend forces a distinct, fresh physical human action, and can never be satisfied from a cached or session-unlocked state." vaultkeeper is now the single place that knows this per configured backend instance and enforces it, so consumers never have to reason about YubiKey touch policies or 1Password per-access tricks themselves.Library:
- New
PresenceCapableBackendextension interface (getCapabilities(): Promise<BackendCapabilities>) — mirrorsListableBackend/SigningBackend; it is not a required member ofSecretBackend.BackendCapabilitiesis{ presencePerUse: boolean }and is open to extension. - New
getBackendCapabilities(backend)helper andisPresenceCapableBackend(backend)guard. A backend that does not implement the interface reports{ presencePerUse: false }— an unknown backend never silently claims presence. The capability reflects the configured instance (a YubiKey slot's touch policy, 1Password's access mode), never a hardcoded per-type answer. - New
VaultKeeper.getActiveBackendCapabilities()introspection method. - New
requirePresencePerUse?: booleanoption on the shared access path (store,delete,setup,sign). Enforcement is queried fresh on every call and refuses before any credential/session/device is touched when unsatisfied; when capable, the operation forces a fresh action for that specific call. Presence-gated signing performs a fresh backendsignWithKeyround-trip per call, so no cached key material can satisfy it. - Enforcement is operation-aware and fail-closed via
BackendCapabilities.presenceEnforcedOperations(a list ofPresenceOperation; omitted means all operations). A backend that forces presence for only some operations refuses a flagged uncovered operation withNotCapableErrorrather than passing without a fresh action. 1Passwordper-accessforces presence for reads only (store/deleteroute through the cached session client), so a flaggedstore/deleteon 1Password is correctly refused; a YubiKey touch slot covers every operation. - New typed errors (all extend
VaultError, machine-readable fields, exported):NotCapableError { backendType, capability },PresenceDeclinedError { backendType },PresenceTimeoutError { backendType, timeoutMs }.
CLI:
- New
vaultkeeper backend capabilities [--json]command lists each registered backend as{ type, displayName, presencePerUse }(a flat array with--json, human-readable text otherwise). - New per-command
--require-presence-per-useflag onstore,delete,sign, andexec(never global, never onverify). Withexec, a cached token is never reused under the flag.
- New
-
#108
5958996Thanks @mike-north! - Make the zero-config default backend safe: the shortest documented getting-started path can no longer silently write a secret to your real OS credential store.- With no config file present, a bare
VaultKeeper.init()— andvaultkeeper config initwith no--backend— now resolves to the portable, self-containedfilebackend on every platform, including macOS and Windows. Previously it targeted the OS-native credential store (macOS Keychain, Windows DPAPI), so copy-pasting the first documented example could store a secret in the real login keychain before the user knew backends existed. - The platform-native store stays fully supported as an explicit opt-in:
vaultkeeper config init --backend keychain/--backend dpapi, or an explicit config. - New public
defaultBackendType()returns the zero-config default ('file') on every platform. The formerplatformDefaultBackendType()is renamed toplatformNativeBackendType()— it never was the zero-config default and now reads as what it is: the OS-native store you can opt into. - The
doctor/store(anddelete/exec/config show) "no config file" advisory now names thefiledefault and spells out the remediation asvaultkeeper config init --backend file, never a bareconfig init, so following the hint verbatim persists exactly the backend that was in effect.
Note: this changes only the TypeScript library and Node.js CLI. The native Rust CLI's zero-config default is unchanged for now.
- With no config file present, a bare
-
#159
68d8b9cThanks @mike-north! - Make signing and verification of arbitrary challenges a first-class, CLI-exposed primitive with a stable, third-party-verifiable signature format.- New CLI commands (
@vaultkeeper/cli):key create --name <n> --type ed25519provisions a signing keypair (unknown--typeexits 2, never a silent default);key export --name <n>prints the SPKI PEM public key;sign --name <n>reads all of stdin and writes exactly the detached signature to stdout (pipeline-safe; status on stderr);verify --public-key <pem> --signature <sig>verifies a detached signature fully offline (no config, backend, or key store).verifyadds exit code3for a signature that did not verify — a deliberate, documented exception to the0/1/2taxonomy so scripts can tell a bad signature from a broken tool. - Signatures are detached-payload Compact JWS (RFC 7515 §7.2.2 + RFC 7797
b64:false,crit:["b64"],algEdDSA/Ed25519). Any standards-compliant JOSE library can verify a signature given the payload and the public key. - Signing keys are a distinct resource from secrets: a new backend signing contract (
generateSigningKey/getPublicKey/signWithKey, mirroringListableBackend) keeps private key material backend-side. It never flows throughstore()/retrieve()/fetch()/exec()or a capability token's claims, andfetch()/exec()/getSecret()reject a signing-key token outright. Thefilebackend implements the contract; backends that do not fail with a typedSigningNotSupportedError. - Breaking (library): the
SignRequest/SignResult/VerifyRequestshapes andVaultKeeper.sign()/VaultKeeper.verify()are reshaped to the JWS contract.sign()now takes a signing-key capability token from the newauthorizeSigningKey()and returns{ jws };verify()is async and takes{ payload, jws, publicKey }. New public API:createSigningKey(),exportPublicKey(),authorizeSigningKey(),SigningBackend/isSigningBackend,SigningAlgorithm,SigningPublicKey, and theSigningKeyNotFoundError/SigningNotSupportedErrortyped errors.
- New CLI commands (
Patch Changes
-
#231
f863dfcThanks @mike-north! - Wrap config-directory CREATION failures in a typedFilesystemErrorinstead of leaking a raw Node error.vaultkeeper config init(and the firststore, which persists key state before writing any secret) against a config directory whose parent is read-only previously aborted with the raw, unwrappedError: EACCES: permission denied, mkdir '<path>'— no error class, no plain-English description, no fix hint. Only the config-directory READ paths had been wrapped previously.The directory-creation path now surfaces a typed
FilesystemError(with the path and the underlying errno code) rendered through the CLI's error formatter with directory-specific wording and a parent-directory fix hint (check that the parent directory is writable, or choose a writable location with--config-dir). The rawEACCES/mkdirerrno text no longer reaches the user, and the command still exits non-zero. The key-state write path is likewise wrapped. -
#158
a93ac5eThanks @mike-north! - Fix two CLI config-error remediation gaps left over from #129:- An unreadable
config.json(e.g.EACCES/EPERMfrom a root-owned file orchmod 000) now gets a CLI-native message naming the file path and suggesting a permissions check — it no longer falls through to the library's "install@vaultkeeper/cli" text, and it never recommendsconfig init --force(which would hit the same permission error trying to write the replacement file). - A structurally invalid
config.jsonnow names the failing field again (e.g.The config at `<path>` is invalid (`version`) — run `vaultkeeper config init --force` to overwrite it.) — #129 dropped this detail with no replacement.
- An unreadable
-
#188
06596e2Thanks @mike-north! - Route the shared config-file presence check through the typedFilesystemErrorpath, and fix three CLI message papercuts.store,config show,delete, andexecagainst a config directory the process cannot read (e.g.chmod 000) no longer leak a raw NodeEACCES: permission denied, access '.../config.json'string. They now render the same typedFilesystemErrorwith a permissions remediation thatdoctoralready produced — a human message naming the file and pointing at the file's permissions, with a non-zero exit.delete's "secret not found" message no longer tells the user to runstoreto create the secret they are trying to delete. It keeps the shared diagnostic line but gives a neutral, delete-appropriate hint.exec(an access path) still suggests creating the secret.exec's required-flags validation error now includes the standardUsage:line, matching every sibling validation error (exit code 2).vaultkeeper approve --helpnow states thatapproveis a required first step for a new caller in non-interactive/CI contexts (non-TTY stdin), where there is no prompt to grant trust — not merely an optional prompt-avoidance convenience.
-
#160
90a4127Thanks @mike-north! - Fixed CLI error output so recovery hints repair the file they diagnose and read cleanly.- The invalid-config recovery hint now carries an explicit
--config-dir '<dir>'whenever a non-default config directory is active (from--config-dirorVAULTKEEPER_CONFIG_DIR), so the copy-pastedvaultkeeper config init --force …command repairs the exact diagnosed file instead of writing a fresh config to the platform default and leaving the corrupt file untouched. The default-directory case stays bare (no path is leaked). A newgetPlatformDefaultConfigDir()export computes the machine default independent ofVAULTKEEPER_CONFIG_DIR, so a directory that came only from the environment variable still gets an explicit flag (a fresh shell running the pasted command won't have that variable set);getDefaultConfigDir()now delegates to it. FilesystemErrornow renders a human message from its typedpath/permissionfields — plainly stating whether the file is missing or permission-denied, with a suggested next step — instead of leaking the raw NodeENOENT: … open '<path>'text. The typed class and its fields are unchanged.doctorprints the config remediation exactly once (under "Next steps") instead of duplicating it inline on the failing config check.
- The invalid-config recovery hint now carries an explicit
-
#140
94db84cThanks @mike-north! - Fix CLI error-experience papercuts:execanddeletenow report an identical "secret not found" message (Secret "<name>" not found in the "<backend>" backend.) with a recovery hint (Run \vaultkeeper store --name ` to create it.`) — previously each surfaced different wording and neither pointed at a fix.storewith empty stdin now exits 2 (usage error) instead of 1, consistent with a missing or empty--name— both mean "no usable input was given."- The library's
ConfigValidationErrormessage now separates the validation diagnosis from the remediation hint with a period instead of running them together with no punctuation.
-
#109
f24de45Thanks @mike-north! - Document the CLI's exit-code contract (0 success / 1 runtime error / 2 usage error, with an example
of each) and the--config-dirflag /VAULTKEEPER_CONFIG_DIRenv var in the CLI README. Note in
the library README that consumers need"type": "module"(vaultkeeper is ESM-only) and that
useLimitdefaults to unlimited (null) when omitted fromsetup()options.RotationInProgressError's message now includes a next step — runvaultkeeper revoke-key(or
callrevokeKey()) to invalidate the previous key immediately, or wait for the grace period to
elapse — matching the actionable-remediation style of the other domain errors. -
#129
db8936eThanks @mike-north! - Fixed the CLI printing the library's "install @vaultkeeper/cli" remediation when it hit an invalid config (ConfigParseError/ConfigValidationError) — a user already running this CLI was told to install a CLI they already had. The CLI now prints its own remediation naming the file path and the actual recovery command: "The config at<path>is invalid — runvaultkeeper config init --forceto overwrite it." The library's own message (used by JS-API consumers) is unchanged. -
#226
eec6581Thanks @mike-north! - Fixed the CLI README's sign/verify walkthrough:signandverifynow both read the challenge viaprintf '%s', so each sees byte-identical stdin — the previous here-string form appended a trailing newline, making the documented example fail verification with exit 3. Shipped READMEs' runnable examples are now exercised by a CI example-fence check so a documented command sequence that stops working fails the build. -
#206
88684f1Thanks @mike-north! - Make--versiondiscoverable and accept the commonly-guessed-v.vaultkeeper --versionalready worked, but the top-level--help"Global options" block listed only--config-dir, so the version flag was findable only by guessing, and-verrored as an unknown flag.--helpnow lists--version(and-h, --help) under Global options, and-vis wired to the same version output as-V.A bare
vaultkeeperinvocation with no arguments now renders that same full help on stdout and exits0— it prints the identical text--helpdoes, so it is a help request, not a usage error. Genuine misuse (unknown command/flag, missing required argument, empty-stdinstore) still exits2. -
#105
7f9da7aThanks @mike-north! - Add a supported recovery path for a corrupt or unreadableconfig.json.vaultkeeper config init --forcenow overwrites an existing config file, including one that's present-but-unparseable.config initwithout--forcekeeps its current non-destructive refusal, and now points atconfig init --forcein its refusal message.--forcecomposes with--backend(e.g.config init --force --backend file).ConfigParseError(and the otherloadConfigerrors sharing its remediation hint) now namesvaultkeeper config init --forceinstead ofvaultkeeper config init— the previous hint sent users to a command that provably failed in the exact state that produced the error.
-
#126
cfcd61bThanks @mike-north! - Fixdev-modeinvalid-action misdiagnosis and auditconfig.ts/the file backend for plainErrorthrows.vaultkeeper dev-mode <action> --script <path>now distinguishes an invalid action from missing args: an unrecognized action (e.g.banana) emitsunknown action "<x>" (expected "enable" or "disable")(exit 2), whilemissing action or --script flagis reserved for genuinely absent arguments.- The encrypted-file secret backend (
FileBackend) now surfacesEACCES/permission failures reading, writing, or deleting a secret entry as a typedFilesystemErrorinstead of the raw Node.js error. - Added a new
DecryptionError(extendsVaultError) for when a stored secret entry fails to decrypt (corrupted ciphertext or a failed AES-GCM auth tag check) — previously thrown as a plainError.
-
#187
a822564Thanks @mike-north! - Ship a per-packageLICENSEand align docs for signing/verification and packaging.- Every published package now carries its own
LICENSEfile and listsLICENSE+README.mdexplicitly in itsfilesarray, so the packaging declaration matches what npm actually ships (previously only a rootLICENSEexisted, whichnpm packdoes not include in per-package tarballs). A packaging test now assertsLICENSEis present in each tarball. - Documented
sign()'s precondition that the stored secret must be PEM private-key material — secrets are stored as strings andcrypto.createPrivateKey()treats a string as PEM, so raw binary DER must be converted to PEM before storing; a plain-string secret throwsInvalidKeyMaterialError. Added a distinct example key and a runnable end-to-endgenerateKeyPairSync→ store → sign → verify walkthrough, plusInvalidKeyMaterialErrorin the repository README's error table. - Scoped the delegated access patterns (
fetch()/exec()/getSecret()/sign()/verify()) explicitly to the TypeScript library and clarified that@vaultkeeper/wasm'sexecutablePathis a non-enforcing claim label, unlike this library's TOFU-verifiedexecutablePath. - Added a
getSecret()code sample, a top-of-README quick-links/TL;DR block, a note that doctor deliberately checks all supported backends' tooling, and a more precise TypeScript-version note that shows the exact known-good consumercompilerOptionsthe CI matrix verifies across TypeScript 5.0.4–7.0.2.
- Every published package now carries its own
-
#145
f5edcd9Thanks @mike-north! - Give the doctorconfigpreflight check structured error context so the CLI can render a CLI-native remediation instead of the library's install text.- The public
PreflightCheckshape gains an optionalerrorfield (PreflightCheckError:kind+configPath+ optional parselocation) carrying remediation-free, machine-readable context when theconfigcheck fails on a present-but-invalid config file. A consumer can build its own audience-appropriate remediation from these fields instead of parsing the human-readablereasonprose. vaultkeeper doctorrun against a corrupt or invalid config now shows the CLI-native remediation (config path +vaultkeeper config init --force), wording-consistent with every other command, and no longer tells a user already running the CLI to "install @vaultkeeper/cli". The library's ownreasontext is unchanged for library consumers.
- The public
-
#206
88684f1Thanks @mike-north! - Surface the offending field indoctor's remediation for a schema-invalid config.For a config that parses as JSON but fails schema validation (e.g.
backends: []),doctor's Next-steps previously said only that the config was invalid, omitting the field-level reason it gives for JSON-parse errors (which name the line/column). ThePreflightCheckErrorstructured context now carries the offendingfieldfor aconfig-validationfailure — the validation analogue of a parse failure'slocation— sodoctorrenders it (e.g. "is invalid (backends)"), matching the wording every other command already used. Noreasonprose is parsed to do this. -
#177
16f67a9Thanks @mike-north! - Render an unreadable config directory as a failing doctor check instead of a raw crash.vaultkeeper doctorrun against a config directory the process cannot read (e.g. achmod 000directory, so readingconfig.jsoninside it fails withEACCES/EPERM) previously aborted with a raw NodeError: EACCES: permission denied, access '.../config.json'— no typed class, no fix hint, and no checks rendered at all. It now surfaces the read failure as a failingconfigcheck (just like a parse or validation failure), keeps rendering the other checks, prints a permissions-oriented remediation under "Next steps", and exits non-zero. The raw errno string no longer leaks to the user.- The public
PreflightCheckErrorKindgains a'config-read'member, andPreflightCheckErrorgains an optionalcodefield carrying the underlying errno (e.g.EACCES), so a consumer can build a permissions-specific remediation.config init --forceis deliberately not suggested for this failure — it cannot repair a config the process cannot read.
-
#221
2705b3aThanks @mike-north! - Validatebackends[].typeagainst the registered backends when loading a config, closing a gap wheredoctorreported a false "System ready." for a config naming a backend that does not exist.- Config validation (
loadConfig,validateConfig) now rejects abackends[].typethat names no registered backend. A config with an unknown type parses as valid JSON and is structurally valid, but the next real command would throwBackendUnavailableErrorat backend-creation time — sodoctorreporting all-clear undermined the diagnostic the CLI's own corrupted-config recovery points users at. doctor's config check now FAILS (red, exit non-zero) for an unknown backend type and names both the offending type and the valid options — the same guidance the runtimeBackendUnavailableErrorgives — instead of silently passing.- New public
UnknownBackendTypeError(aConfigValidationErrorsubclass) carries the offendingbackendTypeand theknownTypes. Thedoctorpreflight result gains aconfig-unknown-backenderror kind carryingbackendTypeandknownBackendTypes, so a consumer can render the valid-types guidance without parsing prose. - The valid set is read from the backend registry (including any custom backends a consumer registered), not a hardcoded list.
- Config validation (
-
#195
c7f0068Thanks @mike-north! - Redact injected secrets from libraryexec()output, and give the CLI a typed error when a wrapped command cannot be spawned.VaultKeeper.exec()now redacts every injected secret value from the capturedstdout/stderrbefore returning, replacing each occurrence with[REDACTED]. This upholds the documented guarantee that the raw secret never appears in the return value, even when the spawned command echoes it. Multi-secret ({{secret:name}}) injections redact all injected values. Pass the newExecRequest.redact: falseto opt out and receive raw output. The redaction logic is shared with the CLI's streaming--no-redactpath via the new publicredactSecretshelper, so the two surfaces cannot drift.- The CLI
execcommand now maps a spawn failure of the wrapped command (ENOENTfor a nonexistent command,EACCESfor a non-executable file) to a typedExecErrorwith remediation, rendered through the CLI's typed-error formatter, instead of leaking a bareError: spawn <path> ENOENT.
-
#78
414bb05Thanks @mike-north! - HonorBackendConfig.pathfor file-based backends. Previously the documentedpathoption was validated and then silently ignored: secrets always landed in the hardcoded$HOME/.vaultkeeper/<backend>location. Thefile,dpapi, andyubikeybackends now store, retrieve, and delete secrets under the configured directory (created on demand) whenpathis set, falling back to the default location when it is not. The CLIstoreanddeletecommands inherit the fix by routing throughVaultKeeper, which resolves the first enabled backend from config and forwards that backend's configuration.Config validation now rejects a whitespace-only
path(e.g." ") with the newConfigValidationError, instead of silently treating it as a real storage directory. -
#77
26c876cThanks @mike-north! - Ship a package-specific README.md andrepository/homepage/bugsmetadata with every published package, so registry consumers get install instructions and a quick start without leaving npm. -
#111
ebfcd1dThanks @mike-north! - Make the packaged READMEs self-contained:vaultkeeperand@vaultkeeper/clinow include a
minimal, safe-by-default (filebackend) exampleVaultConfig/config JSON, plus inline
explanations of key rotation grace periods, thetrustTierpolicy label, and the
trust-on-first-use (TOFU) check thatexecreports on every run — so the golden path no longer
depends on fetching the unshipped repository README. -
#93
8124067Thanks @mike-north! - Persist key material across processes so cached tokens and the rotation grace period work between CLI invocations.KeyManagerencryption keys are now persisted under the config directory, encrypted at rest with AES-256-GCM under an owner-only (0600) wrapping key — reusing the same authenticated-cipher primitives as the file backend. Persistence is active only whenVaultKeeperloads its configuration from disk (no injectedconfig/backend); instances built with an injectedconfigorbackendkeep keys in memory, so tests and embedders stay hermetic.- A JWE minted by one process is now authorizable by a later process within its validity window: its
kidstill resolves after the minting process exits. Previously every process generated fresh keys, so a cached token always failed withKeyRevokedError. vaultkeeper exec --cachenow genuinely reuses a cached token on a second run by the same trusted caller — without re-minting and without the misleading "Cached token expired" message. Cached tokens are reusable until they expire (the secret's TTL) or the key is rotated/revoked, after whichexectransparently mints a fresh one.- The cached-token path no longer collapses every authorization failure into a generic "expired" message. Each failure now surfaces its actual cause (e.g.
KeyRevokedError,TokenExpiredError). - The rotation grace-period guard now survives across processes: running
rotate-keytwice while the previous key is still in its grace period fails the second time withRotationInProgressError(non-zero exit) instead of silently rotating again.
-
#164
ce43052Thanks @mike-north! - Complete the plain-Erroraudit (issues #115/#126 coveredconfig.tsand the file backend): every remainingthrow new Error(...)in product source now throws a typed error instead.vaultkeeper:util/at-rest.tsandbackend/yubikey-backend.ts's encrypted-envelope decoding now throwDecryptionError(malformed envelope, unsupported/legacy file version, or a failed AES-GCM auth tag check) instead of a plainError.util/platform.ts'scurrentPlatform(),backend/one-password-constants.ts'sgetIntegrationVersion(), andyubikey-backend.ts's YubiKey HMAC response validation now throwSetupError.util/exec.ts'sexecCommand/execCommandFulland the YubiKeyykmanchallenge-response call now throwExecError.OnePasswordBackend's constructor validation (mutually exclusiveaccessMode/serviceAccountToken/accountoptions) now throwsConfigValidationError, and its per-access worker crash/spawn-failure paths now throwBackendUnavailableError. No new public error classes or fields were added — every site reuses an existingVaultErrorsubclass.@vaultkeeper/cli: the non-interactive-approval-required error (in bothapproval.ts'spromptApprovalandcommands/exec.ts's trust gate) now throws a new internalNonInteractiveApprovalError(not part of the public API, matching the existing internalConfigDirFlagErrorpattern) instead of a plainError.
A new repo-wide guard test (
no-plain-error.test.tsin both packages) scans every source file undersrc/and fails if a plainErrorconstruction (throw/reject) reappears. -
#175
f20ea5aThanks @mike-north! - Round out the shipped package docs so a reader offline (registry-only, air-gapped) can find everything without the GitHub URL:vaultkeeperREADME: new "Multiple secrets in one request" section documentingSecretTokenMapand the{{secret:name}}placeholder for injecting several secrets into onefetch()/exec()call; a runnable inlineexec()example (secret injected viaenv); a complete error-types table covering allVaultErrorsubclasses; a fullVaultConfig/BackendConfigfield reference; and a "Doctor / preflight checks" section explaining required-vs-informational checks and that a plugin checkmark means "binary detected on PATH", not "backend active".vaultkeeperREADME:verify()now notes that the disallowed-algorithm throw does not apply to Ed25519/Ed448 keys (the algorithm override is ignored). The "Testing against this library" section notes@vaultkeeper/test-helpersbelongs indevDependenciesand warns that the realVaultKeeper.setup()always requiresexecutablePathorskipTrust.@vaultkeeper/test-helpersREADME: strengthened the warning that the test-only zero-argsetup()default does not carry over to the realVaultKeeper.setup().@vaultkeeper/cliREADME: new "Doctor / preflight checks" section on checkmark semantics — plugin checks (op/ykman) are informational when their backend isn't enabled, but enabling the1password/yubikeybackend promotes its tool check to required; points at the now-self-contained library README for the full error hierarchy and config reference.
-
#119
0c1daefThanks @mike-north! - Close doc gaps left over from the shipped-README audit:vaultkeeper's and@vaultkeeper/cli's READMEexecexamples now mention the default[REDACTED]output redaction and the--no-redactescape hatch inline.- The
vaultkeeperpackage README now inlines a development-mode explanation, asign()/verify()example, and a brief error-hierarchy summary instead of deferring them solely to the unshipped repository README;@vaultkeeper/cli's README gets an inline development-mode explanation too. - States a supported TypeScript version (5.x) in both READMEs, and documents a
require()/CommonJS quick-start variant alongside the existing ESM one. verify()'s JSDoc now calls out that it is synchronous and throws immediately (not via a rejectedPromise) for a disallowed algorithm.- Adds a
./package.jsonsubpath tovaultkeeper'sexportsmap.
-
#220
e800683Thanks @mike-north! - Polish setup() editor guidance and CLI/README papercuts.setup()compile-error hint. BothVaultKeeper.setup()in thevaultkeeperlibrary and@vaultkeeper/wasmnow carry a TSDoc note that names the exact compile errors a missing trust choice produces (TS2554/TS2345) and the two remedies — add exactly one ofexecutablePathorskipTrust: true— so hovering the call in-editor explains the fix rather than leaving the bare compiler message. The WASMsetup()also gains a runnable@example.useLimit"use" semantics documented. The README now spells out thatuseLimitbounds calls tovault.authorize(jwe), not downstream delegatedfetch()/exec()/getSecret()calls: eachauthorize(jwe)consumes one use, and the resultingCapabilityTokencan be reused across many delegated calls; only a secondauthorize(jwe)throwsUsageLimitExceededError.verifyinline-PEM parsing.vaultkeeper verify --public-key/--signaturenow reject inline PEM material with a clear, actionable usage error (exit 2) instead of node's opaque "argument is ambiguous" — the flags are file-path-only, and the message says so and points at the--public-key=<path>escape for a path that legitimately begins with a dash.Unknown-command suggestion. An unrecognized subcommand now prints an npm/git/cargo-style
Did you mean '<closest>'?suggestion (e.g.doctro→doctor) plus a one-line pointer tovaultkeeper --helpand the docs, giving tarball-only users a discovery path.README Quick Start. The CLI Quick Start code block now includes an inline
--config-dir/VAULTKEEPER_CONFIG_DIRreminder so a copy-paster gets the isolated-config guidance that was previously only in prose. -
#79
bfa32f3Thanks @mike-north! - Make the CLI's trust-on-first-use (TOFU) model functional.vaultkeeper approve --script <path>now computes the script's SHA-256 and records it in the trust manifest (idempotently), andvaultkeeper execconsults the manifest before prompting: a caller whose current hash is already approved runs without an interactive prompt and reports a verified trust state, while a modified or unapproved caller is treated as untrusted. The library gains two public methods onVaultKeeper—approveExecutable()andcheckExecutableTrust()— plus theExecutableTrustStatustype, which back this behavior. -
#196
7ee1a61Thanks @mike-north! - Type-enforce thesetup()trust choice, fix the quick-start rebuild footgun, and polish docs and CLI usage errors.SetupOptionsis now a discriminated union (type-enforced trust XOR).VaultKeeper.setup()'s options argument is required and must carry exactly one ofexecutablePath(TOFU verification) orskipTrust: true(development opt-out). Supplying neither — including a baresetup('NAME')orsetup('NAME', {})— or both is now a compile-time error rather than a runtime-only failure;ExecutableTrustRequiredErrorremains the runtime backstop for untyped (plain-JavaScript) callers.SetupOptionsBaseis exported for the common (non-trust) options.@vaultkeeper/test-helpersgains a matching publicTestVaultSetupOptionstype;TestVault.setup()keeps its permissive, trust-choice-optional signature (it still defaults toskipTrust: true).Quick-start rebuild footgun fixed. The library quick start no longer steers first-timers to
executablePath: process.argv[1], which pins TOFU trust to the compiled entry-file hash and throwsIdentityMismatchErroron the next run after any rebuild. The runnable snippets now use the development-safe{ skipTrust: true }, with an inline warning and a clearly-framed production example that binds a stable anchor (a released binary orprocess.execPath), plus a cross-reference to Development mode for frequently-rebuilt local callers.Docs and CLI papercuts. Documented
exec()'s[REDACTED]-by-default output redaction and theredact: falseopt-out in the library README; clarified thatVaultKeeper.init()is in-memory and does not writeconfig.json(only the CLIconfig initdoes); clarified that pre-approving a caller is a required first step for non-interactive/CI firstexec(CLI) versus auto-recorded on first encounter (library); and documented the WASMdoctor()unscoped required-vs-informational semantics. The CLI now prints aUsage:block (and exits 2) for an unknown top-level flag, matching the unknown-command and subcommand-level usage errors. -
Updated dependencies [
46df0b0,f863dfc,75685ac,90a4127,4ebfa5d,94db84c,f24de45,eec6581,7f9da7a,0ca9d3f,b270562,5c47f18,cfcd61b,a822564,f5edcd9,88684f1,7c8ab85,16f67a9,2705b3a,d511437,2d848a7,c7f0068,9f06652,16e68b0,c521414,414bb05,ea628e5,7f46237,f6e692b,8fd800c,26c876c,ebfcd1d,8124067,ce43052,38fafb5,f20ea5a,0c1daef,f2fe1d2,5958996,e800683,1c115c8,68d8b9c,11fe95d,bfa32f3,7ee1a61,6b6d11a,8ded257,a4a6cb7,f2baa86]:- vaultkeeper@0.7.0