Envault Serve: Your Encrypted .env Files Now Have a REST API
You encrypt your secrets with envault. You rotate them with envault. You sync them across environments with envault. But when your MCP server, CI sidecar, or agent runtime needs a secret at runtime, you still write custom decryption glue code.
Not anymore. envault serve exposes your decrypted secrets as a JSON HTTP API with a single command. Bearer token auth. Prefix filtering. Health checks. Zero custom code.
Try it now
pip install envault · envault serve --port 8080 · Your secrets are live over HTTP
View on GitHub →In This Article
The Gap: Encrypted at Rest, Unusable at Runtime
Secrets management tools solve half the problem. You encrypt .env files, rotate keys, and sync across environments. But the other half — getting decrypted secrets to the processes that need them — still requires custom work:
- MCP servers that need API keys at startup — you shell out to
envault decrypt, parse the output, inject into env vars - CI/CD sidecars that fetch secrets for deploy-time injection — you write a script that calls the vault API, handles auth, retries on failure
- AI agent runtimes that need credentials mid-session — you either bake secrets into the agent config (risky) or add a secrets-fetch step to every agent session
Each of these is a small integration project. Small projects add up. And every custom integration is a place where secrets leak, auth breaks, or rotation fails silently.
envault serve eliminates the glue code. Start the server, authenticate with a Bearer token, fetch secrets over HTTP. That's it.
How envault serve Works
One command starts the API:
# Start the secrets API on port 8080 (default) envault serve --port 8080 # Or specify a custom host and password envault serve --host 0.0.0.0 --port 3000 --password your-master-key
The server reads your .envault.yml config, connects to your configured secret store (local .env, AWS SSM, Vault, Doppler, or 1Password), and exposes three endpoints:
GET /health— Store connectivity check (unauthenticated)GET /secrets— List all secret keys, with optional?prefix=XfilteringGET /secrets/{key}— Get the decrypted value for a specific key
On startup, envault prints your API token — the SHA-256 hash of your encryption key. Use this as a Bearer token in all requests:
✓ envault serve listening on http://127.0.0.1:8080
API token: a3f8c2d1e4b7...
GET /secrets — list all secret keys
GET /secrets?prefix=X — filter keys by prefix
GET /secrets/{key} — get decrypted value
GET /health — store connectivity check
Press Ctrl+C to stop
Auth Model: Bearer Token from Your Encryption Key
No separate API key to manage. No OAuth flow. No IAM role to configure. The authentication token is derived from the same encryption key you already use with envault:
# The API token is SHA-256(your-encryption-key) # Same password you use for envault decrypt / envault rotate # Fetch secrets with curl curl -H "Authorization: Bearer a3f8c2d1e4b7..." \ http://localhost:8080/secrets # Filter by prefix (e.g., all Stripe keys) curl -H "Authorization: Bearer a3f8c2d1e4b7..." \ "http://localhost:8080/secrets?prefix=STRIPE"
This design is intentional: if someone has your encryption key, they already have full access to your secrets via envault decrypt. The API doesn't introduce a new trust boundary — it uses the existing one. The SHA-256 hash prevents the plaintext key from appearing in HTTP headers or logs, while maintaining the same access level.
Key Derivation, Not Key Storage
The Bearer token is a deterministic SHA-256 hash of your encryption key — envault never stores it. Recompute it anytime: echo -n "your-key" | sha256sum. Rotate your encryption key, and the API token changes automatically.
API Reference
GET /health
Connectivity check for the backing store. No auth required.
$ curl http://localhost:8080/health
{
"status": "ok",
"checks": {
"local": { "status": "ok", "path": ".env.production" }
}
}
GET /secrets
List all secret keys. Supports ?prefix=X to filter. Auth required.
$ curl -H "Authorization: Bearer TOKEN" \
http://localhost:8080/secrets?prefix=STRIPE
{
"keys": ["STRIPE_PUBLIC_KEY", "STRIPE_SECRET_KEY", "STRIPE_WEBHOOK_SECRET"],
"count": 3
}
GET /secrets/{key}
Get the decrypted value for a single key. Auth required.
$ curl -H "Authorization: Bearer TOKEN" \
http://localhost:8080/secrets/STRIPE_SECRET_KEY
{
"key": "STRIPE_SECRET_KEY",
"value": "sk_live_..."
}
Three Use Cases Where This Matters
1. MCP Server Secret Injection
When you wrap a CLI as an MCP server with click-to-mcp, the server process needs API keys to call external services. Instead of baking secrets into the MCP config or the process environment:
# Start envault serve as a sidecar envault serve --port 8080 & # Your MCP server fetches secrets at startup STRIPE_KEY=$(curl -s -H "Authorization: Bearer $TOKEN" \ http://localhost:8080/secrets/STRIPE_SECRET_KEY | jq -r .value) # MCP server starts with the decrypted key click-to-mcp serve my-tool --env STRIPE_SECRET_KEY=$STRIPE_KEY
No secrets in config files. No secrets in container images. The sidecar pattern keeps the decryption boundary tight.
2. CI/CD Deploy-Time Secret Fetch
In your GitHub Actions deploy step, fetch secrets from envault instead of storing them as GitHub Secrets (which can't be rotated automatically and have a 48-hour cache lag):
# .github/workflows/deploy.yml
- name: Fetch secrets from envault
run: |
curl -s -H "Authorization: Bearer ${{ secrets.ENVAULT_TOKEN }}" \
http://envault.internal:8080/secrets?prefix=PROD_ | \
jq -r '.keys[]' | while read key; do
val=$(curl -s -H "Authorization: Bearer ${{ secrets.ENVAULT_TOKEN }}" \
"http://envault.internal:8080/secrets/$key" | jq -r .value)
echo "$key=$val" >> $GITHUB_ENV
done
Pair this with envault rotate-all --env prod in a scheduled job, and your deployment always gets the latest rotated secrets — no manual secret updates in GitHub.
3. AI Agent Runtime Credential Store
AI agents that use tool-calling patterns need credentials mid-session. Instead of embedding secrets in agent config (which gets logged, versioned, and leaked):
# Agent runtime fetches credentials on demand
def get_credential(key: str) -> str:
resp = requests.get(
f"http://localhost:8080/secrets/{key}",
headers={"Authorization": f"Bearer {os.environ['ENVAULT_TOKEN']}"}
)
return resp.json()["value"]
# Agent uses credentials only when needed, never stored in memory long-term
stripe_key = get_credential("STRIPE_SECRET_KEY")
This is the integration pattern recommended by the Agent Vault evaluation (COM-256): envault serve as a lightweight, self-hosted credential store backend for AI agent workflows.
How This Compares to Vault, Doppler, and Infisical
| envault serve | HashiCorp Vault | Doppler | Infisical | |
|---|---|---|---|---|
| Setup | 1 command | Server + storage backend + IAM | Cloud account + CLI + sync | Cloud account + CLI + agents |
| Auth model | SHA-256 of encryption key | Tokens, roles, policies | Service tokens + RBAC | Service tokens + RBAC |
| Self-hosted | Yes, fully | Yes | No (cloud only) | Yes (Agent Vault) |
| Secrets stay local | Yes | Yes (with config) | No | Partial |
| Zero external deps | Yes — stdlib only | No — Consul/storage backend | No — cloud API | No — KMS + agents |
| Cost | Free, open source | Free (self-host) / $1.50/hr (HCP) | Free tier / $18/mo+ | Free tier / $8/seat/mo |
| Best for | Small teams, CI/CD sidecars, agent runtimes | Enterprise, multi-team, compliance | SaaS teams, cloud-native | Teams needing agent secrets |
When to choose envault serve: You want a secrets API without standing up a Vault cluster, without sending secrets to a cloud service, and without adding a new dependency to your infrastructure. If you already use envault for encryption and rotation, envault serve is the natural runtime extension — same config, same keys, same trust boundary.
When to choose Vault: You need enterprise-grade audit logging, multi-team RBAC, secret leasing with automatic revocation, or PKI/certificate management. Vault is the right choice at scale — envault serve is the right choice when you need a secrets API in 30 seconds.
Security Model and Trade-offs
Honest security posture for envault serve:
- Bind address: Defaults to
127.0.0.1(localhost only). Use--host 0.0.0.0only behind a firewall or reverse proxy. - Transport: No built-in TLS. Run behind a reverse proxy (nginx, Caddy) for HTTPS in production, or use a VPN/service mesh.
- Auth: Bearer token via constant-time comparison (
hmac.compare_digest— timing-safe). No rate limiting — add this at the reverse proxy layer. - Secrets in memory: Decrypted values exist in the server process memory for the duration of the request. Not cached between requests.
- Rotation: Pair with
envault rotateandenvault rotate-allfor automatic key rotation. Restart the server to pick up rotated values.
The intended deployment is a sidecar or internal service — not an internet-facing endpoint. If you need internet-facing secrets distribution, use Vault with proper TLS, IAM policies, and audit logging.
Get Started
# Install envault pip install git+https://github.com/Coding-Dev-Tools/envault.git # Initialize your project rh-envault init my-project # Start the secrets API rh-envault serve --port 8080 # Test it curl http://localhost:8080/health curl -H "Authorization: Bearer YOUR_TOKEN" http://localhost:8080/secrets
Your encrypted secrets were already safe at rest. Now they're accessible at runtime — with the same key, the same config, and one new command.
Start serving your secrets over HTTP
pip install envault · Apache 2.0 · Free and open source
View on GitHub → Full command docs →DevForge builds 11 open-source CLI tools for developers — from API contract validation to environment variable management to infrastructure cost previews. Every tool is built by autonomous AI agents. See all tools →