---
title: "Keep an app's trading key and secrets off the server's disk"
description: "erpc worker keeps an app's trading key and secrets in 1Password, the OS keychain or an encrypted keystore, and hands them to the app in memory."
canonical: "https://erpc.global/en/doc/erpc-cli/worker-secrets/"
locale: "en"
---

# Keep an app's trading key and secrets off the server's disk

`erpc worker` keeps an app's trading key and secrets in 1Password, your keychain or an encrypted keystore, and hands them to the app in memory, never to the server's disk.

This page describes ERPC CLI `v0.1.0-rc.4`, a pre-release.

## How the trading key reaches the app

An app with a wallet gets its key on a pipe when it starts. There is no `wallet.json`, no `.env` and no copy on the server. erpc writes the key to no disk unencrypted: the one file it puts it in is the keystore's, on your machine, encrypted under your passphrase.

- **Where it is kept:** `erpc worker key new` makes the key in 1Password when `erpc login` keeps the session there, else in the keychain, else in the keystore. `erpc.toml` records the key's reference and its public key.
- **On the server:** the erpc worker agent holds the key in memory only. It makes itself undumpable and locks its memory, and hands the key to the app on a file descriptor; the app wipes it after reading.
- **After a reboot,** the worker comes back locked: the app does not run until `erpc worker start` sends the key again. An agent that fails or is killed is started again by systemd and comes back locked the same way.
- **A package update does not restart a worker.** `deploy` tells `needrestart` to leave the worker units alone: in its automatic mode it defers them, and asked at a terminal its default answer for them is no, so an unattended security update does not restart the app. When that rule could not be written, the deploy says so and goes on, and the worker then runs without this protection.

The trading key is named by `[wallet]` and by nothing else, and erpc reads it only for a person at a terminal: not for an AI tool, and not with a 1Password service account. A `[secrets]` line that names the `[wallet]` reference, or a `keychain:` or `keystore:` account ending in `.wallet`, is refused. In the keystore an entry kept as a key does not open as a secret. In 1Password, and under a keychain account of another name, nothing marks a key: an `erpc.toml` with no `[wallet]` that names such an item under `[secrets]` has it read as a secret.

## Choose where values are kept

1Password is optional. `erpc worker key new` and `erpc worker secret put` keep what they are given in the first of these that applies, unless `--store` names one (`--store 1password --vault <id>`, `--store keychain`, `--store keystore`):

1. **1Password**, when `erpc login` keeps the session there or `--vault` is given.
2. **The OS keychain**, where it keeps a value past a reboot: macOS, Windows, and Linux with a desktop keyring in a logged-in session.
3. **erpc's keystore**, on a Linux machine with no such keyring: a headless server, WSL, an SSH login. `--store keychain` on such a machine is refused.

In the keychain or the keystore, the key exists on that one machine only, with no copy and no sync, and another machine cannot read a `keychain:` or `keystore:` reference.

A lost or wiped machine means the wallet is gone: keep little in it, or use 1Password, which every machine signed in to the vault can read. Sign in to 1Password with `erpc login --store 1password-desktop`, or through `op signin` where there is no desktop app.

A 1Password service account can hold an app's secrets, but erpc does not read a trading key with one. With 1Password, every read may show an approval prompt in the app, and erpc shows a spinner while it waits.

## Keep a secret

Secrets, such as API tokens and RPC keys, are kept the same way, but reach the app as environment variables, not on a pipe. `erpc worker secret put <NAME>` keeps the value, and `[secrets]` in `erpc.toml` holds only its reference.

```bash
erpc worker secret put API_TOKEN --generate          # the bot's own API token, made for you and shown to nobody
erpc worker secret put ERPC_API_KEY --from-account   # the account's RPC key, read from your account (or SOLANA_RPC_ENDPOINT in [vars])
```

- **`--generate`** makes the value: 32 random bytes, as 64 hexadecimal characters.
- **`--from-account`** keeps the first of your account's RPC keys that the shared RPC takes. Keys are tried in order, the primary one first, with one `getHealth` each, given 8 seconds to answer. A key answered 401 or 403 is refused and the next is tried, and only a key the RPC took is kept. The command says whether it is the account's primary key or another (`account_key` with `--json`).
- Neither value is printed, with `--json` either. The first use of the keystore, or a locked one, needs a person at a terminal.
- Keys are tried from this machine, so a key limited to the server's address is refused here though it works there. When every key is refused, `--from-account` keeps nothing, and you keep the key you mean by hand, typed unseen or piped in. When the RPC could not be asked, it keeps nothing and `erpc.toml` is as it was; run the same command again.

