Sterling has too many projects Blogging about programming, microcontrollers & electronics, 3D printing, and whatever else...

Ghost: The Go Secret Toolkit

| 1041 words | 5 minutes | golang cli tools secrets ghost
Photo of a empty white hoodie in front of a moving background.

I am struggling to come up with a witty introduction to this topic, so let me just start by telling the origin story. Several years ago, I added some tooling to help me manage secrets related to my dotfiles. The first incarnation was a simple .netrc type solution where I stored the secrets in plain text in files that only my user could read. I added some scripts to help reading and managing these a bit. Over time this evolved into a complex system that allowed me to pull down secrets from my online password vault and store them in a local encrypted Keepass database. This created a new problem: every time a script used a secret, I needed to enter a password. Eventually, I added a small password service to help keep track of the key used to unlock the various password stores so I wouldn’t have to type the password every time.1

The original code was all written in Perl and a little shell script. Then, I partially ported it to Golang adding more features. Some of my dotfiles tools depended on the older code, some on the newer, I didn’t implement some things very well and it was generally becoming a mess. As such, some fairly annoying bugs had crept into the code.

A few months back I decided to spend a few hours each week gathering up all the code and replacing it with a comprehensive system. This version 2.0 of my system went through a few iterations and I soon realized I’d implemented a more or less complete application. I dropped all that into a new project named ghost and here is me announcing it, in case someone other than myself may find it interesting.

The ghost tool provides two primary features:

  1. Keeping Secrets. It provides a command-line tool and Golang packages for maintaining one or more secret databases, reading and writing individual secrets, in a number of backends, including Keepass and LastPass, using a single interface.
  2. Synchronizing Secrets. It provides a command-line tool and Golang packages for copying secrets from one database to another, allowing me to create complete backups of my online password vaults directly into a secure destination.

Keeping Secrets

I will gloss over the configuration and other bits here. If you want to understand the full details of how to use this tool, you can read the README yourself.

Once configured, writing your secrets is as simple as:

ghost set --name "" --username "" --prompt

That will prompt you to enter your password using a pinentry dialog.

And then you can read the secret back via:

ghost get --name ""

and it will output something like: 
  ID: 348737777372782
  Location: Personal
  Password: <hidden>
  Modified: 2023-08-14 07:23:33 -0500 CDT

If you want to use this with a script. you might only be interested in the secret itself:

ghost get --name "" --show-password --output password

You can also output bash-style shell variables, YAML, or JSON. It will not output a password without the --show-password option.

Synchronize Secrets

The other really important use for me is the ability to synchronize my secrets securely. If I have a configuration that looks something like this in my ~/.ghost.yaml file:

    type: lastpass
        keeper: masterPasswordsServer
        secret: LastPass Password
        field: password

    type: keepass
    path: ~/.lastpass-backup.kdbx
        keeper: masterPasswordsServer
        secret: Keepass Master Password
        field: password

    type: human
      - id: LastPass Password
        ask_for: [ password ]

You can copy all the passwords from the LastPass database to a local Keepass database using this command:

ghost sync lastpass lastpass-backup

Now, this might take minutes or hours to finish running due to rate limits inteh LastPass API, but it will eventually make a full copy of your LastPass database. (Though, you must either ensure your LastPass database contains no secrets with duplicate name, username, and location values or you pass the --ignore-duplciates option, which will skip older duplicates.)

Bonus Feature: Password Generator

In addition to the tools mentioned above, it provides a tool for generating a random password. It has two basic modes: random list of characters and the correct-horse-battery-staple method.

ghost random-password

will output something like:


You can control the length with -n, and the frequency of various characters by increasing or decreasing their weights with various options.

To use the correct-horse-battery-staple mode you run the command like so:

ghost random-password --correct-horse-battery-staple

And it will pull random words from your /usr/share/dict/words file until it exceeds the requested length (again set with -n and defaulting to 20):

sick gibbet capons boldly

The default dictionary is not super helpful for password creation, so I need to see about coming up with a better dictionary or better way of filtering the dictionary, but it’s a start.

Other Features

There are a number of other tools provided as well. There’s a password service built in that lets you run a daemon that can temporarily store passwords and enforce secret lifetime policies. It can read and write passwords from the system keyring. It can request passwords from the person at the keyboard. It can store your passwords in an plain text file, if that’s useful to you for some reason (e.g., some API keys are not so important to keep super secure).

Final Notes

I am really happy with how this is working now, so I hope to be able to continue expanding, fixing, and tweaking it as needed. If you find it useful, but that it’s lacking something, patches welcome.

This software is brand new and I intend to use it from the command-line and from other programs I run directly and attended. I recommend against using it for production use of any kind. I won’t call it beta or anything, but it’s like pizza straight from the oven, I won’t be surprised if it burns a hole through my mouth if I’m not careful. That said, please let me know if you find it useful.


  1. All of this history is what I remember happening and this history is spread over several years. I’m not going to go mine my git log to tease out what really happened and the full timeline. This is not that kind of retelling. ↩︎

The content of this site is licensed under Attribution 4.0 International (CC BY 4.0).