1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
|
# szecret
Secret management using [age](https://github.com/FiloSottile/age) encryption.
## Quick Start
```bash
# Store a secret
echo "my-api-key" | szecret put api-key
# Retrieve a secret
szecret get api-key
# List all secrets
szecret list
```
## Commands
| Command | Description |
|---------|-------------|
| `get <name>` | Retrieve and decrypt a secret |
| `put <name>` | Store a secret (reads from stdin) |
| `list` | List all secrets in the store |
| `ready` | Check if private key is configured |
| `ensure` | Ensure private key is configured (interactive) |
| `rotate` | Generate a new key and re-encrypt the global store |
| `create-local <name> <dir>` | Create a localized secret store |
| `put-local <file>` | Encrypt to a local store |
| `get-local <file>` | Decrypt from a local store |
## Local Stores
Local stores allow you to keep encrypted secrets outside the global store, useful for project-specific secrets that live alongside the code.
### Creating a Local Store
```bash
# Initialize a local store in a directory
szecret create-local myproject /path/to/project/secrets/
# This creates:
# - A key in the global store (local-key-myproject)
# - A metadata file at /path/to/project/secrets/.szecret containing:
# store=myproject
# pubkey=age1...
```
The metadata includes the public key for verification, preventing accidental use of the wrong store.
### Using a Local Store
```bash
# Encrypt a secret
echo "db-password" | szecret put-local /path/to/project/secrets/db.age
# Decrypt a secret
szecret get-local /path/to/project/secrets/db.age
```
The commands automatically read the store name from the `.szecret` metadata file in the directory.
## Setup
### Where the store lives
These scripts carry no secrets. The global store is a directory of `.age`
files plus `id.pub`, the public key they are encrypted to, kept in a checkout
of its own. szecret looks for it in this order:
1. `$SZECRET_STORE` and `$SZECRET_PUBLIC_KEY`, if set.
2. `~/.local/share/szecret/store` and `~/.local/share/szecret/id.pub`, beside
the private keys. Normally symlinks into the checkout that holds the store.
With neither, every command stops with setup instructions, and `ready`
fails silently.
### Initial Setup
```bash
# Generate a new keypair (file must be named after its public key)
age-keygen -o private
pubkey="$(age-keygen -y private)"
mkdir -p ~/.local/share/szecret/store
echo "${pubkey}" > ~/.local/share/szecret/id.pub
mv private "${HOME}/.local/share/szecret/${pubkey}"
```
### On a New Machine
```bash
# The ensure command will prompt for your private key
szecret ensure
```
### Rotating the Global Key
```bash
# Re-encrypt all secrets with a new age key
szecret rotate
```
This updates `id.pub` and every file in the store. When complete, commit
those changes in the checkout that holds the store, and update the private key
backup in 1Password.
## Migration Guide
### Migrating Local Stores (pre-2026)
If you created local stores before the metadata system was introduced, run
`create-local` again with the directory - it will detect the existing key
and just add the metadata:
```bash
szecret create-local myapp /path/to/secrets/
# Output: Added metadata for existing store 'myapp'
```
**Before (old syntax):**
```bash
szecret create-local myapp
echo "secret" | szecret put-local myapp /path/to/secrets/password.age
szecret get-local myapp /path/to/secrets/password.age
```
**After (new syntax):**
```bash
# One-time migration
szecret create-local myapp /path/to/secrets/
# Now use simplified commands
echo "secret" | szecret put-local /path/to/secrets/password.age
szecret get-local /path/to/secrets/password.age
```
**For automated scripts:** The old `get-local <store-name> <file>` syntax still works but emits a deprecation warning suggesting the migration command.
## Environment Variables
For testing or custom setups, these environment variables override default paths:
| Variable | Description | Default |
|----------|-------------|---------|
| `SZECRET_STORE` | Global store directory | `~/.local/share/szecret/store` |
| `SZECRET_PUBLIC_KEY` | Public key path | `~/.local/share/szecret/id.pub` |
| `SZECRET_PRIVATE_KEY` | Single private key file (full override) | (none) |
| `SZECRET_PRIVATE_KEY_DIR` | Directory containing private keys (named by their public key) | `~/.local/share/szecret` |
* Private keys are stored in `~/.local/share/szecret/` (or `$SZECRET_PRIVATE_KEY_DIR`), with each file named after its public key (e.g. `age1...`).
* `id.pub` indicates which public key is current.
## Testing
```bash
# Run all tests
./test/run.sh
# Run specific tests
./test/run.sh local create-local
```
See [test/README.md](test/README.md) for more details.
|