# bitwarden-ssh-agent: a script its own author asks you not to use

> This small Python script loads SSH keys stored in a Bitwarden vault into a running ssh-agent, and it is marked deprecated at the top of its README because Bitwarden now ships a native SSH agent. It is worth reading as a worked example of the Bitwarden CLI, and worth not installing.

**joaojacome/bitwarden-ssh-agent** — Small python script to load bitwarden-store ssh keys into ssh-agent

- Repository: https://github.com/joaojacome/bitwarden-ssh-agent
- Stars: 589 · Forks: 52
- Language: Python
- License: MIT
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/joaojacome-bitwarden-ssh-agent

## The README starts with a deprecation warning, not a feature list

The first thing in the repository is a warning block, and it is unambiguous. Bitwarden now has a native SSH agent implementation, there is no reason to use this tool anymore, and you should not use it.

That framing changes how the rest of the page should be read. This is not a project waiting for contributors or a tool with an uncertain future. It is a finished piece of work that has been overtaken by a first party feature, kept in the repository for reference.

The consequence for you is practical. If you are starting a new setup, the decision is made for you, and the article you are reading is about the design rather than about adoption. If you have this script in a dotfiles repository or a setup script somewhere, the useful action is to remove it and move to the native agent.

The description of the repository still says small python script to load bitwarden-store ssh keys into ssh-agent, and that is exactly the scope. It is a single script, bw_add_sshkeys.py, and the top level of the repository holds little else: the script, a README, a LICENSE, a pyproject.toml, a setup.cfg, a mypy.ini, and Nix files in the form of flake.nix and flake.lock. The MIT licence and the Nix packaging are the kind of housekeeping a well maintained little project accumulates, even after it is retired.

## Two requirements, and both are outside the script

The requirements section lists two things, and neither is Python.

The first is the Bitwarden CLI tool, which has to be installed and available in the PATH as bw. The second is that ssh-agent must be running in the current session.

Both requirements are about the environment rather than the code, and both explain where the fragility of this approach lives. The script is a bridge between a password manager and a key agent, and the bridge only works if the pieces on either side are set up and reachable. When the Bitwarden CLI is not on your PATH, the script fails immediately with a missing command rather than with anything about keys.

The ssh-agent requirement is subtler. A script that adds keys to an agent can only add them to an agent that is already listening on the socket it expects. A session that has been running for weeks with an old agent socket, or an agent that was never started, is a common source of the key not appearing even though everything else worked.

The repository also includes a mypy.ini and a pyproject.toml, and the pyproject.toml contains only a Black configuration targeting py310. So the project is typed and formatted, and the tooling footprint is limited to that. There is no test suite in the tree, which is consistent with a single file script that talks to a CLI over a pipe.

## Installation is copying one file and making it executable

There is no package to install. The instruction is to save the file bw_add_sshkeys.py in a folder where it can be found when calling it from the command line, and the README explains how to find those folders on Linux by running echo $PATH.

For a single user install, the suggested approach is to save the script under ~/.local/bin/ and make it executable:

The same section later applies the same technique to the Bitwarden CLI binary, recommending moving the executable to ~/.local/bin and making it executable with chmod +x ~/.local/bin/bw, with ~/.local/bin likely already on the path. You can confirm the result by running which bw, which should return the path to the executable.

Using it is then three steps. Run the script:

```shell
   ./bw_add_sshkeys.py
```

Then enter your Bitwarden credentials if a vault session is not already set, and optionally enter the passphrases of your SSH keys if they are not stored in your Bitwarden.

The last step is the one that shows the design. The script can either use a passphrase it finds in the vault or prompt you for one. If the passphrase is stored in Bitwarden, the script can decrypt the key without you typing anything, which is the whole point of the approach.

## The vault layout is a folder of secure notes with two custom fields

The README documents the expected structure of the vault, and it is a convention rather than a schema, which means a mistake shows up as a key that does not appear.

You create a folder called ssh-agent, which can be overridden on the command line. Into that folder you add a new secure note for each key. The private key goes either directly in the notes field, or as an attachment. The attachment route requires Bitwarden Premium, and it needs a custom field called private containing the file name of the private key attachment, with the field name also overridable on the command line.

