# Security

**BLUF**: See the section [Linux kernel keychain](#linux-kernel-keychain) for the
most secure way to run rook. It is fussy, and requires additional tools, and
_only_ works on Linux, but it's about as secure as possible.

Rook's _base_ threat model is simple: it doesn't expose anything to any network
connection, but _does_ trust that the local system is secure. Don't use Rook if
you don't trust the root user, or worry that someone could masquerade as your
user. If your user or root accounts are compromised, then an attacker can talk
to the Rook server over the socket and get at your secrets. Since the socket is
created with user-only RW permissions, and is created in the user's home
directory, it should still be safe on multi-user systems -- with the
aforementioned caveat that root has access to everything. See the note below
about [Secure mode](#secure-mode) about how to tighten security, which
removes most of the caveats about trusting the system.

Rook attempts to keep external dependencies to an absolute minimum. Rook itself
is not a large program, and is only multiple files for semantic encapsulation.
Aside from the Rook code itself, dependencies are:

- [github.com/tobischo/gokeepasslib/v3](https://github.com/tobischo/gokeepasslib),
  which is the library used to process the KeePass libraries. It should have
  *no* network access, and is easy enough to grep for `net` imports. It, itself,
  depends on
- [github.com/tobischo/argon2](https://github.com/tobischo/argon2), which is a
  crypto library. Again, no network and easy enough to grep for `net`
- [golang.org/x/crypto](https://golang.org/x/crypto), which I haven't audited,
  but I guess I trust the Go core team enough
- [golang.org/x/sys](https://golang.org/x/sys), same as above
- [ser1.net/claptrap/v4](https://hg.sr.ht/~ser/claptrap), my bespoke flags
  library. It has no net access, is entirely a single 4k-line file. **I** trust
  it implicitly, but again, it's easy enough to check to make sure it's not
  doing anything shady. Again, the same author who wrote rook also wrote
  claptrap (me).

So, in all of this, Rook should be the only thing using `net`, and would be the
only thing (if I weren't me) I'd be concerned about. **rook** needs `net` to
create the local socket over which the client/server communicate, and there are
only two places **rook** opens a connection, both of which are to a local
socket.

Regardless, as with _any_ secret services, if you don't trust your own system,
don't run these. Don't run KeePassXC at all, because with enough effort, root
can still get memory dumps and extract secrets from your open database.

## Secure mode

Security can be improved by running rook in secure mode, by starting the server
with `--secure`. In this mode, the server will print out a 6-digit pin that the
client will always have to provide for every interaction. Locking and unlocking
the database will generate a new pin.

```
$ rook serve --secure -P secret.kdbx
Password:
123456
$ rook ls
FAIL
BAD COMMAND
exit status 1
$ echo 123456 | rook --pin ls
<all your secrets>
```

There are some subtleties to note:

- There is a difference between a rook PIN and a rook OTP. On OTP is for one-
  time, and one time only. A PIN is for every action (with two exceptions
  below).
- The `open -P` command does _not_ require the PIN: in all of rook, if you can
  prove that you have the DB master password, all other security features are
  irrelevant. `open -P` will print the current PIN.
- If you `--secure` a database, you can still OTP lock and unlock the DB; in
  this scenario, you will _double_ lock the DB. To unlock the first lock, you'll
  need to call `open --pin` with the OTP, after which you're back to simply
  needing to supply the PIN for every command.
- You may OTP lock a PIN authenticated database. This will, effectively, double-
  lock the database, requiring **both** first a `open --pin`, and then the PIN
  auth for all following commands. As usual, providing the master password with
  `open -P` completely voids these locks.

If you hard `lock` the database, the PIN will be cleared. When you `open -P` it
again, a _new_ PIN will be generated. This bears on cases where you're using it
with the Linux kernel keychain, described below.

### Linux kernel keychain

**Note** that this is more easily managed with the `--keyring` feature
introduced in v0.3.0. This section is left in only in case you want to work with
the kernel keyring manually.

This PIN feature can be used with the Linux kernel keychain to further
strengthen security by preventing other user sessions, and even root, from
accessing secrets. This is the most secure way to run rook.

You will need `keyutils` installed, for the `keyctl` command. To use this, run
the rook server like this:

```sh
$ export ROOK_KEY=$(rook serve --secure --detach ~/secrets.kdbx | keyctl padd user rookpin @s)
2025/04/08 10:40:57 [ERROR] unable to decode database "/home/user/secrets.kdbx": Wrong password? HMAC-SHA256 of header mismatching
2025/04/08 10:40:57 [ERROR] reload() error on server creation
$ rook open -P
Password:
$ keyctl pipe $ROOK_KEY | rook --pin ls
<all your secrets>
```

Note the errors are expected as the database password was not passed to the
serve command.

Since v0.3.0, rook automates this on Linux via the `--keyring` argument. The
process above can be simplified to:

```sh
$ rook --keyring serve -P --detach ~/secrets.kdbx
Password:
$ rook --keyring ls
...
```

However, if you're running `rook serve` in your autostart sequence, you'll be
calling `open -P` after your session is up:

```sh
$ rook open -P
Password:
```

The `--keyring` is not necessary for `open` since it is not a read operation.

#### Hard locks

**Note** that if the database gets hard-locked, by repeated attempts to read
data without providing a pin, or if `rook lock` is called; or if rook is
restarted, then `rook open -P` will generate a **new** pin, and the kernel key
value will need to be updated with this value. In these cases (or in all cases
-- it doesn't hurt to do it this way), run this command:

```
$ rook open -P | keyctl pupdate $(keyctl list @s | rg '^(\d+).*: rookpin' -or '$1')
 
```

Yeah, it's a mouthful, but it's self-contained. The subcommand just gets the key
ID; this could easily be stored in an environment variable, or pulled from your
window manager's environment (e.g. `herbstclient getenv ROOKKEY`) if you set it.

Because of all this interaction with the keychain, you'll probably want to
create a wrapper script; in my case, I created an `rk` file in my `$PATH`:

```zsh
#!/usr/bin/zsh

keyctl pipe $(keyctl list @s | rg '^(\d+).*: rookpin' -or '$1') | rook --pin $@
```

This allows any process to get at the current kernel key ID, even if it changes.
Again, the key ID is not a secret, so this is safe.

If rook crashes, or is killed, you can just run it again and re-open it; as long
as you update the kernel keychain value with `keyctl pupdate` into the same key
name (`ROOK_KEY` above), your session processes will be able to access it.

Explaining they kernel keychain and its use is beyond the scope of this
document, but in short:

- `keyctl` is the tool for interacting with the secure kernel keychain. The `@s`
  restricts access to secrets to programs running in the same session. This
  prevents root from peeking at the PIN, but also prevents a hacker from logging
  in as you and accessing the PIN. In `--secure` mode, without the PIN, the
  secrets can't be accessed.
- The value of `$ROOK_KEY` is the kernel's generated key name; it is not itself
  a secret, but is needed to get at the PIN; the value stored in the key can
  only be retrieved by a process in the same session.
- The secrets (the PIN) is stored _in_ the kernel. All of your passwords are
  still in your KeePass DB; it's only the temporary PIN that's saved in the
  kernel, and that's cleared on reboot. You can also manually clear the value
  with `keyctl`, but it's probably just to `lock` the DB, which invalidates the
  PIN.

## (Auto)Locking

If you want the DB to be locked periodically, run an external cron job that
calls `lock`. Rook _might_ add a lock-after-inactivity function, since this
can't really be done externally, but (a) it'd be useless to me since the
_purpose_ of rook is as a secret-service for other cron jobs like mail syncing,
and so it'd _never_ timeout and lock; (b) I'm really trying to limit the amount
of code in rook, making it more auditable, and that means leaving out features;
and (c) a general lock-after-time regardless of use is arguably more secure, and
a more common feature in these sorts of tools -- and that *can* be done
externally.

## Master password plaintext considerations

Rook itself does not keep the master password in memory in clear text, and
neither does gokeepasslib. Variables that hold the password during credentialing
are cleared, or are scoped out in brief functions.

Don't use the `--password` argument. It's there to simplify testing, nothing
more. If you use it, it could easily be stored in a shell history file, or even
worse, be seen by any user in the sysetem through `ps`.

Use of `ROOK_PASSWORD` for the server is discouraged. While any account that can
inspect the rook process' environment (via `/proc/<pid>/environ`) can also
probably dump memory and get at your secrets, by using the environment variable
your master password is stored in plain text and this is less secure. It's less
of a concern for client calls, because client calls are ephemeral.

The best way to minimize the master password's existence in memory in plain text
is to run the rook server with at most the database path, and then use the
`open` command with the client, e.g.:

```
$ rook serve database.kdbx
2025/04/08 10:40:57 [ERROR] unable to decode database "/path/to/kdbx": Wrong password? HMAC-SHA256 of header mismatching
2025/04/08 10:40:57 [ERROR] reload() error on server creation
$ rook open -P
SUCCESS
```
