burnedsecret-cli (bs)
Command-line interface wrapping the burnedsecret
zero-knowledge secrets REST API. All encryption happens client-side; the server
never sees plaintext or decryption keys — the key travels only in the URL
#k=... fragment, which the server never receives.
pip install burnedsecret-cli
bs auth login
echo "my-secret" | bs secret create
Install
The CLI ships through four channels:
# 1. pip (Python 3.9+)
pip install burnedsecret-cli
# 2. Homebrew (macOS / Linux)
brew install JensrudJ/burnedsecret/bs
# 3. Scoop (Windows)
scoop bucket add burnedsecret https://github.com/JensrudJ/scoop-burnedsecret
scoop install bs
# 4. Standalone binary (no Python needed)
curl -fsSL https://burnedsecret.com/install/cli/install.sh | sh # POSIX
irm https://burnedsecret.com/install/cli/install.ps1 | iex # Windows PowerShell
macOS first run: the standalone binary is currently shipped unsigned. On first launch macOS Gatekeeper may block it; right-click → Open, or
xattr -d com.apple.quarantine $(which bs)to clear the quarantine flag. (Signed/notarized builds are a follow-up — see the v1 gaps below.)
Quickstart
# 1. Authenticate (opens your browser to authorize this machine)
bs auth login
# 2. Create a secret — prints ONLY the share URL on stdout
url=$(echo "the launch codes" | bs secret create --ttl 1h)
echo "$url"
# 3. Read (and burn) it
bs secret read "$url"
Because the share URL is the only thing on stdout, url=$(... ) captures
exactly the URL; every prompt, table, and diagnostic goes to stderr.
Global options
| Option | Env var | Description |
|---|---|---|
--profile NAME |
BURNEDSECRET_PROFILE |
Select a config profile (default: default). |
--json |
— | Emit the raw SDK shape as JSON on stdout. |
--verbose / -v |
— | Extra diagnostics on stderr. |
--version |
— | Print the CLI version and exit. |
The API key is resolved as BURNEDSECRET_API_KEY (env) > the stored
config[profile].api_key.
Command reference
bs secret create
Creates a burn-after-reading secret and prints only the share URL.
| Flag | Description |
|---|---|
--content / -c |
Secret text. If omitted: stdin pipe → --edit → hidden prompt. |
--edit / -e |
Compose the secret in $EDITOR. |
--file / -f |
Encrypt a file as the secret (max 10 MB). Excludes text input. |
--passphrase / -p |
Add a PBKDF2 passphrase layer (bare flag prompts hidden). |
--max-views |
Reads before the secret burns (1..5, default 1). |
--ttl |
Time to live: 5m / 1h / 1d / 7d (default 1d). |
--webhook-url |
Notify this URL when the secret is read. |
--notification-email |
Email this address when the secret is read. |
echo "hunter2" | bs secret create --max-views 3 --ttl 7d
bs secret create --file ./id_rsa --ttl 1h
bs secret create --content "deploy key" --passphrase --ttl 1h # prompts for passphrase
Web-viewer non-interop caveat: secrets created with
--fileor--passphraseare decryptable only by an SDK/CLI client. Their share URL will NOT open in the burnedsecret.com web viewer. Plain text secrets without a passphrase remain web-viewer compatible. (After a--file/--passphrasecreate the CLI also prints a one-line reminder to stderr.)
bs secret read
Reads and burns a secret from its share URL.
| Flag | Description |
|---|---|
URL (argument) |
Share URL — must include the #k=... key fragment. |
--passphrase / -p |
Passphrase for a protected secret (bare flag prompts hidden). |
--output / -o |
Write file-secret bytes to PATH, or - to stream to stdout. |
bs secret read "https://burnedsecret.com/s/abc#k=..."
bs secret read "$url" --passphrase hunter2
bs secret read "$url" --output ./downloaded.bin
bs request create | read
Create a request-for-secret link, then read its fulfillment. The SDK generates an
RSA keypair locally; the private key is never sent to the server and cannot be
regenerated — without it the fulfillment is permanently unrecoverable. By
default create writes the key to <config_dir>/requests/<id>.key (absolute
path echoed to stderr).
create flag |
Description |
|---|---|
--prompt |
What you are asking the recipient to send (required). |
--private-key |
Key destination: a PATH, - (stdout), or env (shell export). |
--fields |
Custom fields: name:type:required;... (type = text|password|textarea). |
--ttl |
5m / 1h / 1d / 7d. |
--webhook-url |
Notify this URL when the request is fulfilled. |
--notification-email |
Email this address when the request is fulfilled. |
url=$(bs request create --prompt "send the AWS key" --ttl 1d)
# ... recipient fulfills it via the link ...
bs request read <request-id> --private-key ~/.config/burnedsecret/requests/<id>.key
read requires --private-key PATH | - (stdin) | env. Free-text fulfillments
print as text; custom-field fulfillments print as JSON.
bs key create | list | revoke | whoami
Key management is browser/dashboard-assisted in v1 (see the gaps below).
| Command | Behavior |
|---|---|
key create |
Alias of bs auth login — the PKCE exchange mints the labeled key. |
key list |
Prints your dashboard /keys URL (view/revoke there). |
key revoke |
Prints the dashboard /keys URL; the CLI never network-revokes a key. |
key whoami |
Shows the stored key_prefix/hostname/base_url — no network call. |
The CLI never mints sibling keys from a stored key: minting from a leaked key would be a privilege-escalation surface.
bs auth login | logout | status
| Command | Behavior |
|---|---|
auth login |
Browser PKCE flow; stores a hostname-labeled key. The key is never printed. |
auth logout |
Forgets the local key (does not revoke server-side — use the dashboard). |
auth status |
Shows the profile's identity (configured?, source env/file, prefix, host). |
bs config list-profiles
Lists configured profiles with their key_prefix/base_url.
Configuration & storage
Config lives at:
- Windows:
%APPDATA%\burnedsecret\config.toml - POSIX:
${XDG_CONFIG_HOME:-~/.config}/burnedsecret/config.toml
On POSIX the config (and any persisted request key) is written with 0600
permissions so only the owner can read the stored API key. Request private keys
default to <config_dir>/requests/<id>.key.
Environment overrides: BURNEDSECRET_API_KEY, BURNEDSECRET_PROFILE.
Exit codes
Result data is always on stdout; diagnostics/errors/prompts are on stderr.
| Code | Meaning |
|---|---|
| 0 | success |
| 1 | generic error |
| 2 | invalid arguments / usage |
| 3 | authentication error |
| 4 | not found |
| 5 | rate limited |
The same table is shown in bs --help; see docs/exit-codes.md
for the canonical reference and scripting examples.
The browser-auth recipe (PKCE + loopback)
bs auth login implements a reusable pattern for authenticating a CLI/native
app through the browser without ever putting a secret in a URL or asking the user
to paste a token. It is a textbook implementation of PKCE (RFC 7636) plus the
loopback-redirect native-app flow (RFC 8252), and the recipe transfers to any
CLI that needs browser auth — not just BurnedSecret:
- Generate ephemeral secrets locally. A random CSRF
stateand a PKCE pair:verifier = base64url(32 random bytes),challenge = base64url(sha256(verifier))(S256). The verifier never leaves the machine; only the challenge is sent. - Bind a one-shot loopback server. Grab a free
127.0.0.1:<port>and start a single-request HTTP server in a thread. This is the redirect target — no public callback host, no firewall holes. - Open the browser to the consent page, carrying only public values: the
loopback
callback, the CSRFstate, the S256code_challenge, and ahostnamelabel for the key. The URL is also echoed to stderr so a headless or no-default-browser user can paste it manually. - Capture and validate the callback. The browser redirects to the loopback
server with
?code=&state=. The handler constant-comparison-checks the returnedstateagainst the one it generated — a mismatch is a hard abort that captures no code, so no exchange ever happens. The callback carries onlycode+state; the API key is never in any URL. - Exchange the single-use code for the key. POST
{code, code_verifier}to the exchange endpoint. The server recomputessha256(verifier)and constant-time compares it to the stored challenge, proving the same client that started the flow is finishing it. The API key is returned only in the response body, then stored chmod-600 and never printed.
Security rationale, in one line each:
- CSRF
state— prevents a forged callback from injecting an attacker's code. - PKCE
verifier/challenge— binds the code to this client; a stolen code is useless without the verifier, which never traveled over the network. - Key never in the callback URL — URLs leak (history, logs, referrers); the key lives only in the exchange response body and on-disk chmod-600.
- Single-use, short-TTL code — narrows the window for replay.
Documented v1 gaps & follow-ups
These are deliberate v1 scope decisions, recorded here rather than silently dropped:
- Access-restriction flags are NOT in v1.
--access-cidrs,--access-countries, and--require-captchaare not available — the shipped RESTcreate_secretshape has no such parameters; adding them needs an SDK extension (D-06 deviation). - OS keyring storage is deferred to v2. Keys are stored in a chmod-600 config file, not the OS keychain/credential manager.
bs secret read --copy(clipboard) is deferred. Pipe to your platform's clipboard tool (pbcopy/clip/wl-copy) in the meantime.bs secret create --bulkis deferred. Create secrets one at a time (or loop in your shell) for now.- Polished tab-completion install is deferred. Typer's completion machinery is wired, but a one-command shell-completion installer is a follow-up.
bs key list/bs key revokeare browser/dashboard-assisted in v1. They print your dashboard/keysURL rather than calling an API-key-authenticated keys endpoint (which would be a privilege-escalation surface). A token-cache-backed headless listing is a v2 option.- Standalone macOS binaries are currently unsigned (see the install note); signing/notarization is a follow-up.
- Chocolatey is a deliberate fast-follow; Scoop covers the Windows package-manager path for v1.
CI usage
Automation does not run the browser flow. CI uses a pre-minted
BURNEDSECRET_API_KEY env var; none of the bs auth/bs key commands are
needed in a pipeline.
Metadata
Release files for burnedsecret-cli 1.0.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| burnedsecret_cli-1.0.2.tar.gz | 45.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| burnedsecret_cli-1.0.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 81.2 kB
Release files / burnedsecret_cli-1.0.2.tar.gz
| Download URL | burnedsecret_cli-1.0.2.tar.gz |
|---|---|
| Size | 45.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
ed1d789f432fc0f0e1c9bd6d115c2a6ce7f57013eae97e0d0477ac46e3e8cfeb
|
|
BLAKE2b-256 checksum How to use checksums |
1864767653f926619e94dfd3728367c96d224993d4bcc8a7f92483ac67e3a175
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.5
|
Release files / burnedsecret_cli-1.0.2-py3-none-any.whl
| Download URL | burnedsecret_cli-1.0.2-py3-none-any.whl |
|---|---|
| Size | 35.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
8c460fa1fd2a398b36779190c6fdb51baeca3b3db205537820d659b072d11d0c
|
|
BLAKE2b-256 checksum How to use checksums |
869e38e30ea3f946839c343bfb3a678581d6e7465059e35fc6e4c17de941d484
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.5
|