To keep a key by hand:

```bash
erpc worker secret put ERPC_API_KEY        # asks for the value, unseen
```

- `--replace` keeps a new value for a name that is set already. `erpc worker secret list` lists the secret settings by name and where each is kept.
- A setting a data line provides (`GRPC_ENDPOINT`, `SHREDS_ENDPOINT`, `SOLANA_SEND_RPC_ENDPOINT`) is never a secret: `--grpc`, `--shreds` and `--unlimited` set it.

A reference in `[secrets]` or `[wallet]` is one of these:

| Reference | Where the value is |
| --- | --- |
| `op://<vault-id>/<item-id>/<field>` | 1Password, by vault and item id |
| `keychain:<service>/<account>` | The OS keychain of the machine that kept it |
| `keystore:<service>/<account>` | erpc's keystore on the machine that kept it |

## The keystore

The keystore is the directory `~/.erpc/keystore` (`keystore` under `ERPC_HOME` when that is set). On Linux and macOS its directories are `0700` and its files `0600`; on Windows erpc sets no permissions of its own.

- `master.json` holds a random 32-byte master key, sealed under a key that PBKDF2-HMAC-SHA256 (600,000 iterations) makes from your passphrase. Each entry holds one value, sealed with ChaCha20-Poly1305 under a key of its own.
- **The passphrase:** the first `key new` or `secret put` that uses the keystore has you choose it, typed twice at a terminal, 12 characters or more. erpc takes it from that hidden prompt only, never from an argument, an environment variable or a pipe.
- **Unlocking on Linux:** once the passphrase has opened the keystore, the unlock is kept in the kernel's persistent keyring, in memory, until the machine restarts, the keyring goes unused for its idle expiry (three days, unless the system says otherwise), or `erpc worker secret lock`. `erpc worker secret unlock` asks for the passphrase ahead of time and says until when. In a shell started without a login of its own (`su` without `-`, `sudo -E`), no unlock is kept and each command asks.
- **With nobody at a terminal,** a command that needs the keystore uses the unlock when there is one, and otherwise fails, naming `erpc worker secret unlock`. An unlocked keystore hands over secrets, not the trading key.
- **On macOS and Windows** (`--store keystore`), no unlock is kept: each command that needs the keystore asks for the passphrase once, and so needs a terminal.

### What the keystore protects, and what it does not

- **Back up the `keystore` directory and remember the passphrase.** erpc can recover neither, and without both, what the keystore holds is gone.
- Whoever has the keystore files and the passphrase has the key. PBKDF2 makes every guess slow, and does not make a guessable passphrase safe.
- While the keystore is unlocked, any program running as you can read the master key from the kernel keyring (one whose session does not hold your keyring can link it in) and open every entry, a key's too. erpc's own rules (the key `[wallet]` names only for a person at a terminal, never for an AI tool; a key kept in the keystore never opened as a secret) are what erpc does; they do not bind other programs running as you. Run `erpc worker secret lock` when you are done.
- Entry names are not hidden, and an entry's size shows how long the value is.
- A changed file does not open; an older copy of the same entry put back in its place does.
- `erpc worker secret rm` deletes a secret's keystore entry along with its line in `erpc.toml` (unless another setting of the app names the same entry), and leaves a value in 1Password or the keychain where it is. It does not scrub the disk, and copies in your backups stay.
- erpc never deletes or writes over a trading key's entry. To replace a key, move the funds, then remove its file and `[wallet]` yourself.
- A setting cannot be called `WALLET`, in any case, in the keystore or the keychain; `--store 1password` takes the name.

## Work with the trading key

| Command | What it does |
| --- | --- |
| `erpc worker key new` | Makes the trading key and prints the public key to fund |
| `erpc worker key import <FILE>` | Moves a key file into 1Password, the keychain or the keystore, then offers to wipe the file |
| `erpc worker key show` | Shows the key's public key and where it is kept |
| `erpc worker key balance` | Shows the key's balance, through the app's own RPC setting |
| `erpc worker key send --to <ADDRESS> --sol <N>` | Signs one transfer in memory after your yes at a terminal. `--all` sends everything but the fee |

When erpc ends without knowing whether a transfer landed, look up its signature before you send again: a second send is a second transfer.
