Warning! kbs2 is beta-quality software! Using kbs2 means accepting that your secrets may be
lost or compromised at any time!
kbs2 is a command line utility for managing secrets.
Quick links:
- Installation
- Quick start guide
- CLI documentation
- Configuration
- Customization
- Why another password manager?
- Technical details
- Hacking
- History
kbs2 is available via a variety of official and community-supplied packages.
See the matrix below for a list of repositories containing kbs2.
These packages are the recommended way to install kbs2 if you are not developing it.
This is a community-maintained package.
kbs2 can be installed from available
AUR packages
using an AUR helper. For example,
$ yay -S kbs2Other distributions will be supported sooner or later. Help us by looking at the open packaging issues!
This is a community-maintained package.
kbs2 can be installed through Nix:
$ nix-env -iA nixpkgs.kbs2If you're a Linux user, you'll need some X11 libraries. For Debian-based distributions:
$ sudo apt install -y libxcb-shape0-dev libxcb-xfixes0-devkbs2 itself is most easily installed via cargo:
$ cargo install kbs2After installation, kbs2 is completely ready for use. See the
Configuration section for some optional changes that you can
make.
Initialize a new kbs2 configuration:
$ kbs2 initBy default, a fresh kbs2 configuration will store records in $HOME/.local/share/kbs2. Users
can override this by passing --store-dir DIR to kbs2 init, or at any point by modifying
store in the config itself.
kbs2 init will automatically generate a configuration file and keypair, prompting you for
a "master" password.
Note: By default, most kbs2 commands will start the authentication agent (kbs2 agent)
in the background if it isn't already running.
Create a new (login) record:
$ kbs2 new amazon
? Username? jonf-bonzo
? Password? [hidden]List available records:
$ kbs2 list
amazon
facebookPull the password from a record:
$ kbs2 pass -c amazon
# alternatively, pipeline it
$ kbs2 pass facebook | pbcopyRemove a record:
$ kbs2 rm facebookkbs2's subcommands are substantially more featured than the above examples demonstrate;
run each with --help to see a full set of supported options.
initialize kbs2 with a new config and keypair
USAGE:
kbs2 init [FLAGS] [OPTIONS]
FLAGS:
-f, --force overwrite the config and keyfile, if already present
-h, --help Prints help information
--insecure-not-wrapped don't wrap the keypair with a master password
OPTIONS:
-s, --store-dir <DIR> the directory to store encrypted kbs2 records in
[default: $HOME/.local/share/kbs2]
Create a new config and keypair, prompting the user for a master password:
$ kbs2 initCreate a new config and keypair without a master password:
$ kbs2 init --insecure-not-wrappedCreate a new config and keypair in a different location:
$ kbs2 -c /some/config/dir initCreate a new config keypair in a different location and specify a non-default store:
$ kbs2 -c /home/config/dir init --store-dir /some/store/dircreate a new record
USAGE:
kbs2 new [FLAGS] [OPTIONS] <label>
ARGS:
<label> the record's label
FLAGS:
-f, --force overwrite, if already present
-h, --help Prints help information
-t, --terse read fields in a terse format, even when connected to a tty
OPTIONS:
-G, --generator <generator> use the given generator to generate sensitive fields
[default: default]
-k, --kind <kind> the kind of record to create [default: login]
[possible values: login, environment, unstructured]
Create a new login record named foobar:
$ kbs2 new foobar
? Username? hasdrubal
? Password? **********Create a new environment record named twitter-api, overwriting it if it already exists:
$ kbs2 new -f -k environment twitter-api
? Variable? TWITTER_API
? Value? [hidden]
[Press [enter] to auto-generate]Create a new login record named pets.com, generating the password with the default generator:
$ kbs2 new pets.com
? Username? catlover1312
? Password?
[Press [enter] to auto-generate]Entering nothing in the password prompt will cause kbs2 to generate a password
using the "default" generator. You can use the --generator option to specify
a different generator, if you have another one configured.
Create a new login record named email, getting the fields in a terse format:
$ kbs2 new -t email < <(echo -e "[email protected]\x01hunter2")When in "terse" mode, kbs2 expects fields to be separated by \x01 (ASCII SOH)
characters.
list records
USAGE:
kbs2 list [FLAGS] [OPTIONS]
FLAGS:
-d, --details print (non-field) details for each record
-h, --help Prints help information
OPTIONS:
-k, --kind <kind> list only records of this kind
[possible values: login, environment, unstructured]
List all records, one per line:
$ kbs2 list
foobar
twitter-api
pets.com
emailList (non-sensitive) details for each record. The format of the detailed listing is
{record} {kind} {timestamp}.
$ kbs2 list -d
foobar login 1590277900
twitter-api environment 1590277907
pets.com login 1590277920
email login 1590277953List only environment records:
$ kbs2 list -k environment
twitter-apiremove one or more records
USAGE:
kbs2 rm <label>...
ARGS:
<label>... the labels of the records to remove
FLAGS:
-h, --help Prints help information
Remove the foobar record:
$ kbs2 rm foobarrename a record
Usage: kbs2 rename [OPTIONS] <old-label> <new-label>
Arguments:
<old-label> the record's current label
<new-label> the new record label
Options:
-f, --force overwrite, if already present
-h, --help Print help
Rename the foo record to bar:
$ kbs2 rename foo barRename foo to bar, even if bar already exists:
$ kbs2 rename --force foo bardump one or more records
USAGE:
kbs2 dump [FLAGS] <label>...
ARGS:
<label>... the labels of the records to dump
FLAGS:
-h, --help Prints help information
-j, --json dump in JSON format (JSONL when multiple)
Dump the twitter-api record:
$ kbs2 dump twitter-api
Label twitter-api
Kind environment
Variable TWITTER_API
Value 92h2890fn83fb2378fbf283bf73fbxkfnso90Dump the pets.com record in JSON format:
$ kbs2 dump -j pets.com | json_pp
{
"timestamp" : 1590363392,
"label" : "pets.com",
"body" : {
"fields" : {
"username" : "hasdrubal",
"password" : "hunter2"
},
"kind" : "Login"
}
}Dump multiple records, demonstrating JSONL:
$ kbs2 dump -j carthage roma
{"timestamp":1590363392,"label":"bepis","body":{"kind":"Login","fields":{"username":"hamilcar","password":"ihatecato"}}}
{"timestamp":1590363392,"label":"conk","body":{"kind":"Login","fields":{"username":"cato","password":"carthagodelendaest"}}}get the password in a login record
USAGE:
kbs2 pass [FLAGS] <label>
ARGS:
<label> the record's label
FLAGS:
-c, --clipboard copy the password to the clipboard
-h, --help Prints help information
Get the password for the pets.com record:
$ kbs2 pass pets.com
hunter2Copy the password for the pets.com record into the clipboard:
$ kbs2 pass -c pets.comget an environment record
USAGE:
kbs2 env [FLAGS] <label>
ARGS:
<label> the record's label
FLAGS:
-h, --help Prints help information
-n, --no-export print only VAR=val without `export`
-v, --value-only print only the environment variable value, not the variable name
Get an environment record in export-able form:
$ kbs2 env twitter-api
export TWITTER_API=92h2890fn83fb2378fbf283bf73fbxkfnso90Get just the value in an environment record:
$ kbs2 env -v twitter-api
92h2890fn83fb2378fbf283bf73fbxkfnso90modify a record with a text editor
USAGE:
kbs2 edit [FLAGS] <label>
ARGS:
<label> the record's label
FLAGS:
-h, --help Prints help information
-p, --preserve-timestamp don't update the record's timestamp
Open the email record for editing:
$ kbs2 edit emailOpen the email record for editing with a custom $EDITOR:
$ EDITOR=vim kbs2 edit emailgenerate secret values using a generator
USAGE:
kbs2 generate [generator]
ARGS:
<generator> the generator to use [default: default]
FLAGS:
-h, --help Prints help information
Generate a secret using the default generator:
$ kbs2 generate
rrayxfky-81x=h6iGenerate a secret using a generator named pwgen:
$ kbs2 generate pwgen
iit4wie6faeL4aiyupheec5Xochoserorun the kbs2 authentication agent
USAGE:
kbs2 agent [FLAGS] [SUBCOMMAND]
FLAGS:
-F, --foreground run the agent in the foreground
-h, --help Prints help information
SUBCOMMANDS:
flush remove all unwrapped keys from the running agent
help Prints this message or the help of the given subcommand(s)
unwrap unwrap the current config's key in the running agent
Run the kbs2 agent in the background, prompting the user to unwrap the current config's key:
$ kbs2 agentRun the kbs2 agent in the foreground, for debugging purposes:
$ RUST_LOG=debug kbs2 agent --foregroundremove all unwrapped keys from the running agent
USAGE:
kbs2 agent flush [FLAGS]
FLAGS:
-h, --help Prints help information
-q, --quit quit the agent after flushing
Remove all keys from the current kbs2 agent:
$ kbs2 agent flushask the current agent whether it has the current config's key
USAGE:
kbs2 agent query
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
kbs2 agent query exits with a few discrete codes to signal the query status:
0: query succeeded, agent is running and has a keypair for the config's public key1: query failed, agent is running but does not have the queried keypair2: query failed, agent is running but the keypair isn't managed by the agent (i.e., it's an unwrapped keypair)3: query failed, agent is not running
All other error codes should be treated as an unspecified error that prevented a query.
Query the agent for the current config:
$ kbs2 agent query && echo "success" || echo "failure"Query the agent for another config's keypair:
$ kbs2 -c /some/other/config agent queryunwrap the current config's key in the running agent
USAGE:
kbs2 agent unwrap
FLAGS:
-h, --help Prints help information
Add the current config's key to the kbs2 agent:
$ kbs2 agent unwrapAdd a custom config's key to the kbs2 agent:
$ kbs2 -c /path/to/config/dir agent unwrapchange the master password on a wrapped key
USAGE:
kbs2 rewrap [FLAGS]
FLAGS:
-f, --force overwrite a previous backup, if one exists
-h, --help Prints help information
-n, --no-backup don't make a backup of the old wrapped key
Change the password on the wrapped key in the default config:
$ kbs2 rewrapChange the password on a wrapped key in another config:
$ kbs2 -c /path/to/config/dir rewrapChange the password on a wrapped key without making a backup of the old wrapped key:
$ kbs2 rewrap -nre-encrypt the entire store with a new keypair and master password
USAGE:
kbs2 rekey [FLAGS]
FLAGS:
-h, --help Prints help information
-n, --no-backup don't make a backup of the old wrapped key, config, or store
Re-key the default config and its store:
$ kbs2 rekeyRe-key without making backups of the original keyfile, config, and store (not recommended):
$ kbs2 rekey --no-backupRe-key a different configuration and store:
$ kbs2 -c /some/other/kbs2/conf/dir rekeyinteract with kbs2's configuration file
USAGE:
kbs2 config <SUBCOMMAND>
OPTIONS:
-h, --help Print help information
SUBCOMMANDS:
dump dump the active configuration file as JSON
help Print this message or the help of the given subcommand(s)
dump the active configuration file as JSON
USAGE:
kbs2 config dump [OPTIONS]
OPTIONS:
-h, --help Print help information
-p, --pretty pretty-print the JSON
Dump the current configuration as JSON:
$ kbs2 config dump
# pretty-print the dumped JSON
$ kbs2 config dump --prettykbs2 stores its configuration in <config dir>/kbs2/config.toml, where <config dir> is determined
by the the XDG basedir specification.
On Linux, it's probably ~/.config/kbs2.
NOTE: If config.toml isn't found in a configuration directory, kbs2 attempts to use
kbs2.conf in the same directory. This is for backwards compatibility, and will be removed
once kbs2 has its first stable release.
config.toml is TOML-formatted, and might look something like this after a clean start with kbs2 init:
public-key = "age1elujxyndwy0n9j2e2elmk9ns8vtltg69q620dr0sz4nu5fgj95xsl2peea"
keyfile = "/home/william/.config/kbs2/key"
store = "/home/william/.local/share/kbs2"
[commands.pass]
clipboard-duration = 10
clear-after = trueThe public-key setting records the public half of the age keypair used by kbs2.
kbs2 init pre-populates this setting; users should not modify it unless also modifying
the keyfile setting (e.g., to point to a pre-existing age keypair).
The keyfile setting records the path to the private half of the age keypair used by kbs2.
kbs2 init pre-populates this setting; users should not modify it unless also modifying
the public-key setting (e.g., to point to a pre-existing age keypair).
The agent-autostart setting controls whether or not kbs2 attempts to auto-start the
authentication agent (kbs2 agent) whenever encryption or decryption operations are requested.
By default, kbs2 agent will be started (unless it's already running).
When set to false, kbs2 will report an error if kbs2 agent is not running. In this case,
users should configure their system to launch kbs2 agent at login (or some other convenient time).
The wrapped settings records whether keyfile is a "wrapped" private key, i.e. whether
the private key itself is encrypted with a master password.
By default, kbs2 init asks the user for a master password and creates a wrapped key.
See the kbs2 init documentation for more information.
The store setting records the path to the secret store, i.e. where records are kept.
Users may modify this setting to store their records in a custom directory.
The pinentry setting specifies the
Pinentry binary to use for passphrase
operations (i.e., prompting the user for their master password).
pinentry is a reasonable default for most systems; macOS users may wish to use
pinentry-mac instead.
The pre-hook setting can be used to run a command before (almost) every kbs2 invocation.
There are currently three cases where the configured pre-hook will not run:
kbs2(i.e., no subcommand)kbs2 agent(and allkbs2 agentsubcommands)kbs2 init
All other subcommands, including custom subcommands, will cause the configured pre-hook to run.
Read the Hooks documentation for more details.
The post-hook setting can be used to run a command after (almost) every kbs2 invocation,
on success.
There are currently three cases where the configured post-hook will not run:
kbs2(i.e., no subcommand)kbs2 agent(and allkbs2 agentsubcommands)kbs2 init
All other subcommands, including custom subcommands, will cause the configured post-hook to run.
Read the Hooks documentation for more details.
The error-hook setting can be used to run a command after (almost) every kbs2 invocation,
on failure.
There are currently three cases where the configured error-hook will not run:
kbs2(i.e., no subcommand)kbs2 agent(and allkbs2 agentsubcommands)kbs2 init
All other subcommands, including custom subcommands, will cause the configured error-hook to run.
The error-hook setting passes a single argument to its hook, which is a string representation
of the error that occurred.
Read the Hooks documentation for more details.
The reentrant-hooks setting controls whether hooks are run multiple times when a hook itself
runs kbs2. By default, hooks are run only for the initial kbs2 invocation.
Read the Reentrancy section of the Hooks documentation for more details.
The commands.new.default-username setting allows the user to specify a default
username for logins created with kbs2 new.
When specified, kbs2 new's username prompt will fill in the default when the user presses
only [enter].
The commands.new.pre-hook setting is like the global pre-hook setting, except that it runs
immediately before record creation during kbs2 new (and only kbs2 new).
The commands.new.post-hook setting is like the global post-hook setting, except that it runs
immediately after record creation during kbs2 new (and only kbs2 new).
The commands.new.post-hook setting passes a single argument to its hook, which is the label
of the record that was just created. For example, the following:
[commands.new]
post-hook = "~/.config/kbs2/hooks/post-new.sh"# ~/.config/kbs2/hooks/post-new.sh
>&2 echo "[+] created ${1}"would produce:
$ kbs2 new foo
? Username? bar
? Password? **********
[+] created fooThe commands.pass.clipboard-duration setting determines the duration, in seconds, for persisting
a password stored in the clipboard via kbs2 pass -c.
The commands.pass.clear-after setting determines whether or not the clipboard is cleared at
all after kbs2 pass -c.
Setting this to false overrides any duration configured in commands.pass.clipboard-duration.
The command.pass.pre-hook setting is like the global pre-hook setting, except that it runs
immediately before record access during kbs2 pass (and only kbs2 pass).
The command.pass.post-hook setting is like the global post-hook setting, except that it runs
immediately after record access during kbs2 pass (and only kbs2 pass).
The command.pass.clear-hook is like the other command.pass hooks, except that it only runs
after the password has been cleared from the clipboard.
The commands.edit.editor setting controls which editor is used when opening a file with
kbs2 edit. This setting takes precedence over the $EDITOR environment variable, which is
used as a fallback.
This setting is allowed to contain flags. For example, the following would be split correctly:
[commands.edit]
editor = "subl -w"The command.edit.post-hook setting is like the global post-hook setting, except that it runs
immediately after record editing during kbs2 edit (and only kbs2 edit).
The command.rm.post-hook setting is like the global post-hook setting, except that it runs
immediately after record removal during kbs2 rm (and only kbs2 rm).
The label of each record removed by kbs2 rm is passed as a separate argument to
the post-hook.
The command.rename.post-hook setting is like the global post-hook setting, except that it runs
immediately after record removal during kbs2 rename (and only kbs2 rename).
The record's old and new names are passed as separate arguments to the post-hook,
in that order.
kbs2 supports generators for producing sensitive values, allowing users to automatically
generate passwords and environment variables.
Generators are configured as entries in [[generators]].
The following configures an generator named "hexonly" that generates a secret from the configured alphabet and length.
[[generators]]
name = "hexonly"
alphabets = ["0123456789abcdef"]
length = 16By default, kbs2's configuration includes a default generator that looks
something like this:
[[generators]]
name = "default"
# kbs2 samples from each alphabet, to ensure a good distribution of symbols
alphabets = [
"abcdefghijklmnopqrstuvwxyz",
"ABCDEFGHIJKLMNOPQRSTUVWXYZ",
"0123456789",
"(){}[]-_+=",
]
length = 16These generators can be used with kbs2 new. For example, the following will
use the hexonly generator when the user presses [enter] instead of manually
entering a password.
$ kbs2 new -G hexonly pets.com
? Username? catlover2000
? Password?
[Press [enter] to auto-generate]Beyond the configuration above, kbs2 offers several avenues for customization.
kbs2 supports git-style subcommands, allowing you to easily write your own.
For example, running the following:
$ kbs2 frobulate --xyz
will cause kbs2 to run kbs2-frobulate --xyz. Custom commands are allowed to read from and
write to the config file under the [commands.ext.<name>] hierarchy.
When run via kbs2, custom commands receive the following environment variables:
KBS2_CONFIG_DIR: The path to the configuration directory thatkbs2itself was loaded with. Subcommands can use this path to read the current configuration file or any other content stored in the configuration directory.- NOTE: Subcommands are encouraged to use
kbs2 config dumpto read the configuration state instead of attempting to find the correct file manually.
- NOTE: Subcommands are encouraged to use
KBS2_STORE: The path to the secret store.KBS2_SUBCOMMAND: Always set to1. This can be used to determine whether a subcommand was run viakbs2(e.g.kbs2 foo) versus directly (e.g.kbs2-foo).KBS2_MAJOR_VERSION,KBS2_MINOR_VERSION,KBS2_PATCH_VERSION: The major, minor, and patch numbers for the version ofkbs2that executed this subcommand. Subcommands can use these numbers to enforce running under a minimum (or maximum) version ofkbs2.
The contrib/ext-cmds directory contains several useful external commands.
kbs2 exposes hook-points during the lifecycle of an invocation, allowing users to
inject additional functionality or perform their own bookkeeping.
All hooks, whether pre- or post-, have the following behavior:
- Hooks do not inherit
stdinorstdoutfrom the parentkbs2process - Hooks do inherit
stderrfrom the parent process, and may use it to print anything they please - Hooks always run from the
storedirectory - Hooks are run with
KBS2_HOOK=1in their environment and withKBS2_CONFIG_DIRset to the configuration directory that the originalkbs2command was loaded with - An error exit from a hook (or failure to execute) causes the entire
kbs2command to fail
Hooks may introduce additional behavior, so long as it does not conflict with the above. Any additional hook behavior is documented under that hook's configuration setting.
kbs2's hooks are non-reentrant by default.
To understand what that means, imagine the following hook setup:
pre-hook = "~/.config/kbs2/hooks/pre.sh"# ~/.config/kbs2/hooks/pre.sh
kbs2 some-other-commandand then:
$ kbs2 listIn this setting, most users would expect pre.sh to be run exactly once: on kbs2 list.
However, naively, it ought to execute twice: once for kbs2 list, and again for
kbs2 some-other-command. In other words, naively, hooks would reenter themselves whenever
they use kbs2 internally.
Most users find this confusing and would consider it an impediment to hook writing, so kbs2
does not do this by default. However, should you wish for reentrant hooks, you have two
options:
- You can set
reentrant-hookstotruein the configuration. This will make all hooks reentrant — it's all or nothing, intentionally. - You can
unsetor otherwise delete theKBS2_HOOKenvironment variable in your hook before runningkbs2internally. This allows you to control which hooks cause reentrancy. Beware:KBS2_HOOKis an implementation detail! Unset it at your own risk!
kbs2 supports two basic options for managing the (wrapped) key that encrypts all records
in the secret store: rewrapping and rekeying.
Rewrapping means changing the password on your wrapped key. Rewrapping does not
modify the underlying key itself, which means that your individual records in the store
do not change. Rewrapping is done with the kbs2 rewrap command.
You should rewrap under the following (non-exhaustive) conditions:
- You're doing a routine update of your master password
- You believe that your master password has been disclosed, but not the underlying wrapped key
Rekeying means changing the wrapped key itself, and consequently re-encrypting every record
with the new wrapped key. When rekeying you can choose the same master password as the old key.
However, you should choose a new password. Unlike rewrapping, rekeying does change
the individual records in your store, and makes them no longer decryptable with your previous
key. Rekeying is done with the kbs2 rekey command.
You should rekey under the following (non-exhaustive) conditions:
- You believe that your underlying wrapped key has been disclosed
- You're sharing a
kbs2to a new device, and you'd like that device to have its own wrapped key
Rekeying is a more drastic operation than rewrapping: it involves rewriting the keypair,
the kbs2 config, and every record in the store. This means it comes with some technical caveats:
-
kbs2 rekeydoes not preserve the layout of your config file. Users should be mindful of this when rekeying. -
kbs2 rekeymakes a backup of the secret store by copying each record in the store to a backup folder. Anything in the secret store that is not a record (like a metadata or revision control directory, or a hidden file) is not copied during backup. Rekeying causeskbs2to write the newly encrypted records into the same store, so any non-record members of the store will remain unmodified.
No good reason. See the history section.
kbs2's threat model is similar to that of most password and secret managers. In particular:
kbs2does not attempt to defend against therootuser or arbitrary code executed by the current user.kbs2tries to avoid operations that would result in secret material (i.e. the private key and the decrypted contents of records) being saved or cached on disk, but does not attempt to present the consumers of secret material from doing so.kbs2, by default, attempts to prevent offline private key extraction by encrypting the private key at rest with a master password.kbs2does not attempt to prevent the user from mishandling their master password.
kbs2 does not implement any cryptography on its own — it uses only the cryptographic
primitives supplied by an age implementation. In particular,
kbs2 uses the rage implementation of age.
The particulars of kbs2's cryptographic usage are as follows:
- Every
kbs2configuration file specifies a symmetric keypair. The public key is stored in thepublic-keyconfiguration setting, while the private key is stored in the file referenced by thekeyfilesetting. - By default,
kbs2"wraps" (i.e. encrypts) the private key with a master password. This makes offline key extraction attacks more difficult (although not impossible) and makes the consequences of wrapped private key disclosure less severe. Users may choose to use a non-wrapped key by passing--insecure-not-wrappedtokbs2 init.
As mentioned under Threat Model and Cryptography, kbs2 uses
a wrapped private key by default.
Without any persistence, wrapped key usage would be tedious: the user would have to re-enter
their master password on each kbs2 action, defeating the point of having a secret manager.
To avoid this, kbs2 establishes persistence of the unwrapped key with an authentication agent:
running kbs2 agent will start a daemon in the background, which subsequent kbs2 invocations
can connect to (as needed) via a Unix domain socket. By default, running kbs2 agent
will prompt the user for the currently configured key's master password. Users can add additional
unwrapped keys to their running agent by invoking kbs2 agent unwrap.
Hacking on kbs2 is relatively straightforward. To build a fully functional development copy,
just use cargo build in the repository root:
$ cargo build
$ ./target/debug/kbs2 --helpOf note: some functionality in the age crate has pathological performance in debug builds. In particular, decryption and key unwrapping are known to be particularly slow.
To avoid this, use a release build:
$ cargo build --release
$ ./target/release/kbs2 --helpkbs2 uses log and env_logger for logging. You can past RUST_LOG=debug in your environment
to enable debug logging:
$ RUST_LOG=debug ./target/release/kbs2 list -k loginSee the env_logger documentation for more possible RUST_LOG values.
TL;DR: kbs2 is short for "KBSecret 2".
In 2017, I wrote KBSecret as a general purpose secret manager for the Keybase ecosystem.
KBSecret was written in Ruby and piggybacked off of Keybase + KBFS for encryption, storage, and synchronization. It was also extremely flexible, allowing user-defined record types, secret sharing between users and teams, and a variety of convenient and well-behaved CLI tools for integration into my development ecosystem.
Unfortunately, KBSecret was also extremely slow: it was written in obnoxiously metaprogrammed Ruby, relied heavily on re-entrant CLIs, and was further capped by the latency and raw performance of KBFS itself.
Having a slow secret manager was fine for my purposes, but I no longer trust that Keybase (and KBFS) will continue to receive the work they require. I also no longer have the time to maintain KBSecret's (slowly) deteriorating codebase.
kbs2 is my attempt to reproduce the best parts of KBSecret in a faster language. Apart from the
name and some high-level design decisions, it shares nothing in common with the original KBSecret.
It's only named kbs2 because I'm used to typing "kbs" in my terminal.