enseal
Beta: enseal is under active development. APIs and CLI flags may change between releases.
Secure, ephemeral secret sharing for developers.
Stop pasting secrets into Slack. enseal makes the secure path faster than the insecure one — share .env files and secrets through encrypted, single-use channels with one command and zero setup.
# sender
)
# recipient
Installation
From crates.io
From source
# binary at ./target/release/enseal
Prebuilt binaries
Download from GitHub Releases for Linux (x86_64, aarch64), macOS (Intel, Apple Silicon), and Windows.
Quick Start
Anonymous mode (zero setup)
Share secrets using a one-time code. No keys, no accounts — works immediately.
# terminal 1 (sender)
# terminal 2 (recipient) — enter the code
Works with single secrets too:
# terminal 1 — pipe a token
|
# terminal 2 — prints to stdout
Both terminals must be open at the same time — the sender waits until the recipient connects.
Identity mode (key-based, no codes)
For teams with established key trust. Encrypt to a name, no coordination needed.
# one-time setup
# sender encrypts to recipient by name
# or push through the public relay (no codes at all)
# or produce an encrypted file (no network)
Inject secrets into a process (never touch disk)
# anonymous mode: sender gives you a code
# identity mode: listen on relay, sender pushes when ready
# from an encrypted file drop
Secrets exist only in the child process's memory. When it exits, they're gone.
Features
Three Sharing Modes
Anonymous mode (default) — wormhole-based, zero setup. A human-readable code is all you need. SPAKE2 mutual authentication prevents MITM attacks.
With --relay, anonymous mode bypasses wormhole and uses the enseal relay transport instead. Both sides must use the same relay:
Async upload (--upload) — sender-only. Encrypts locally and posts to burnurl.dev, returning a self-destructing URL the recipient opens in a browser. No CLI required on the recipient side.
# Secret URL: https://burnurl.dev/s/a3f9c2e1...
# Expires: 2026-03-08 19:42:00 UTC (24h)
# Reads: 1 (self-destructs on first open)
Recipient opens the URL in any browser — no enseal install needed. Add --passphrase to encrypt client-side before upload (server sees only ciphertext):
API access requires a Pro or Team plan on burnurl.dev. Set BURNURL_API_KEY to your key. Override the base URL for self-hosted instances: BURNURL_URL=https://burnurl.internal.
Identity mode — public-key encryption for known teammates. Encrypt to a name.
Identity mode supports three transport options:
# wormhole (default, no --relay): generates a code like anonymous mode
# relay push (with --relay): zero codes, pushes directly to recipient's channel
# file drop (with --output): no network, produces encrypted file
# produces ./drop/sarah@company.com.env.age
Flexible Input
enseal accepts secrets from multiple sources:
# .env file (default)
# environment profile
# pipe from stdin
|
|
|
# inline (careful — visible in shell history)
# wrap raw string as KEY=VALUE
|
Variable Interpolation
${VAR} references are resolved before sending so recipients get fully expanded values:
DB_HOST=postgres.internal
DB_PORT=5432
DATABASE_URL=postgres://user:pass@${DB_HOST}:${DB_PORT}/myapp
Supports ${VAR:-default} fallback syntax. Circular and forward references are detected and rejected. Use --no-interpolate to send raw ${VAR} syntax.
Filtering
Control which variables are sent:
# exclude public/non-secret vars
# send only matching vars
# skip .env parsing entirely (send raw file)
Smart Receive
Output adapts to what was sent:
# .env payload -> writes to file
# write to specific file
# raw string -> prints to stdout (pipe-friendly)
# force clipboard
# force stdout for any payload
# receive from encrypted file drop (identity mode)
Inject
Receive secrets and inject them directly as environment variables into a child process. Secrets never touch the filesystem.
# anonymous mode: inject via wormhole code
# identity mode: listen for incoming transfer on relay
# from encrypted file drop
With --listen, the receiver connects to the relay and waits. The sender pushes with enseal share .env --to alex --relay wss://relay.enseal.dev — no codes exchanged, zero coordination needed.
.env Toolkit
Beyond sharing, enseal is a complete .env security toolkit:
# check: verify your .env has all required vars
)
# diff: compare two .env files (keys only, never values)
)
)
# redact: strip values for safe sharing of structure
DATABASE_URL=<REDACTED>
API_KEY=<REDACTED>
PORT=<REDACTED>
# validate: check values against schema rules
# template: generate .env.example with type hints
# DATABASE_URL=<postgres connection string>
# API_KEY=<32+ character string>
# PORT=<integer, 1024-65535>
At-Rest Encryption
Encrypt .env files for safe git storage using age encryption:
# whole-file encryption
)
# per-variable: keys visible for diffing, values encrypted
# DB_HOST=ENC[age:abc123...]
# DB_PORT=ENC[age:def456...]
# multi-recipient: anyone on the team can decrypt
Identity & Key Management
# generate your keypair
# share your public key with teammates
# import a teammate's key (shows fingerprint, prompts for confirmation)
# list all trusted keys and aliases
# show your key fingerprint (for out-of-band verification)
# remove a trusted key
# create aliases for convenience
# create groups for multi-recipient sharing
# delete a group
Public Relay
A free public relay is available at wss://relay.enseal.dev. Use it for quick testing or when you don't need a private relay.
# check relay health
# use it for identity-mode transfers
# or set it globally
Try it yourself
# 1. generate keys (one-time)
# 2. export and import your own key (for self-testing)
# enter an alias when prompted (e.g. "mykey")
# 3. test file drop (no network needed)
# 4. test relay push (two terminals)
# terminal 1 (receiver):
|
# terminal 2 (sender):
Self-Hosted Relay
Keep everything inside your network. The relay is stateless — it sees only ciphertext.
# Docker (one command)
# Or as a binary
# Check relay health
enseal serve speaks plain WebSocket (ws://). For TLS, put a reverse proxy (Caddy, nginx) in front and connect with wss://.
With --relay set, all modes route through your relay:
# Anonymous mode — generates enseal channel code instead of wormhole code
# info: Share code: 3421-amber-frost
# Or set globally
Identity mode with a self-hosted relay is fully codeless:
# receiver listens on the relay
# sender pushes directly — no code generated
Schema Validation
Define rules in .enseal.toml at the project root:
[]
= ["DATABASE_URL", "API_KEY", "JWT_SECRET"]
[]
= "^postgres://"
= "PostgreSQL connection string"
[]
= "integer"
= [1024, 65535]
[]
= 32
Then validate:
Validation also runs automatically when receiving .env files — catching broken configs before they cause confusion.
Environment Profiles
How It Works
Anonymous Mode
Wormhole (default, no --relay):
- Sender encrypts the payload with
age - A SPAKE2 key exchange establishes a shared secret via the public wormhole relay
- The encrypted payload transits through the relay
- Recipient decrypts with the negotiated key
- The channel is destroyed — single use, time-limited
The relay never sees plaintext. The wormhole code provides mutual authentication.
Enseal relay (--relay):
- Sender encrypts the payload with
ageand sends it to the enseal relay under a generated channel code - Recipient connects to the same relay with the same code and receives the payload
- The channel is consumed on first receive
There is no SPAKE2 in this mode — the channel code is the only credential.
Async Upload (--upload)
- The sender serializes the payload to an
Envelope(JSON, SHA-256 integrity check) - Optionally encrypts it client-side with an age scrypt passphrase (
--passphrase) - POSTs the payload to
burnurl.dev/api/secretover HTTPS - burnurl.dev stores it with server-side AES-256-GCM at rest and returns a self-destruct URL
- The URL is valid for the configured TTL (up to 24h on the free tier), single read only
The sender shares the URL. The recipient opens it in any browser — no enseal needed. With --passphrase, the passphrase must be shared separately; the server never sees plaintext.
Tiers: API access requires a Pro or Team plan on burnurl.dev. Set BURNURL_API_KEY to your key — the free tier has no API access.
Override BURNURL_URL to point at a self-hosted burnurl instance.
Identity Mode (Public Key)
- Sender encrypts with the recipient's
agepublic key - Sender signs with their own
ed25519key - Payload transits through relay, file drop, or wormhole
- Recipient decrypts with their private key
- Recipient verifies the sender's signature
Trust is based on which keys you've imported.
Transport options in identity mode:
| Transport | Flag | How it works |
|---|---|---|
| Wormhole (default) | --to sarah |
Generates a code, like anonymous mode but with signing |
| Relay push | --to sarah --relay URL |
Pushes to recipient's deterministic channel, no code |
| File drop | --to sarah --output ./dir/ |
Produces encrypted .env.age file, no network |
With relay push, the recipient listens with enseal inject --listen --relay URL -- cmd or receives the file drop with enseal receive ./file.env.age.
Security Model
Protected:
- Secrets in transit (encrypted channel)
- Secrets in Slack/email history (ephemeral, no persistence)
- MITM attacks (SPAKE2 / public key auth)
- Malicious relay (E2E encryption, relay sees ciphertext only)
- Sender impersonation (identity mode: ed25519 signatures)
- Secrets on disk (inject mode: process memory only)
- Secrets in git (encrypt: at-rest encryption)
Not protected:
- Compromised endpoints (if the machine is owned, nothing helps)
- Key distribution (you trust the keys you import — no PKI, no CA)
Configuration
Optional .enseal.toml in your project root:
[]
= "wss://relay.enseal.dev" # public relay (identity mode)
# relay = "ws://relay.internal:4443" # self-hosted without TLS
# relay = "wss://relay.internal:4443" # self-hosted with TLS reverse proxy
[]
= ["^PUBLIC_", "^NEXT_PUBLIC_", "^REACT_APP_"]
[]
= "devops-team"
[]
= ["DATABASE_URL", "API_KEY", "JWT_SECRET"]
CLI Reference
CORE
enseal share [<file>] Send secrets (file, pipe, or --secret)
enseal receive [<code|file>] Receive secrets
enseal inject [<code>] -- <cmd> Inject secrets into a process
enseal keys <subcommand> Manage identity keys and aliases
enseal serve Run self-hosted relay server
.ENV TOOLKIT
enseal check [file] Verify .env has all vars from .env.example
enseal diff <file1> <file2> Compare .env files (keys only)
enseal redact <file> Replace values with <REDACTED>
enseal validate <file> Validate against schema rules
enseal template <file> Generate .env.example with type hints
ENCRYPTION
enseal encrypt <file> Encrypt .env for git storage
enseal decrypt <file> Decrypt an encrypted .env
share flags
--to <name> Identity mode: encrypt to recipient (alias, group, or identity)
--output <dir> File drop: write encrypted file (identity mode, no network)
--upload Post to burnurl.dev (async, browser-readable, no CLI on recipient side)
--ttl <hours> Secret TTL for --upload (1-24, default: 24)
--passphrase Encrypt client-side before --upload (prompts; server never sees plaintext)
--secret <value> Inline secret (raw string or KEY=VALUE)
--label <name> Human label for raw/piped secrets
--as <KEY> Wrap raw input as KEY=<value>
--relay <url> Route through relay server. Anonymous mode: uses enseal relay transport
(generates channel code, bypasses wormhole). Identity mode: push to
recipient's channel. Also: ENSEAL_RELAY env var.
--env <profile> Environment profile (resolves to .env.<profile>)
--exclude <pattern> Regex to exclude vars
--include <pattern> Regex to include only matching vars
--no-filter Send raw file, skip .env parsing
--no-interpolate Don't resolve ${VAR} references before sending
--words <n> Words in wormhole code (2-5, default: 2). Wormhole mode only (no --relay).
--quiet / -q Minimal output
receive flags
--output <path> Write to specific file
--clipboard Copy to clipboard instead of stdout/file
--no-write Print to stdout even for .env payloads
--relay <url> Use specific relay server
--quiet / -q Minimal output
inject flags
--listen Listen for incoming identity-mode transfer (requires --relay)
--relay <url> Use specific relay server (also: ENSEAL_RELAY)
--quiet / -q Minimal output
keys subcommands
enseal keys init Generate your keypair
enseal keys export Print your public key bundle
enseal keys import <file> Import a colleague's public key
enseal keys list Show all trusted keys and aliases
enseal keys remove <identity> Remove a trusted key
enseal keys fingerprint Show your key fingerprint
enseal keys alias <name> <identity> Map short name to identity
enseal keys group create <name> Create a named group
enseal keys group add <group> <id> Add identity to group
enseal keys group remove <group> <id> Remove identity from group
enseal keys group list [name] List groups or group members
enseal keys group delete <name> Delete a group
serve flags
--port <port> Listen port (default: 4443)
--bind <addr> Bind address (default: 0.0.0.0)
--max-mailboxes <n> Max concurrent channels (default: 100)
--channel-ttl <seconds> Idle channel lifetime (default: 300)
--max-payload <bytes> Max WebSocket message size (default: 1048576)
--rate-limit <n> Max connections per minute per IP (default: 10)
--health Print server health check and exit
encrypt / decrypt flags
--per-var Per-variable encryption (keys visible, values encrypted)
--to <name> Encrypt to specific recipients (multi-key)
Global flags
--verbose / -v Debug output (never prints secret values)
--quiet / -q Minimal output (for scripting)
Comparison
| enseal | Slack DM | 1Password Share | dotenvx | croc | |
|---|---|---|---|---|---|
| Zero setup | Yes | Yes | No | No | Yes |
| End-to-end encrypted | Yes | No | Yes | N/A | Yes |
| Ephemeral (no history) | Yes | No | Yes | N/A | Yes |
| .env aware | Yes | No | No | Yes | No |
| Process injection | Yes | No | No | Yes | No |
| Schema validation | Yes | No | No | No | No |
| At-rest encryption | Yes | N/A | N/A | Yes | No |
| Self-hostable relay | Yes | No | No | N/A | Yes |
| Raw string/pipe support | Yes | Yes | No | No | Yes |
Roadmap
- v0.1 — Core: share/receive, pipe/stdin, .env toolkit (check, diff, redact) (done)
- v0.2 — Identity mode: keys, aliases,
--toflag (done) - v0.3 — Inject command, self-hosted relay (done)
- v0.4 — Schema validation, templates, interpolation, profiles (done)
- v0.5 — At-rest encryption (encrypt/decrypt) (done)
- v0.10 — Groups, Helm chart, docs (done)
- v0.11 — Security hardening, docs sync (done)
- v0.12 — .enseal.toml wired up, private relay for anonymous mode (done)
- v0.16 — Async upload via burnurl.dev (
--upload) (current) - v1.0 — crates.io publish, final polish
License
MIT