The passphrase is optional and works the same way. If your key is encrypted with a passphrase and you want it decrypted automatically, you save the passphrase into a custom field called passphrase, and the field name can be overridden too. You can create that field as hidden if you do not want the passphrase displayed by default.

Then you repeat those steps for each subsequent key. Two things are worth noting. The hidden field option matters, because a passphrase visible in the vault UI is a weaker control than one that needs a deliberate reveal. And storing the passphrase next to the key it protects means the two are protected by the same vault session, which is a real consideration about what the arrangement actually buys you.

## Five command line flags cover the configuration surface

The command line overrides section lists five flags, and they map onto the vault layout described above.

The --debug or -d flag shows debug output. The --foldername or -f flag sets the folder name used to search for SSH keys, defaulting to ssh-agent. The --customfield or -c flag sets the custom field name where the private key file name is stored, defaulting to private. The --passphrasefield or -p flag sets the custom field name where the passphrase for the key is stored, defaulting to passphrase. And --session or -s takes a session key of bitwarden.

The session flag is the one that changes the interaction model. Instead of typing your Bitwarden credentials, you can pass a session key so the script reuses an existing open session, which is what makes it usable in an automated or repeated context.

Taken together, the flags exist because the vault layout is user defined. A person who keeps keys in a differently named folder, or who names their custom fields differently, can point the script at their own conventions rather than rearranging the vault. That is a reasonable design for a tool whose job is to read somebody else's data.

## What the script actually does with the vault

The answer to what it does is one line: it fetches SSH keys stored in a Bitwarden vault and adds them to ssh-agent.

The mechanism behind that line is the Bitwarden CLI. The setup section for the CLI says to download it, extract the binary from the zip file, make it executable and add it to your path so that it can be found on the command line. If you want to build the CLI yourself, the README links the instructions on the Bitwarden GitHub page.

So the flow is: the script calls bw to log in and list the items in the named folder, extracts the private key material from each secure note, whether from the notes field or from an attachment referenced by a custom field, retrieves the passphrase from its custom field when present, and hands the resulting key to the running ssh-agent.

That is the entire value proposition, and it is a modest one by design. It does not manage the vault, it does not sync keys, it does not keep an agent alive, and it has no notion of which host a key is for. It moves keys from one place to another so that the tools you already use can find them, and it does that at the moment you run it.

The native Bitwarden agent covers the same ground inside the password manager itself, which is why the deprecation notice is not a soft warning.

## Conclusion

Do not install this script in new setups, because its own README states that Bitwarden now has a native SSH agent implementation and that there is no reason to use this tool anymore. Read it instead if you want a concrete example of driving the Bitwarden CLI to read a vault, since the folder, secure note and custom field conventions it uses are documented in full, and if you are still on an old setup, migrate to the native agent rather than maintaining a script that only exists because the native agent was not there yet.

## FAQ

### Is bitwarden-ssh-agent still recommended?

No. The README opens with a deprecation warning stating that Bitwarden now has a native SSH agent implementation, that there is no reason to use this tool anymore, and that you should not use it.

### What do I need before running bw_add_sshkeys.py?

The Bitwarden CLI tool installed and available in your PATH as bw, and an ssh-agent running in the current session. The script is then copied to a folder on your PATH, for example ~/.local/bin, and made executable with chmod +x.

### How are the SSH keys stored in Bitwarden?

In a folder called ssh-agent, one secure note per key. The private key goes either directly in the notes field or as an attachment, with a custom field named private holding the attachment file name. An optional custom field named passphrase holds the key passphrase and can be created as a hidden field.

### What command line options does bw_add_sshkeys.py accept?

The README lists --debug or -d, --foldername or -f defaulting to ssh-agent, --customfield or -c defaulting to private, --passphrasefield or -p defaulting to passphrase, and --session or -s for a bitwarden session key.

## Sources

- [Issues](https://github.com/joaojacome/bitwarden-ssh-agent/issues)
- [joaojacome/bitwarden-ssh-agent on GitHub](https://github.com/joaojacome/bitwarden-ssh-agent)
- [License: MIT](https://github.com/joaojacome/bitwarden-ssh-agent/blob/master/LICENSE)
- [README](https://github.com/joaojacome/bitwarden-ssh-agent/blob/master/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/joaojacome-bitwarden-ssh-agent
