git:20260223.7ca4ae6 to v1.0.0

133 added, 176 removed. Audit C to A.

- # zKettle — Self-Destructing Secret Sharing
-
- zKettle is a zero-knowledge, self-hosted secret sharing tool. Secrets are encrypted client-side with AES-256-GCM, the server stores only ciphertext, and the decryption key lives in the URL fragment — never sent to the server. Secrets auto-delete after a configurable number of views or time limit. zKettle is AI-native: agents can create, read, revoke, and audit secrets programmatically.
-
- ## When to Use
-
- - **Credential sharing** — share passwords, API keys, or tokens with teammates via a one-time link
- - **API key rotation** — create a new secret, share it, revoke the old one
- - **Temporary access** — grant short-lived access with a low view count and short TTL
- - **Secure dead drops** — leave a secret for someone to pick up once, then it vanishes
-
- ## Installation
-
- ### Go install (requires Go 1.25+)
-
- ```bash
- go install github.com/benderterminal/zkettle@latest
- ```
-
- This installs to `$GOPATH/bin` (typically `~/go/bin`). Make sure it's in your PATH: `export PATH="$HOME/go/bin:$PATH"`
-
- ### Binary download
-
- ```bash
- curl -fsSL https://github.com/benderterminal/zkettle/releases/latest/download/zkettle-$(uname -s | tr A-Z a-z)-$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/') -o /usr/local/bin/zkettle && chmod +x /usr/local/bin/zkettle
- ```
-
- ### From source
-
- ```bash
- git clone https://github.com/benderterminal/zkettle.git && cd zkettle && make install
- ```
-
- ## MCP Setup
-
- **Before configuring, ask the user:** Do you want zKettle to generate **public URLs** (recommended — shareable with anyone on the internet) or **local-only URLs** (only accessible on your machine/network)? Public mode uses a free Cloudflare Quick Tunnel — no account or DNS setup required. It works for both local and remote sharing. Only use local mode if sharing is strictly limited to your local network.
-
- > **Important:** Use the **absolute path** to the `zkettle` binary in your MCP config. Many MCP clients (including Claude Code) do not inherit your shell's PATH, so a bare `zkettle` command will silently fail to start.
-
- Add to your MCP client configuration (Claude Code, Claude Desktop, or any MCP-compatible agent):
-
- **Public URLs (recommended)** — secrets shareable with anyone via Cloudflare Quick Tunnel:
-
- ```json
- {
- "mcpServers": {
- "zkettle": {
- "command": "/absolute/path/to/zkettle",
- "args": ["mcp", "--port", "3001", "--tunnel"]
- }
- }
- }
- ```
-
- **Local only** — use only if sharing is strictly limited to your local network:
-
- ```json
- {
- "mcpServers": {
- "zkettle": {
- "command": "/absolute/path/to/zkettle",
- "args": ["mcp", "--port", "3001"]
- }
- }
- }
- ```
-
- **Claude Code shortcut** — use `claude mcp add` to configure with the correct scope and path automatically:
-
- ```bash
- # Public URLs (recommended)
- claude mcp add -s user zkettle -- /absolute/path/to/zkettle mcp --port 3001 --tunnel
-
- # Local only
- claude mcp add -s user zkettle -- /absolute/path/to/zkettle mcp --port 3001
- ```
-
- The MCP server starts an HTTP backend on the specified port and communicates with the agent over stdio. All encryption and decryption happens locally — the server never sees plaintext.
-
- ## CLI Usage
-
- ```bash
- # Start the server locally
- zkettle serve --port 3000
-
- # Start with a public URL via Cloudflare Quick Tunnel (no config needed)
- zkettle serve --tunnel
-
- # Create a secret (reads from stdin)
- echo "my secret password" | zkettle create --server http://localhost:3000 --views 1 --minutes 60
-
- # Read a secret
- zkettle read "http://localhost:3000/s/abc123#key"
-
- # Revoke a secret
- zkettle revoke --server http://localhost:3000 --token <delete-token> <id>
-
- # Generate a random secret
- zkettle generate --length 32 --charset symbols
-
- # List active secrets (requires admin token)
- zkettle list --server http://localhost:3000 --admin-token <token>
- ```
-
- ### Cloudflare Quick Tunnel
+ ---
+ name: secret-sharing
+ description: |
+ Share secrets securely using zKettle — a zero-knowledge, self-destructing secret sharing tool.
+ Create, read, revoke, generate, and audit secrets via MCP tools. Secrets are encrypted client-side
+ with AES-256-GCM; the server never sees plaintext. Use for credential sharing, API key rotation,
+ temporary access grants, and secure dead drops.
+ allowed-tools: mcp__zkettle__create_secret mcp__zkettle__read_secret mcp__zkettle__revoke_secret mcp__zkettle__list_secrets mcp__zkettle__generate_secret
+ metadata:
+ author: benderterminal
+ version: "1.0.0"
+ ---
- The `--tunnel` flag creates an instant public URL (e.g. `https://random-name.trycloudflare.com`) with no account, no config, and no DNS setup. Generated secret URLs will automatically use the tunnel URL. Works with both `serve` and `mcp` commands:
+ # Secret Sharing with zKettle
- ```bash
- # Server with public URL
- zkettle serve --tunnel
+ Share passwords, API keys, tokens, certificates, and other sensitive data via self-destructing, one-time URLs. All encryption happens client-side — the server stores only ciphertext.
- # MCP with public URL (shareable links use the tunnel domain)
- zkettle mcp --port 3001 --tunnel
- ```
+ ## Prerequisites
- ## Web UI
+ This skill requires a running zKettle MCP server. If zKettle is not installed or configured, see the [zKettle README](https://github.com/benderterminal/zkettle) for installation and MCP setup instructions. zKettle also offers a CLI, HTTP API, and Web UI — refer to the README if the MCP tools are insufficient for a particular workflow.
- Start the server and open `http://localhost:3000` in a browser. Use `--tunnel` for a public URL shareable with anyone — without it, the web UI is only accessible on localhost. All encryption happens client-side using the Web Crypto API.
+ ## How It Works
- Features:
- - **Create secrets** with configurable view limits and expiry (presets or custom datetime picker)
- - **Generate random secrets** (password, token, or hex key presets) directly in the form
- - **Reveal secrets** via shareable URLs — the viewer auto-detects if a secret has expired or been consumed
- - **Recent secrets panel** tracks created secrets with view counts, expiry timestamps, and revoke buttons
- - **Copy Without Revealing** copies a secret to clipboard without rendering it in the page
- - **Status polling** automatically updates the viewer and recent panel when secrets expire
+ - Secrets are encrypted with AES-256-GCM on the client
+ - The decryption key lives in the URL fragment (`#key`) — never sent to the server
+ - Secrets auto-delete after a configurable view count or time limit
+ - The server is zero-knowledge: it stores only ciphertext and metadata
- ## Tool Reference
+ ## Tools
### `create_secret`
Encrypt and store a secret, returning an expiring URL.
| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
- | `content` | string | no | — | Plaintext secret to encrypt (max 500KB). WARNING: visible in agent conversation context. Use `file` instead for maximum security. |
- | `file` | string | no | — | Read secret content from this file path instead of `content`. Keeps plaintext out of agent context. |
+ | `content` | string | no | — | Plaintext to encrypt (max 500KB). Visible in conversation context — use `file` instead for maximum security. |
+ | `file` | string | no | — | Read secret from this file path instead of `content`. Keeps plaintext out of conversation. |
| `views` | integer | no | `1` | Max views before auto-delete (1–100) |
- | `minutes` | integer | no | `1440` | Minutes until expiry (1–43200, i.e. 30 days max) |
-
- Provide exactly one of `content` or `file`.
+ | `minutes` | integer | no | `1440` | Minutes until expiry (1–43200) |
- **Returns:** `url` (with decryption key in fragment) and `delete_token` for later revocation.
+ Provide exactly one of `content` or `file`. Returns `url` (with decryption key in fragment) and `delete_token`.
### `read_secret`
- Retrieve and decrypt a secret from a zKettle URL. Consumes one view. Use the `file` or `clipboard` parameters to keep the decrypted secret out of the agent conversation context.
+ Decrypt a secret from a zKettle URL. **Consumes one view.**
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `url` | string | yes | Full zKettle URL including the `#key` fragment |
| `file` | string | no | Write decrypted secret to this file path (0600 permissions) instead of returning it |
- | `clipboard` | boolean | no | Copy to system clipboard instead of returning it. No auto-clear. Mutually exclusive with `file`. |
+ | `clipboard` | boolean | no | Copy to system clipboard instead of returning. Mutually exclusive with `file`. |
- **Returns:** Decrypted plaintext (default), or confirmation message when `file` or `clipboard` is used.
+ Returns decrypted plaintext by default, or a confirmation message when `file` or `clipboard` is used.
### `revoke_secret`
- Delete a secret immediately.
+ Permanently delete a secret before it expires.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `id` | string | yes | Secret ID |
| `delete_token` | string | yes | Token returned by `create_secret` |
- **Returns:** Confirmation of revocation.
-
### `list_secrets`
- List all active secrets (metadata only — no content or keys). Local MCP access only.
-
- **Returns:** JSON array of secret metadata (ID, creation time, expiry, remaining views).
+ List all active secrets (metadata only — no content or keys). Returns IDs, creation time, expiry, and remaining views.
### `generate_secret`
Generate a cryptographically random secret. Optionally encrypt and store it in one step.
| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `length` | integer | no | `32` | Length in characters (1–4096) |
- | `charset` | string | no | `"alphanumeric"` | Character set: alphanumeric, symbols, hex, base64url |
+ | `charset` | string | no | `"alphanumeric"` | Character set: `alphanumeric`, `symbols`, `hex`, `base64url` |
| `create` | boolean | no | `false` | If true, encrypt and store the generated secret |
- | `views` | integer | no | `1` | When create=true, max views before auto-delete (1–100) |
- | `minutes` | integer | no | `1440` | When create=true, minutes until expiry (1–43200) |
+ | `views` | integer | no | `1` | When `create=true`, max views (1–100) |
+ | `minutes` | integer | no | `1440` | When `create=true`, minutes until expiry (1–43200) |
- **Returns:** Raw generated text, or `url` + `delete_token` when `create=true`.
+ Returns raw generated text, or `url` + `delete_token` when `create=true`.
- ## Recommended Secure Patterns
+ ## Defaults
- When using zKettle via MCP, plaintext secrets can appear in the agent conversation
- context (tool inputs/outputs). Use these patterns to minimize exposure:
+ - **1 view** if `views` is omitted (single-use)
+ - **1440 minutes** (24 hours) if `minutes` is omitted
- ### Reading secrets without exposing plaintext
+ ## Limits
+
+ - **500KB** max plaintext per secret
+ - **100 views** max per secret
+ - **43200 minutes** (30 days) max TTL
+
+ ## Secure Patterns
+
+ Plaintext secrets can appear in conversation context (tool inputs/outputs). Use these patterns to minimize exposure.
+
+ ### Creating secrets — keep plaintext out of context
+
+ - `create_secret(file="/path/to/secret.txt")` — reads from file, plaintext never in tool call. Ask the user before deleting the source file afterward.
+ - `generate_secret(create=true)` — generates + encrypts in one step, plaintext never in response
+ - Prefer these over `create_secret(content="...")` which puts the raw value in conversation context
+
+ ### Reading secrets — keep plaintext out of context
+
- `read_secret(url="...", file="/tmp/secret.txt")` — writes to file, returns confirmation only
- `read_secret(url="...", clipboard=true)` — copies to user's clipboard, returns confirmation only
- - Subagents can read the file at the path without the secret entering conversation context
+ - Subagents can read the file at the written path without the secret entering conversation context
+ - Default `read_secret(url="...")` returns plaintext directly into conversation — warn the user before using this form
+ - **File cleanup**: Files written by `file` do NOT self-destruct. After the secret has been used, ask the user for permission to delete the file, then remove it. Do not leave secret files on disk.
- ### Creating secrets without exposing plaintext
- - `create_secret(file="/path/to/secret.txt")` — reads content from file, plaintext never in tool call
- - `generate_secret(create=true)` — generates + encrypts in one step, plaintext never in response
+ ## Common Workflows
- ### Web UI
- - Use the **Copy Without Revealing** button on the viewer page to copy a secret to clipboard without rendering it in the page
+ ### Share a credential with someone
- ## Common Agent Patterns
+ ```
+ 1. create_secret(file="/path/to/credential.txt", views=1, minutes=60)
+ — or create_secret(content="the-password", views=1, minutes=60) if plaintext exposure is acceptable
+ 2. Share the returned URL with the recipient
+ ```
- > For maximum security, use `file` instead of `content` when creating secrets — see [Recommended Secure Patterns](#recommended-secure-patterns) above.
+ The secret vanishes after one view or 60 minutes, whichever comes first.
- ### Credential rotation
+ ### Rotate an API key
+
```
- 1. create_secret(content="new-db-password", views=2, minutes=240)
- 2. Share URL with recipient
- 3. revoke_secret(id="old-secret-id", delete_token="old-token")
+ 1. generate_secret(create=true, length=48, charset="alphanumeric", views=2, minutes=240)
+ 2. Share the URL with the team member who needs it
+ 3. revoke_secret(id="old-secret-id", delete_token="old-token") to kill the old one
```
### Temporary access grant
+
```
- 1. create_secret(content="temp-token", views=1, minutes=60)
- 2. Send one-time link — expires after a single view or 60 minutes
+ 1. create_secret(content="temp-token", views=1, minutes=30)
+ 2. Send the one-time link — expires after a single view or 30 minutes
```
+ ### Audit active secrets
+
+ ```
+ 1. list_secrets() — review what's still live
+ 2. revoke_secret() on any that are no longer needed
+ ```
+
### Read a shared secret
+
```
- 1. Receive URL from human or another agent
- 2. read_secret(url="https://example.com/s/abc123#key")
+ 1. Receive a zKettle URL from a human or another agent
+ 2. read_secret(url="https://example.com/s/abc123#key", file="/tmp/secret.txt")
+ — or read_secret(url="...", clipboard=true) to copy to clipboard
+ — or read_secret(url="...") to return plaintext directly (enters conversation context)
3. Use the decrypted content
```
- ### Audit active secrets
+ ## Agent-to-Agent Secret Sharing
+
+ zKettle is well-suited for secure coordination between agents — subagents, teammates, or any multi-agent workflow where credentials need to move between contexts without leaking into conversation logs.
+
+ ### When to use
+
+ - A subagent needs a credential to complete its task (e.g., an engineer agent needs a deploy token)
+ - Agents on a team need to pass secrets to each other without the lead's context window seeing the plaintext
+ - A workflow generates a credential in one agent and consumes it in another
+
+ ### Pattern: file-based handoff (recommended)
+
+ The creating agent writes the secret to zKettle, passes only the URL to the receiving agent. The receiving agent reads it to a file, uses it, then cleans up.
+
```
- 1. list_secrets() → review what's still live
- 2. revoke_secret() on any that are no longer needed
+ Agent A (creator):
+ 1. create_secret(content="deploy-token-xyz", views=1, minutes=30)
+ 2. Send the URL to Agent B via message
+
+ Agent B (consumer):
+ 1. read_secret(url="...", file="/tmp/deploy-token.txt")
+ 2. Use the file contents for the task
+ 3. Delete /tmp/deploy-token.txt when done
```
- ## Security Model
+ The plaintext never enters either agent's conversation context — Agent A sees only the URL and delete token, Agent B sees only a confirmation message.
- - **Zero-knowledge**: The server stores only AES-256-GCM ciphertext. The decryption key is in the URL fragment, which is never sent to the server.
- - **Client-side encryption**: All encryption/decryption happens on the client (CLI, browser Web Crypto API, or MCP tool).
- - **Self-destructing**: Secrets auto-delete after the configured view count or time limit.
+ ### Pattern: generated credential handoff
- ## Limits and Gotchas
+ When the secret doesn't need to be a specific value (e.g., a new API key, a temporary password):
- - **500KB** max plaintext size per secret
- - **100 views** max per secret
- - **43200 minutes** (30 days) max TTL
- - **1 view** default if `views` is omitted
- - **1440 minutes** (24 hours) default if `minutes` is omitted
- - **URL fragments**: The `#key` portion of the URL contains the decryption key. Some tools strip URL fragments — ensure the full URL is passed to `read_secret`
- - **Local-only list**: `list_secrets` is only available via the local MCP stdio transport, not over the network API
+ ```
+ Agent A:
+ 1. generate_secret(create=true, length=48, views=2, minutes=60)
+ 2. Send the URL to Agent B and keep the delete_token
+ 3. After Agent B confirms usage, revoke_secret() to clean up
+ ```
+
+ ### Guidelines
+
+ - Set `views` to the exact number of agents that need to read the secret — no more
+ - Use short TTLs (`minutes`) appropriate to the workflow duration
+ - The creating agent should retain the `delete_token` and revoke after the workflow completes
+ - Prefer `file` on the read side so plaintext stays out of all conversation contexts
+ - The receiving agent is responsible for deleting the local file after use
+
+ ## Gotchas
+
+ - **URL fragments matter**: The `#key` portion contains the decryption key. Some tools strip URL fragments — always ensure the full URL (including `#key`) is passed to `read_secret`.
+ - **Reading consumes a view**: If the secret has only 1 view remaining, `read_secret` permanently destroys it. Warn the user before reading single-view secrets.
+ - **`list_secrets` is metadata only**: It returns IDs, creation time, expiry, and remaining views — never content or decryption keys.
+ - **Conversation exposure**: Using `create_secret(content="...")` puts the plaintext in the conversation context. Use `generate_secret(create=true)` when the user doesn't need a specific value.