burnedsecret-cli 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- burnedsecret_cli-1.0.0/.gitignore +56 -0
- burnedsecret_cli-1.0.0/PKG-INFO +278 -0
- burnedsecret_cli-1.0.0/README.md +262 -0
- burnedsecret_cli-1.0.0/burnedsecret_cli.spec +120 -0
- burnedsecret_cli-1.0.0/docs/exit-codes.md +35 -0
- burnedsecret_cli-1.0.0/packaging/homebrew/bs.rb.template +48 -0
- burnedsecret_cli-1.0.0/packaging/install/install.ps1 +104 -0
- burnedsecret_cli-1.0.0/packaging/install/install.sh +130 -0
- burnedsecret_cli-1.0.0/packaging/scoop/bs.json.template +36 -0
- burnedsecret_cli-1.0.0/pyproject.toml +28 -0
- burnedsecret_cli-1.0.0/src/burnedsecret_cli/__init__.py +3 -0
- burnedsecret_cli-1.0.0/src/burnedsecret_cli/auth_flow.py +369 -0
- burnedsecret_cli-1.0.0/src/burnedsecret_cli/client_factory.py +41 -0
- burnedsecret_cli-1.0.0/src/burnedsecret_cli/commands/__init__.py +1 -0
- burnedsecret_cli-1.0.0/src/burnedsecret_cli/commands/_ttl.py +77 -0
- burnedsecret_cli-1.0.0/src/burnedsecret_cli/commands/auth.py +216 -0
- burnedsecret_cli-1.0.0/src/burnedsecret_cli/commands/key.py +147 -0
- burnedsecret_cli-1.0.0/src/burnedsecret_cli/commands/request.py +271 -0
- burnedsecret_cli-1.0.0/src/burnedsecret_cli/commands/secret.py +302 -0
- burnedsecret_cli-1.0.0/src/burnedsecret_cli/config.py +251 -0
- burnedsecret_cli-1.0.0/src/burnedsecret_cli/errors.py +120 -0
- burnedsecret_cli-1.0.0/src/burnedsecret_cli/main.py +159 -0
- burnedsecret_cli-1.0.0/src/burnedsecret_cli/output.py +91 -0
- burnedsecret_cli-1.0.0/tests/test_auth_command.py +171 -0
- burnedsecret_cli-1.0.0/tests/test_auth_flow.py +360 -0
- burnedsecret_cli-1.0.0/tests/test_client_factory.py +99 -0
- burnedsecret_cli-1.0.0/tests/test_config.py +157 -0
- burnedsecret_cli-1.0.0/tests/test_key_command.py +107 -0
- burnedsecret_cli-1.0.0/tests/test_output_discipline.py +60 -0
- burnedsecret_cli-1.0.0/tests/test_request_command.py +202 -0
- burnedsecret_cli-1.0.0/tests/test_secret_command.py +217 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Flutter
|
|
2
|
+
.dart_tool/
|
|
3
|
+
.flutter-plugins
|
|
4
|
+
.flutter-plugins-dependencies
|
|
5
|
+
build/
|
|
6
|
+
pubspec.lock
|
|
7
|
+
|
|
8
|
+
# gen-l10n output — generated from lib/l10n/*.arb at build time (generate: true)
|
|
9
|
+
lib/l10n/app_localizations*.dart
|
|
10
|
+
# gen-l10n untranslated-messages-file report (l10n.yaml). Project-root-relative
|
|
11
|
+
# JSON listing, per locale, every key still missing from a target ARB. A
|
|
12
|
+
# generated WARNING report regenerated on every gen-l10n run — never committed
|
|
13
|
+
# (avoids churn); drained locally via scripts/translate_arb.py.
|
|
14
|
+
untranslated_messages.json
|
|
15
|
+
|
|
16
|
+
# Phase 15 (D-02): generated by scripts/build_docs.py in CI; source is docs/site/
|
|
17
|
+
web/docs/
|
|
18
|
+
web/security/
|
|
19
|
+
.venv-docs/
|
|
20
|
+
|
|
21
|
+
# Firebase
|
|
22
|
+
.firebase/
|
|
23
|
+
**/ui-debug.log
|
|
24
|
+
**/firestore-debug.log
|
|
25
|
+
**/firebase-debug.log
|
|
26
|
+
|
|
27
|
+
# Service account keys — NEVER commit
|
|
28
|
+
*-adminsdk-*.json
|
|
29
|
+
service-account*.json
|
|
30
|
+
*sa-key*.json
|
|
31
|
+
|
|
32
|
+
# Python
|
|
33
|
+
__pycache__/
|
|
34
|
+
*.pyc
|
|
35
|
+
functions/lib/
|
|
36
|
+
functions/venv/
|
|
37
|
+
functions/.venv-test/
|
|
38
|
+
.pytest_cache/
|
|
39
|
+
|
|
40
|
+
# MaxMind GeoLite2 DB — large binary, CC BY-SA 4.0 license terms,
|
|
41
|
+
# fetched at CI build time by Codemagic, never committed.
|
|
42
|
+
functions/GeoLite2-Country.mmdb
|
|
43
|
+
functions/GeoLite2-Country.tar.gz
|
|
44
|
+
functions/.geoip_cache/
|
|
45
|
+
|
|
46
|
+
# IDE
|
|
47
|
+
.idea/
|
|
48
|
+
.vscode/
|
|
49
|
+
*.iml
|
|
50
|
+
|
|
51
|
+
# OS
|
|
52
|
+
.DS_Store
|
|
53
|
+
Thumbs.db
|
|
54
|
+
|
|
55
|
+
# local CLI dev venv
|
|
56
|
+
cli/.venv/
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: burnedsecret-cli
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Command-line interface for the burnedsecret.com zero-knowledge secrets API
|
|
5
|
+
Author: BurnedSecret
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: burnedsecret,cli,encryption,secrets,zero-knowledge
|
|
8
|
+
Requires-Python: >=3.11
|
|
9
|
+
Requires-Dist: burnedsecret<2,>=1
|
|
10
|
+
Requires-Dist: rich>=13.8
|
|
11
|
+
Requires-Dist: tomli-w>=1.2
|
|
12
|
+
Requires-Dist: typer>=0.26
|
|
13
|
+
Provides-Extra: dev
|
|
14
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
|
|
17
|
+
# burnedsecret-cli (`bs`)
|
|
18
|
+
|
|
19
|
+
Command-line interface wrapping the [burnedsecret](https://burnedsecret.com)
|
|
20
|
+
zero-knowledge secrets REST API. All encryption happens client-side; the server
|
|
21
|
+
never sees plaintext or decryption keys — the key travels only in the URL
|
|
22
|
+
`#k=...` fragment, which the server never receives.
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pip install burnedsecret-cli
|
|
26
|
+
bs auth login
|
|
27
|
+
echo "my-secret" | bs secret create
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Install
|
|
31
|
+
|
|
32
|
+
The CLI ships through four channels:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
# 1. pip (Python 3.9+)
|
|
36
|
+
pip install burnedsecret-cli
|
|
37
|
+
|
|
38
|
+
# 2. Homebrew (macOS / Linux)
|
|
39
|
+
brew install JensrudJ/burnedsecret/bs
|
|
40
|
+
|
|
41
|
+
# 3. Scoop (Windows)
|
|
42
|
+
scoop bucket add burnedsecret https://github.com/JensrudJ/scoop-burnedsecret
|
|
43
|
+
scoop install bs
|
|
44
|
+
|
|
45
|
+
# 4. Standalone binary (no Python needed)
|
|
46
|
+
curl -fsSL https://burnedsecret.com/install/cli/install.sh | sh # POSIX
|
|
47
|
+
irm https://burnedsecret.com/install/cli/install.ps1 | iex # Windows PowerShell
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
> **macOS first run:** the standalone binary is currently shipped unsigned. On
|
|
51
|
+
> first launch macOS Gatekeeper may block it; right-click → Open, or
|
|
52
|
+
> `xattr -d com.apple.quarantine $(which bs)` to clear the quarantine flag.
|
|
53
|
+
> (Signed/notarized builds are a follow-up — see the v1 gaps below.)
|
|
54
|
+
|
|
55
|
+
## Quickstart
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
# 1. Authenticate (opens your browser to authorize this machine)
|
|
59
|
+
bs auth login
|
|
60
|
+
|
|
61
|
+
# 2. Create a secret — prints ONLY the share URL on stdout
|
|
62
|
+
url=$(echo "the launch codes" | bs secret create --ttl 1h)
|
|
63
|
+
echo "$url"
|
|
64
|
+
|
|
65
|
+
# 3. Read (and burn) it
|
|
66
|
+
bs secret read "$url"
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Because the share URL is the only thing on stdout, `url=$(... )` captures
|
|
70
|
+
exactly the URL; every prompt, table, and diagnostic goes to stderr.
|
|
71
|
+
|
|
72
|
+
## Global options
|
|
73
|
+
|
|
74
|
+
| Option | Env var | Description |
|
|
75
|
+
| ------------------- | ------------------------ | -------------------------------------------------------- |
|
|
76
|
+
| `--profile NAME` | `BURNEDSECRET_PROFILE` | Select a config profile (default: `default`). |
|
|
77
|
+
| `--json` | — | Emit the raw SDK shape as JSON on stdout. |
|
|
78
|
+
| `--verbose` / `-v` | — | Extra diagnostics on stderr. |
|
|
79
|
+
| `--version` | — | Print the CLI version and exit. |
|
|
80
|
+
|
|
81
|
+
The API key is resolved as `BURNEDSECRET_API_KEY` (env) > the stored
|
|
82
|
+
`config[profile].api_key`.
|
|
83
|
+
|
|
84
|
+
## Command reference
|
|
85
|
+
|
|
86
|
+
### `bs secret create`
|
|
87
|
+
|
|
88
|
+
Creates a burn-after-reading secret and prints **only** the share URL.
|
|
89
|
+
|
|
90
|
+
| Flag | Description |
|
|
91
|
+
| -------------------------- | ------------------------------------------------------------------------ |
|
|
92
|
+
| `--content` / `-c` | Secret text. If omitted: stdin pipe → `--edit` → hidden prompt. |
|
|
93
|
+
| `--edit` / `-e` | Compose the secret in `$EDITOR`. |
|
|
94
|
+
| `--file` / `-f` | Encrypt a file as the secret (max 10 MB). Excludes text input. |
|
|
95
|
+
| `--passphrase` / `-p` | Add a PBKDF2 passphrase layer (bare flag prompts hidden). |
|
|
96
|
+
| `--max-views` | Reads before the secret burns (1..5, default 1). |
|
|
97
|
+
| `--ttl` | Time to live: `5m` / `1h` / `1d` / `7d` (default `1d`). |
|
|
98
|
+
| `--webhook-url` | Notify this URL when the secret is read. |
|
|
99
|
+
| `--notification-email` | Email this address when the secret is read. |
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
echo "hunter2" | bs secret create --max-views 3 --ttl 7d
|
|
103
|
+
bs secret create --file ./id_rsa --ttl 1h
|
|
104
|
+
bs secret create --content "deploy key" --passphrase --ttl 1h # prompts for passphrase
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
> **Web-viewer non-interop caveat:** secrets created with `--file` or
|
|
108
|
+
> `--passphrase` are decryptable only by an SDK/CLI client. Their share URL will
|
|
109
|
+
> **NOT** open in the burnedsecret.com web viewer. Plain text secrets without a
|
|
110
|
+
> passphrase remain web-viewer compatible. (After a `--file`/`--passphrase`
|
|
111
|
+
> create the CLI also prints a one-line reminder to stderr.)
|
|
112
|
+
|
|
113
|
+
### `bs secret read`
|
|
114
|
+
|
|
115
|
+
Reads and **burns** a secret from its share URL.
|
|
116
|
+
|
|
117
|
+
| Flag | Description |
|
|
118
|
+
| --------------------- | ------------------------------------------------------------------- |
|
|
119
|
+
| `URL` (argument) | Share URL — **must** include the `#k=...` key fragment. |
|
|
120
|
+
| `--passphrase` / `-p` | Passphrase for a protected secret (bare flag prompts hidden). |
|
|
121
|
+
| `--output` / `-o` | Write file-secret bytes to PATH, or `-` to stream to stdout. |
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
bs secret read "https://burnedsecret.com/s/abc#k=..."
|
|
125
|
+
bs secret read "$url" --passphrase hunter2
|
|
126
|
+
bs secret read "$url" --output ./downloaded.bin
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### `bs request create | read`
|
|
130
|
+
|
|
131
|
+
Create a request-for-secret link, then read its fulfillment. The SDK generates an
|
|
132
|
+
RSA keypair locally; the **private key is never sent to the server and cannot be
|
|
133
|
+
regenerated** — without it the fulfillment is permanently unrecoverable. By
|
|
134
|
+
default `create` writes the key to `<config_dir>/requests/<id>.key` (absolute
|
|
135
|
+
path echoed to stderr).
|
|
136
|
+
|
|
137
|
+
| `create` flag | Description |
|
|
138
|
+
| ---------------------- | --------------------------------------------------------------------- |
|
|
139
|
+
| `--prompt` | What you are asking the recipient to send (required). |
|
|
140
|
+
| `--private-key` | Key destination: a `PATH`, `-` (stdout), or `env` (shell export). |
|
|
141
|
+
| `--fields` | Custom fields: `name:type:required;...` (type = text\|password\|textarea). |
|
|
142
|
+
| `--ttl` | `5m` / `1h` / `1d` / `7d`. |
|
|
143
|
+
| `--webhook-url` | Notify this URL when the request is fulfilled. |
|
|
144
|
+
| `--notification-email` | Email this address when the request is fulfilled. |
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
url=$(bs request create --prompt "send the AWS key" --ttl 1d)
|
|
148
|
+
# ... recipient fulfills it via the link ...
|
|
149
|
+
bs request read <request-id> --private-key ~/.config/burnedsecret/requests/<id>.key
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
`read` requires `--private-key PATH | - (stdin) | env`. Free-text fulfillments
|
|
153
|
+
print as text; custom-field fulfillments print as JSON.
|
|
154
|
+
|
|
155
|
+
### `bs key create | list | revoke | whoami`
|
|
156
|
+
|
|
157
|
+
Key management is **browser/dashboard-assisted** in v1 (see the gaps below).
|
|
158
|
+
|
|
159
|
+
| Command | Behavior |
|
|
160
|
+
| ------------- | ------------------------------------------------------------------------------ |
|
|
161
|
+
| `key create` | Alias of `bs auth login` — the PKCE exchange mints the labeled key. |
|
|
162
|
+
| `key list` | Prints your dashboard `/keys` URL (view/revoke there). |
|
|
163
|
+
| `key revoke` | Prints the dashboard `/keys` URL; the CLI never network-revokes a key. |
|
|
164
|
+
| `key whoami` | Shows the stored `key_prefix`/`hostname`/`base_url` — **no network call**. |
|
|
165
|
+
|
|
166
|
+
The CLI never mints sibling keys from a stored key: minting from a leaked key
|
|
167
|
+
would be a privilege-escalation surface.
|
|
168
|
+
|
|
169
|
+
### `bs auth login | logout | status`
|
|
170
|
+
|
|
171
|
+
| Command | Behavior |
|
|
172
|
+
| -------------- | ----------------------------------------------------------------------------- |
|
|
173
|
+
| `auth login` | Browser PKCE flow; stores a hostname-labeled key. The key is **never printed**. |
|
|
174
|
+
| `auth logout` | Forgets the local key (does **not** revoke server-side — use the dashboard). |
|
|
175
|
+
| `auth status` | Shows the profile's identity (configured?, source env/file, prefix, host). |
|
|
176
|
+
|
|
177
|
+
### `bs config list-profiles`
|
|
178
|
+
|
|
179
|
+
Lists configured profiles with their `key_prefix`/`base_url`.
|
|
180
|
+
|
|
181
|
+
## Configuration & storage
|
|
182
|
+
|
|
183
|
+
Config lives at:
|
|
184
|
+
|
|
185
|
+
- **Windows:** `%APPDATA%\burnedsecret\config.toml`
|
|
186
|
+
- **POSIX:** `${XDG_CONFIG_HOME:-~/.config}/burnedsecret/config.toml`
|
|
187
|
+
|
|
188
|
+
On POSIX the config (and any persisted request key) is written with `0600`
|
|
189
|
+
permissions so only the owner can read the stored API key. Request private keys
|
|
190
|
+
default to `<config_dir>/requests/<id>.key`.
|
|
191
|
+
|
|
192
|
+
Environment overrides: `BURNEDSECRET_API_KEY`, `BURNEDSECRET_PROFILE`.
|
|
193
|
+
|
|
194
|
+
## Exit codes
|
|
195
|
+
|
|
196
|
+
Result data is always on stdout; diagnostics/errors/prompts are on stderr.
|
|
197
|
+
|
|
198
|
+
| Code | Meaning |
|
|
199
|
+
| ---- | ------------------------- |
|
|
200
|
+
| 0 | success |
|
|
201
|
+
| 1 | generic error |
|
|
202
|
+
| 2 | invalid arguments / usage |
|
|
203
|
+
| 3 | authentication error |
|
|
204
|
+
| 4 | not found |
|
|
205
|
+
| 5 | rate limited |
|
|
206
|
+
|
|
207
|
+
The same table is shown in `bs --help`; see [`docs/exit-codes.md`](docs/exit-codes.md)
|
|
208
|
+
for the canonical reference and scripting examples.
|
|
209
|
+
|
|
210
|
+
## The browser-auth recipe (PKCE + loopback)
|
|
211
|
+
|
|
212
|
+
`bs auth login` implements a **reusable** pattern for authenticating a CLI/native
|
|
213
|
+
app through the browser without ever putting a secret in a URL or asking the user
|
|
214
|
+
to paste a token. It is a textbook implementation of **PKCE (RFC 7636)** plus the
|
|
215
|
+
**loopback-redirect native-app flow (RFC 8252)**, and the recipe transfers to any
|
|
216
|
+
CLI that needs browser auth — not just BurnedSecret:
|
|
217
|
+
|
|
218
|
+
1. **Generate ephemeral secrets locally.** A random CSRF `state` and a PKCE pair:
|
|
219
|
+
`verifier = base64url(32 random bytes)`, `challenge = base64url(sha256(verifier))`
|
|
220
|
+
(S256). The verifier never leaves the machine; only the challenge is sent.
|
|
221
|
+
2. **Bind a one-shot loopback server.** Grab a free `127.0.0.1:<port>` and start a
|
|
222
|
+
single-request HTTP server in a thread. This is the redirect target — no public
|
|
223
|
+
callback host, no firewall holes.
|
|
224
|
+
3. **Open the browser to the consent page**, carrying only public values: the
|
|
225
|
+
loopback `callback`, the CSRF `state`, the S256 `code_challenge`, and a
|
|
226
|
+
`hostname` label for the key. The URL is also echoed to stderr so a headless or
|
|
227
|
+
no-default-browser user can paste it manually.
|
|
228
|
+
4. **Capture and validate the callback.** The browser redirects to the loopback
|
|
229
|
+
server with `?code=&state=`. The handler **constant-comparison-checks the
|
|
230
|
+
returned `state`** against the one it generated — a mismatch is a hard abort
|
|
231
|
+
that captures no code, so no exchange ever happens. The callback carries only
|
|
232
|
+
`code` + `state`; the API key is **never** in any URL.
|
|
233
|
+
5. **Exchange the single-use code for the key.** POST `{code, code_verifier}` to
|
|
234
|
+
the exchange endpoint. The server recomputes `sha256(verifier)` and
|
|
235
|
+
constant-time compares it to the stored challenge, proving the same client that
|
|
236
|
+
started the flow is finishing it. The **API key is returned only in the
|
|
237
|
+
response body**, then stored chmod-600 and never printed.
|
|
238
|
+
|
|
239
|
+
Security rationale, in one line each:
|
|
240
|
+
|
|
241
|
+
- **CSRF `state`** — prevents a forged callback from injecting an attacker's code.
|
|
242
|
+
- **PKCE `verifier`/`challenge`** — binds the code to this client; a stolen code
|
|
243
|
+
is useless without the verifier, which never traveled over the network.
|
|
244
|
+
- **Key never in the callback URL** — URLs leak (history, logs, referrers); the
|
|
245
|
+
key lives only in the exchange response body and on-disk chmod-600.
|
|
246
|
+
- **Single-use, short-TTL code** — narrows the window for replay.
|
|
247
|
+
|
|
248
|
+
## Documented v1 gaps & follow-ups
|
|
249
|
+
|
|
250
|
+
These are deliberate v1 scope decisions, recorded here rather than silently
|
|
251
|
+
dropped:
|
|
252
|
+
|
|
253
|
+
- **Access-restriction flags are NOT in v1.** `--access-cidrs`,
|
|
254
|
+
`--access-countries`, and `--require-captcha` are not available — the shipped
|
|
255
|
+
REST `create_secret` shape has no such parameters; adding them needs an SDK
|
|
256
|
+
extension (D-06 deviation).
|
|
257
|
+
- **OS keyring storage is deferred to v2.** Keys are stored in a chmod-600
|
|
258
|
+
config file, not the OS keychain/credential manager.
|
|
259
|
+
- **`bs secret read --copy` (clipboard) is deferred.** Pipe to your platform's
|
|
260
|
+
clipboard tool (`pbcopy` / `clip` / `wl-copy`) in the meantime.
|
|
261
|
+
- **`bs secret create --bulk` is deferred.** Create secrets one at a time (or
|
|
262
|
+
loop in your shell) for now.
|
|
263
|
+
- **Polished tab-completion install is deferred.** Typer's completion machinery
|
|
264
|
+
is wired, but a one-command shell-completion installer is a follow-up.
|
|
265
|
+
- **`bs key list` / `bs key revoke` are browser/dashboard-assisted in v1.** They
|
|
266
|
+
print your dashboard `/keys` URL rather than calling an API-key-authenticated
|
|
267
|
+
keys endpoint (which would be a privilege-escalation surface). A
|
|
268
|
+
token-cache-backed headless listing is a v2 option.
|
|
269
|
+
- **Standalone macOS binaries are currently unsigned** (see the install note);
|
|
270
|
+
signing/notarization is a follow-up.
|
|
271
|
+
- **Chocolatey** is a deliberate fast-follow; Scoop covers the Windows
|
|
272
|
+
package-manager path for v1.
|
|
273
|
+
|
|
274
|
+
### CI usage
|
|
275
|
+
|
|
276
|
+
Automation does **not** run the browser flow. CI uses a pre-minted
|
|
277
|
+
`BURNEDSECRET_API_KEY` env var; none of the `bs auth`/`bs key` commands are
|
|
278
|
+
needed in a pipeline.
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
# burnedsecret-cli (`bs`)
|
|
2
|
+
|
|
3
|
+
Command-line interface wrapping the [burnedsecret](https://burnedsecret.com)
|
|
4
|
+
zero-knowledge secrets REST API. All encryption happens client-side; the server
|
|
5
|
+
never sees plaintext or decryption keys — the key travels only in the URL
|
|
6
|
+
`#k=...` fragment, which the server never receives.
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
pip install burnedsecret-cli
|
|
10
|
+
bs auth login
|
|
11
|
+
echo "my-secret" | bs secret create
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
The CLI ships through four channels:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
# 1. pip (Python 3.9+)
|
|
20
|
+
pip install burnedsecret-cli
|
|
21
|
+
|
|
22
|
+
# 2. Homebrew (macOS / Linux)
|
|
23
|
+
brew install JensrudJ/burnedsecret/bs
|
|
24
|
+
|
|
25
|
+
# 3. Scoop (Windows)
|
|
26
|
+
scoop bucket add burnedsecret https://github.com/JensrudJ/scoop-burnedsecret
|
|
27
|
+
scoop install bs
|
|
28
|
+
|
|
29
|
+
# 4. Standalone binary (no Python needed)
|
|
30
|
+
curl -fsSL https://burnedsecret.com/install/cli/install.sh | sh # POSIX
|
|
31
|
+
irm https://burnedsecret.com/install/cli/install.ps1 | iex # Windows PowerShell
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
> **macOS first run:** the standalone binary is currently shipped unsigned. On
|
|
35
|
+
> first launch macOS Gatekeeper may block it; right-click → Open, or
|
|
36
|
+
> `xattr -d com.apple.quarantine $(which bs)` to clear the quarantine flag.
|
|
37
|
+
> (Signed/notarized builds are a follow-up — see the v1 gaps below.)
|
|
38
|
+
|
|
39
|
+
## Quickstart
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# 1. Authenticate (opens your browser to authorize this machine)
|
|
43
|
+
bs auth login
|
|
44
|
+
|
|
45
|
+
# 2. Create a secret — prints ONLY the share URL on stdout
|
|
46
|
+
url=$(echo "the launch codes" | bs secret create --ttl 1h)
|
|
47
|
+
echo "$url"
|
|
48
|
+
|
|
49
|
+
# 3. Read (and burn) it
|
|
50
|
+
bs secret read "$url"
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Because the share URL is the only thing on stdout, `url=$(... )` captures
|
|
54
|
+
exactly the URL; every prompt, table, and diagnostic goes to stderr.
|
|
55
|
+
|
|
56
|
+
## Global options
|
|
57
|
+
|
|
58
|
+
| Option | Env var | Description |
|
|
59
|
+
| ------------------- | ------------------------ | -------------------------------------------------------- |
|
|
60
|
+
| `--profile NAME` | `BURNEDSECRET_PROFILE` | Select a config profile (default: `default`). |
|
|
61
|
+
| `--json` | — | Emit the raw SDK shape as JSON on stdout. |
|
|
62
|
+
| `--verbose` / `-v` | — | Extra diagnostics on stderr. |
|
|
63
|
+
| `--version` | — | Print the CLI version and exit. |
|
|
64
|
+
|
|
65
|
+
The API key is resolved as `BURNEDSECRET_API_KEY` (env) > the stored
|
|
66
|
+
`config[profile].api_key`.
|
|
67
|
+
|
|
68
|
+
## Command reference
|
|
69
|
+
|
|
70
|
+
### `bs secret create`
|
|
71
|
+
|
|
72
|
+
Creates a burn-after-reading secret and prints **only** the share URL.
|
|
73
|
+
|
|
74
|
+
| Flag | Description |
|
|
75
|
+
| -------------------------- | ------------------------------------------------------------------------ |
|
|
76
|
+
| `--content` / `-c` | Secret text. If omitted: stdin pipe → `--edit` → hidden prompt. |
|
|
77
|
+
| `--edit` / `-e` | Compose the secret in `$EDITOR`. |
|
|
78
|
+
| `--file` / `-f` | Encrypt a file as the secret (max 10 MB). Excludes text input. |
|
|
79
|
+
| `--passphrase` / `-p` | Add a PBKDF2 passphrase layer (bare flag prompts hidden). |
|
|
80
|
+
| `--max-views` | Reads before the secret burns (1..5, default 1). |
|
|
81
|
+
| `--ttl` | Time to live: `5m` / `1h` / `1d` / `7d` (default `1d`). |
|
|
82
|
+
| `--webhook-url` | Notify this URL when the secret is read. |
|
|
83
|
+
| `--notification-email` | Email this address when the secret is read. |
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
echo "hunter2" | bs secret create --max-views 3 --ttl 7d
|
|
87
|
+
bs secret create --file ./id_rsa --ttl 1h
|
|
88
|
+
bs secret create --content "deploy key" --passphrase --ttl 1h # prompts for passphrase
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
> **Web-viewer non-interop caveat:** secrets created with `--file` or
|
|
92
|
+
> `--passphrase` are decryptable only by an SDK/CLI client. Their share URL will
|
|
93
|
+
> **NOT** open in the burnedsecret.com web viewer. Plain text secrets without a
|
|
94
|
+
> passphrase remain web-viewer compatible. (After a `--file`/`--passphrase`
|
|
95
|
+
> create the CLI also prints a one-line reminder to stderr.)
|
|
96
|
+
|
|
97
|
+
### `bs secret read`
|
|
98
|
+
|
|
99
|
+
Reads and **burns** a secret from its share URL.
|
|
100
|
+
|
|
101
|
+
| Flag | Description |
|
|
102
|
+
| --------------------- | ------------------------------------------------------------------- |
|
|
103
|
+
| `URL` (argument) | Share URL — **must** include the `#k=...` key fragment. |
|
|
104
|
+
| `--passphrase` / `-p` | Passphrase for a protected secret (bare flag prompts hidden). |
|
|
105
|
+
| `--output` / `-o` | Write file-secret bytes to PATH, or `-` to stream to stdout. |
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
bs secret read "https://burnedsecret.com/s/abc#k=..."
|
|
109
|
+
bs secret read "$url" --passphrase hunter2
|
|
110
|
+
bs secret read "$url" --output ./downloaded.bin
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### `bs request create | read`
|
|
114
|
+
|
|
115
|
+
Create a request-for-secret link, then read its fulfillment. The SDK generates an
|
|
116
|
+
RSA keypair locally; the **private key is never sent to the server and cannot be
|
|
117
|
+
regenerated** — without it the fulfillment is permanently unrecoverable. By
|
|
118
|
+
default `create` writes the key to `<config_dir>/requests/<id>.key` (absolute
|
|
119
|
+
path echoed to stderr).
|
|
120
|
+
|
|
121
|
+
| `create` flag | Description |
|
|
122
|
+
| ---------------------- | --------------------------------------------------------------------- |
|
|
123
|
+
| `--prompt` | What you are asking the recipient to send (required). |
|
|
124
|
+
| `--private-key` | Key destination: a `PATH`, `-` (stdout), or `env` (shell export). |
|
|
125
|
+
| `--fields` | Custom fields: `name:type:required;...` (type = text\|password\|textarea). |
|
|
126
|
+
| `--ttl` | `5m` / `1h` / `1d` / `7d`. |
|
|
127
|
+
| `--webhook-url` | Notify this URL when the request is fulfilled. |
|
|
128
|
+
| `--notification-email` | Email this address when the request is fulfilled. |
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
url=$(bs request create --prompt "send the AWS key" --ttl 1d)
|
|
132
|
+
# ... recipient fulfills it via the link ...
|
|
133
|
+
bs request read <request-id> --private-key ~/.config/burnedsecret/requests/<id>.key
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
`read` requires `--private-key PATH | - (stdin) | env`. Free-text fulfillments
|
|
137
|
+
print as text; custom-field fulfillments print as JSON.
|
|
138
|
+
|
|
139
|
+
### `bs key create | list | revoke | whoami`
|
|
140
|
+
|
|
141
|
+
Key management is **browser/dashboard-assisted** in v1 (see the gaps below).
|
|
142
|
+
|
|
143
|
+
| Command | Behavior |
|
|
144
|
+
| ------------- | ------------------------------------------------------------------------------ |
|
|
145
|
+
| `key create` | Alias of `bs auth login` — the PKCE exchange mints the labeled key. |
|
|
146
|
+
| `key list` | Prints your dashboard `/keys` URL (view/revoke there). |
|
|
147
|
+
| `key revoke` | Prints the dashboard `/keys` URL; the CLI never network-revokes a key. |
|
|
148
|
+
| `key whoami` | Shows the stored `key_prefix`/`hostname`/`base_url` — **no network call**. |
|
|
149
|
+
|
|
150
|
+
The CLI never mints sibling keys from a stored key: minting from a leaked key
|
|
151
|
+
would be a privilege-escalation surface.
|
|
152
|
+
|
|
153
|
+
### `bs auth login | logout | status`
|
|
154
|
+
|
|
155
|
+
| Command | Behavior |
|
|
156
|
+
| -------------- | ----------------------------------------------------------------------------- |
|
|
157
|
+
| `auth login` | Browser PKCE flow; stores a hostname-labeled key. The key is **never printed**. |
|
|
158
|
+
| `auth logout` | Forgets the local key (does **not** revoke server-side — use the dashboard). |
|
|
159
|
+
| `auth status` | Shows the profile's identity (configured?, source env/file, prefix, host). |
|
|
160
|
+
|
|
161
|
+
### `bs config list-profiles`
|
|
162
|
+
|
|
163
|
+
Lists configured profiles with their `key_prefix`/`base_url`.
|
|
164
|
+
|
|
165
|
+
## Configuration & storage
|
|
166
|
+
|
|
167
|
+
Config lives at:
|
|
168
|
+
|
|
169
|
+
- **Windows:** `%APPDATA%\burnedsecret\config.toml`
|
|
170
|
+
- **POSIX:** `${XDG_CONFIG_HOME:-~/.config}/burnedsecret/config.toml`
|
|
171
|
+
|
|
172
|
+
On POSIX the config (and any persisted request key) is written with `0600`
|
|
173
|
+
permissions so only the owner can read the stored API key. Request private keys
|
|
174
|
+
default to `<config_dir>/requests/<id>.key`.
|
|
175
|
+
|
|
176
|
+
Environment overrides: `BURNEDSECRET_API_KEY`, `BURNEDSECRET_PROFILE`.
|
|
177
|
+
|
|
178
|
+
## Exit codes
|
|
179
|
+
|
|
180
|
+
Result data is always on stdout; diagnostics/errors/prompts are on stderr.
|
|
181
|
+
|
|
182
|
+
| Code | Meaning |
|
|
183
|
+
| ---- | ------------------------- |
|
|
184
|
+
| 0 | success |
|
|
185
|
+
| 1 | generic error |
|
|
186
|
+
| 2 | invalid arguments / usage |
|
|
187
|
+
| 3 | authentication error |
|
|
188
|
+
| 4 | not found |
|
|
189
|
+
| 5 | rate limited |
|
|
190
|
+
|
|
191
|
+
The same table is shown in `bs --help`; see [`docs/exit-codes.md`](docs/exit-codes.md)
|
|
192
|
+
for the canonical reference and scripting examples.
|
|
193
|
+
|
|
194
|
+
## The browser-auth recipe (PKCE + loopback)
|
|
195
|
+
|
|
196
|
+
`bs auth login` implements a **reusable** pattern for authenticating a CLI/native
|
|
197
|
+
app through the browser without ever putting a secret in a URL or asking the user
|
|
198
|
+
to paste a token. It is a textbook implementation of **PKCE (RFC 7636)** plus the
|
|
199
|
+
**loopback-redirect native-app flow (RFC 8252)**, and the recipe transfers to any
|
|
200
|
+
CLI that needs browser auth — not just BurnedSecret:
|
|
201
|
+
|
|
202
|
+
1. **Generate ephemeral secrets locally.** A random CSRF `state` and a PKCE pair:
|
|
203
|
+
`verifier = base64url(32 random bytes)`, `challenge = base64url(sha256(verifier))`
|
|
204
|
+
(S256). The verifier never leaves the machine; only the challenge is sent.
|
|
205
|
+
2. **Bind a one-shot loopback server.** Grab a free `127.0.0.1:<port>` and start a
|
|
206
|
+
single-request HTTP server in a thread. This is the redirect target — no public
|
|
207
|
+
callback host, no firewall holes.
|
|
208
|
+
3. **Open the browser to the consent page**, carrying only public values: the
|
|
209
|
+
loopback `callback`, the CSRF `state`, the S256 `code_challenge`, and a
|
|
210
|
+
`hostname` label for the key. The URL is also echoed to stderr so a headless or
|
|
211
|
+
no-default-browser user can paste it manually.
|
|
212
|
+
4. **Capture and validate the callback.** The browser redirects to the loopback
|
|
213
|
+
server with `?code=&state=`. The handler **constant-comparison-checks the
|
|
214
|
+
returned `state`** against the one it generated — a mismatch is a hard abort
|
|
215
|
+
that captures no code, so no exchange ever happens. The callback carries only
|
|
216
|
+
`code` + `state`; the API key is **never** in any URL.
|
|
217
|
+
5. **Exchange the single-use code for the key.** POST `{code, code_verifier}` to
|
|
218
|
+
the exchange endpoint. The server recomputes `sha256(verifier)` and
|
|
219
|
+
constant-time compares it to the stored challenge, proving the same client that
|
|
220
|
+
started the flow is finishing it. The **API key is returned only in the
|
|
221
|
+
response body**, then stored chmod-600 and never printed.
|
|
222
|
+
|
|
223
|
+
Security rationale, in one line each:
|
|
224
|
+
|
|
225
|
+
- **CSRF `state`** — prevents a forged callback from injecting an attacker's code.
|
|
226
|
+
- **PKCE `verifier`/`challenge`** — binds the code to this client; a stolen code
|
|
227
|
+
is useless without the verifier, which never traveled over the network.
|
|
228
|
+
- **Key never in the callback URL** — URLs leak (history, logs, referrers); the
|
|
229
|
+
key lives only in the exchange response body and on-disk chmod-600.
|
|
230
|
+
- **Single-use, short-TTL code** — narrows the window for replay.
|
|
231
|
+
|
|
232
|
+
## Documented v1 gaps & follow-ups
|
|
233
|
+
|
|
234
|
+
These are deliberate v1 scope decisions, recorded here rather than silently
|
|
235
|
+
dropped:
|
|
236
|
+
|
|
237
|
+
- **Access-restriction flags are NOT in v1.** `--access-cidrs`,
|
|
238
|
+
`--access-countries`, and `--require-captcha` are not available — the shipped
|
|
239
|
+
REST `create_secret` shape has no such parameters; adding them needs an SDK
|
|
240
|
+
extension (D-06 deviation).
|
|
241
|
+
- **OS keyring storage is deferred to v2.** Keys are stored in a chmod-600
|
|
242
|
+
config file, not the OS keychain/credential manager.
|
|
243
|
+
- **`bs secret read --copy` (clipboard) is deferred.** Pipe to your platform's
|
|
244
|
+
clipboard tool (`pbcopy` / `clip` / `wl-copy`) in the meantime.
|
|
245
|
+
- **`bs secret create --bulk` is deferred.** Create secrets one at a time (or
|
|
246
|
+
loop in your shell) for now.
|
|
247
|
+
- **Polished tab-completion install is deferred.** Typer's completion machinery
|
|
248
|
+
is wired, but a one-command shell-completion installer is a follow-up.
|
|
249
|
+
- **`bs key list` / `bs key revoke` are browser/dashboard-assisted in v1.** They
|
|
250
|
+
print your dashboard `/keys` URL rather than calling an API-key-authenticated
|
|
251
|
+
keys endpoint (which would be a privilege-escalation surface). A
|
|
252
|
+
token-cache-backed headless listing is a v2 option.
|
|
253
|
+
- **Standalone macOS binaries are currently unsigned** (see the install note);
|
|
254
|
+
signing/notarization is a follow-up.
|
|
255
|
+
- **Chocolatey** is a deliberate fast-follow; Scoop covers the Windows
|
|
256
|
+
package-manager path for v1.
|
|
257
|
+
|
|
258
|
+
### CI usage
|
|
259
|
+
|
|
260
|
+
Automation does **not** run the browser flow. CI uses a pre-minted
|
|
261
|
+
`BURNEDSECRET_API_KEY` env var; none of the `bs auth`/`bs key` commands are
|
|
262
|
+
needed in a pipeline.
|