Metadata-Version: 2.5
Name: burnedsecret-cli
Version: 1.0.3
Summary: Command-line interface for the burnedsecret.com zero-knowledge secrets API
Author: BurnedSecret
License: MIT
Keywords: burnedsecret,cli,encryption,secrets,zero-knowledge
Requires-Python: >=3.11
Requires-Dist: burnedsecret<2,>=1
Requires-Dist: rich>=13.8
Requires-Dist: tomli-w>=1.2
Requires-Dist: typer>=0.26
Provides-Extra: dev
Requires-Dist: pytest; extra == 'dev'
Description-Content-Type: text/markdown

# burnedsecret-cli (`bs`)

Command-line interface wrapping the [burnedsecret](https://burnedsecret.com)
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.

```bash
pip install burnedsecret-cli
bs auth login
echo "my-secret" | bs secret create
```

## Install

The CLI ships through five channels:

```bash
# 1. pip (Python 3.11+)
pip install burnedsecret-cli

# 2. Homebrew (macOS Apple Silicon only; Linux uses install.sh or pip)
brew install JensrudJ/burnedsecret/bs

# 3. Scoop (Windows)
scoop bucket add burnedsecret https://github.com/JensrudJ/scoop-burnedsecret
scoop install bs

# 4. Chocolatey (Windows)
choco install burnedsecret-cli

# 5. 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
```

### Supported platforms

| Platform              | Channels                                   |
| --------------------- | ------------------------------------------ |
| Linux x86_64          | binary (`install.sh`), pip                 |
| Linux arm64           | binary (`install.sh`), pip                 |
| macOS Apple Silicon   | binary (`install.sh`), Homebrew, pip       |
| macOS Intel (x86_64)  | pip only                                   |
| Windows x86_64        | Scoop, Chocolatey, zip (`install.ps1`), pip |
| Windows arm64         | pip                                        |

The Homebrew formula is macOS Apple Silicon only (it has no `on_linux`
block); Linux users install the standalone binary via `install.sh` or use pip.
No Intel macOS binary is published, by decision, and none is planned: the pip
install is the identical tool. `brew install` and `install.sh` on an Intel Mac
say so and point at `pip install burnedsecret-cli`.

> **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

```bash
# 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.                             |

```bash
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 `--file` or
> `--passphrase` are 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`/`--passphrase`
> create 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.        |

```bash
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.                    |

```bash
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`](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:

1. **Generate ephemeral secrets locally.** A random CSRF `state` and a PKCE pair:
   `verifier = base64url(32 random bytes)`, `challenge = base64url(sha256(verifier))`
   (S256). The verifier never leaves the machine; only the challenge is sent.
2. **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.
3. **Open the browser to the consent page**, carrying only public values: the
   loopback `callback`, the CSRF `state`, the S256 `code_challenge`, and a
   `hostname` label for the key. The URL is also echoed to stderr so a headless or
   no-default-browser user can paste it manually.
4. **Capture and validate the callback.** The browser redirects to the loopback
   server with `?code=&state=`. The handler **constant-comparison-checks the
   returned `state`** against the one it generated — a mismatch is a hard abort
   that captures no code, so no exchange ever happens. The callback carries only
   `code` + `state`; the API key is **never** in any URL.
5. **Exchange the single-use code for the key.** POST `{code, code_verifier}` to
   the exchange endpoint. The server recomputes `sha256(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-captcha` are not available — the shipped
  REST `create_secret` shape 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 --bulk` is 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 revoke` are browser/dashboard-assisted in v1.** They
  print your dashboard `/keys` URL 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.
- **No Intel macOS binary (decided 2026-09-17):** use pip; the wheel is the
  same code. Apple Silicon gets the standalone binary and the Homebrew formula.

### 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.
