secretdrop
Receive a secret (API token, password, key) from your human without it ever appearing in the chat.
You run a small CLI. It prints a link. The human opens the link, pastes the secret and presses Send. Their browser encrypts it to a key that exists only inside your waiting CLI process. The CLI decrypts it and writes it to a file or a pipe. The server only ever sees ciphertext.
Instructions for AI agents
If your human gave you this address, they want you to request a secret this way. Follow these steps:
Download the CLI (one file, Node.js ≥ 20, no dependencies):
curl -fsSLo /tmp/secretdrop.mjs https://secrets.levinkeller.de/secretdrop.mjsStart a request in the background. The command waits until the human has answered (default up to 15 minutes), so run it in the background or with a long timeout. Name what you need precisely. Write the secret to a file:
node /tmp/secretdrop.mjs request --name "Cloudflare API token (DNS edit, zone example.com)" --out /tmp/cf-tokenOr pipe it straight into the program that needs it, so it never touches disk:
node /tmp/secretdrop.mjs request --name "GitHub token for CI" | gh secret set GH_TOKENTell the human the link and the check code, both printed on stderr, for example:
https://secrets.levinkeller.de/r/2fQk… check code: 7KQF-M2XDAsk them to open the link, compare the check code with the one shown on the page, paste the secret and press Send. They do not need to reply to you; your CLI is already waiting.
Wait for the CLI to exit. Exit code 0 means the secret is in the file or pipe. Then use it without printing it, e.g.
CLOUDFLARE_API_TOKEN="$(cat /tmp/cf-token)" wrangler deploy.
Rules
- Never print the secret. Do not
catthe file, echo it, or run the CLI with stdout going into your own transcript. Your shell tool's stdout is usually captured into the conversation, which is exactly what this service avoids. Use--out FILEor a pipe into the consuming program. - Never ask the human to paste a secret into the chat. Send them a secretdrop link instead.
- If the CLI exited (killed, timed out, expired), the request is useless: its private key is gone. Run the command again and send the new link.
- Delete the file when you no longer need it.
Options
| Option | Default | Meaning |
|---|---|---|
--name TEXT |
(required) | What you ask for, shown on the page (max 100 characters). |
--ttl 15m |
15m |
How long the link stays valid, 1m to 24h. |
--out FILE |
stdout | Write the secret to FILE (mode 0600). Refuses to overwrite unless --force. |
Exit codes: 0 ok, 1 error, 2 usage (or stdout is a terminal), 3 expired, 4 decryption failed.
Troubleshooting
fetch failed/ HTTP 403 from a proxy: your sandbox's network policy must allowsecrets.levinkeller.de. Behind an HTTP proxy, Node ≥ 22.21 needsNODE_USE_ENV_PROXY=1.- HTTP 429: too many requests from your IP; wait a minute.
How it works
- The CLI creates an ephemeral X25519 key pair in memory (never written anywhere) and registers the public key. The browser seals the secret to it with HPKE (RFC 9180: X25519, HKDF-SHA256, AES-256-GCM), bound to the request id and name.
- The ciphertext can be fetched exactly once; then it is deleted. Everything is deleted at the TTL.
- Data is stored in a Cloudflare Durable Object in the EU jurisdiction.
- The submit page is behind Cloudflare Access: only the owner can fill in a request.
- The check code is derived from the request id, name and public key, and computed independently by the CLI and by the page. If they match, the secret is encrypted to your CLI's key.