#encryption #vault #secret #axum #ephemeral

sirr-server

Sirr server library — axum HTTP server with redb storage and ChaCha20Poly1305 encryption

9 stable releases

new 1.0.36 Mar 7, 2026
1.0.35 Mar 5, 2026
1.0.28 Mar 2, 2026
1.0.23 Feb 28, 2026

#611 in HTTP server


Used in sirrd

Custom license

265KB
6K SLoC

Sirr

License: BSL 1.1 CI

The secret manager built for the AI era. Every secret expires. By design.

Sirr is a self-hosted vault where secrets are ephemeral by default — not as a feature you opt into, but as the core philosophy. Set a TTL, a read limit, or both. Once the condition is met, the secret is gone. No cleanup, no stale credentials, no blast radius.

Single binary. Single file database. Zero runtime dependencies.


The AI-Era Credential Problem

Every time you paste a database URL, API key, or token into an AI assistant, you face a hard choice: productivity now vs. security debt forever.

With ChatGPT, Copilot, Claude, or any AI coding tool:

  • The credential appears in your conversation history
  • It may be retained by the provider for fine-tuning or review
  • Even if you delete the chat, you can't guarantee deletion from their side
  • The credential itself persists in your vault until you manually revoke it
  • You probably won't remember to revoke it

Traditional secret managers don't solve this. Vault, AWS Secrets Manager, 1Password — all of them are permanent stores with optional rotation. They're excellent at what they do, but they treat secrets as assets to preserve, not liabilities to eliminate.

Sirr is different. The secret is born dying.

# Give Claude exactly one read window — 1 hour, 1 read
sirr push PROD_DB="postgres://user:pass@host/db" --reads 1 --ttl 1h

# Tell Claude: "analyze the schema at PROD_DB"
# Claude reads it via MCP → read counter hits limit → credential deleted
# The conversation gets retained. The credential doesn't.

This isn't paranoia. This is correct threat modeling for an age where your coding assistant is a third party.


Why Not Vault or AWS Secrets Manager?

HashiCorp Vault is infrastructure secret management. It's designed for long-lived service credentials, PKI, dynamic database roles at scale. It requires cluster setup, unseal keys, policy authoring, and significant operational investment. It has TTLs and lease mechanisms, but the mental model is preservation: secrets are assets to be rotated, not destroyed. There is no "burn after N reads." The complexity is justified when you're managing secrets for hundreds of services in a regulated enterprise. It's overkill when a developer needs to share a credential with an AI agent for one task.

AWS Secrets Manager is a managed permanent store with automated rotation. At $0.40/secret/month plus API call charges, with no read-count enforcement, no single-binary self-hosting, and deep AWS lock-in, it solves a different problem: "keep this secret, rotate it automatically, and integrate it with our IAM policies." It is not designed to answer: "how do I give an AI agent exactly one use of this credential?"

Sirr occupies a different position: ephemeral credentials for humans and AI agents working in the short term. The comparison isn't Sirr vs. Vault — it's Sirr vs. the current practice of pasting credentials into chat windows and hoping for the best.

Sirr HashiCorp Vault AWS Secrets Manager
Setup Single binary Cluster + unsealing AWS account + IAM
Mental model Secrets die Secrets rotate Secrets persist
Burn-after-N-reads Yes No No
AI/MCP integration Native No No
Self-hosted Yes Yes No
Price Free ≤100 secrets OSS free / Enterprise $$ $0.40/secret/month
Operational burden Near zero High Medium

Quick Start

Run the server

With a key file (recommended for production):

# Generate the master key file once.
openssl rand -hex 32 > master.key
chmod 400 master.key

docker run -d \
  -p 39999:39999 \
  -v ./sirr-data:/data \
  -v ./master.key:/run/secrets/master.key:ro \
  -e SIRR_MASTER_KEY_FILE=/run/secrets/master.key \
  ghcr.io/sirrlock/sirrd

Or with an environment variable (development only — visible via docker inspect):

docker run -d \
  -p 39999:39999 \
  -v ./sirr-data:/data \
  ghcr.io/sirrlock/sirrd

Or as a binary:

./sirrd serve
# Optionally protect writes: SIRR_API_KEY=my-key ./sirrd serve

Push and retrieve

# One-time credential (burns after 1 read)
sirr push DB_URL="postgres://..." --reads 1 --ttl 1h
sirr get DB_URL      # returns value
sirr get DB_URL      # 404 — burned

# Patchable secret (keeps the key, allows value updates)
sirr push CF_TOKEN=abc123 --reads 5 --no-delete
sirr get CF_TOKEN    # returns value, reads remaining: 4

# Expiring .env for your team
sirr push .env --ttl 24h
sirr pull .env       # on the other machine

# Run a process with secrets injected as env vars
sirr run -- node app.js

AI Workflows

Claude Code (MCP)

Install the MCP server so Claude can read and write secrets directly:

npm install -g @sirrlock/mcp

.mcp.json:

{
  "mcpServers": {
    "sirr": {
      "command": "sirr-mcp",
      "env": {
        "SIRR_SERVER": "http://localhost:39999",
        "SIRR_API_KEY": "your-api-key"
      }
    }
  }
}

Once connected:

You: "I need you to analyze the schema at PROD_DB. It's in sirr, burn it after you read it."
Claude: [reads PROD_DB via MCP][credential auto-deleted][analyzes schema]

You: "Push my Stripe test key to sirr, one read only, 30 minutes"
Claude: [calls push_secret("STRIPE_KEY", "sk_test_...", reads=1, ttl=1800)]

Python AI Agents (LangChain, CrewAI, AutoGen)

from sirr import SirrClient

sirr = SirrClient(server="http://localhost:39999", api_key=os.environ.get("SIRR_API_KEY"))

# Give an agent a one-use credential
sirr.push("DB_URL", connection_string, reads=1, ttl=3600)

# Agent reads it — credential is gone regardless of what the agent logs or retains

CI/CD One-Time Tokens

# GitHub Actions: deploy token that can only be used once
- run: |
    sirr push DEPLOY_TOKEN="${{ secrets.DEPLOY_TOKEN }}" --reads 1
    sirr run -- ./deploy.sh
    # DEPLOY_TOKEN is gone after deploy.sh runs

Full Usage

# Push
sirr push KEY=VALUE [--ttl <duration>] [--reads <n>] [--no-delete]
sirr push .env [--ttl <duration>]          # push entire .env file

# Retrieve
sirr get KEY                               # stdout, burns if read limit hit
sirr pull .env                             # pull all secrets into .env
sirr run -- <command>                      # inject all secrets as env vars

# Manage
sirr list                                  # metadata only, no values shown
sirr delete KEY
sirr prune                                 # delete all expired secrets now
sirr share KEY                             # print reference URL

# Key rotation (offline — stop the server first)
sirr rotate                                # re-encrypts all records with new key

TTL format: 30s, 5m, 2h, 7d, 30d

--no-delete: Secret is sealed (reads blocked) when max_reads is hit, but stays in the database. Can be updated via PATCH /secrets/:key and unsealed.


Multi-Tenant Mode

Sirr supports org-scoped secrets with role-based access control. Each org has principals (identities) with named API keys and roles that control permissions.

Bootstrap

# Auto-create a default org + admin principal + temporary keys on first boot:
sirrd serve --init
# Or: SIRR_AUTOINIT=true sirrd serve

The bootstrap prints org ID, principal ID, and two temporary API keys (valid 30 minutes). Use those keys to create permanent ones.

Org management (requires master key)

export SIRR_API_KEY=<master-key>

sirr orgs create "My Team"
sirr orgs list
sirr principals create <org_id> alice --role writer
sirr me create-key --name deploy-key    # using a principal key

Using org-scoped secrets

# With a principal API key:
export SIRR_API_KEY=<principal-key>

sirr push DB_URL=postgres://... --org <org_id>
sirr get DB_URL --org <org_id>
sirr list --org <org_id>

Built-in roles

Role Permissions Description
reader rla Read own secrets, list own, read own account
writer rlcpdam Read/list/create/patch/delete own secrets + manage account
admin rRlLcCpPaAmMdD Full org access (all secrets, manage principals/roles)
owner rRlLcCpPaAmMSdD Admin + SirrAdmin (can create/delete orgs)

Custom roles can be created per-org with any combination of the 15 permission bits.

Disabling the public bucket

Set ENABLE_PUBLIC_BUCKET=false to serve only org-scoped routes.


HTTP API

Public routes (no auth required):

GET /secrets/:key

Retrieves value. Increments read counter. Burns or seals record if read limit reached.

{ "key": "DB_URL", "value": "postgres://..." }
// 404 if expired, burned, or not found
// 410 if sealed (delete=false, reads exhausted)

HEAD /secrets/:key

Returns metadata via headers. Does NOT increment read counter.

X-Sirr-Read-Count: 3
X-Sirr-Reads-Remaining: 7    (or "unlimited")
X-Sirr-Delete: false
X-Sirr-Created-At: 1700000000
X-Sirr-Expires-At: 1700003600  (if TTL set)
X-Sirr-Status: active          (or "sealed")
// 200, 404 (not found), or 410 (sealed)

GET /health{ "status": "ok" }

Protected routes (require Authorization: Bearer <SIRR_API_KEY> if SIRR_API_KEY is set):

POST /secrets

{ "key": "DB_URL", "value": "postgres://...", "ttl_seconds": 3600, "max_reads": 1, "delete": true }
// delete defaults to true. Set false for patchable secrets.
// 201: { "key": "DB_URL" }
// 402: license required (>100 secrets without SIRR_LICENSE_KEY)

PATCH /secrets/:key

Update value, max_reads, or TTL. Only works on delete=false secrets. Resets read_count to 0.

{ "value": "new-value", "max_reads": 10, "ttl_seconds": 3600 }
// All fields optional. Omitted fields keep current values.
// 200: updated metadata
// 409: cannot patch a delete=true secret
// 404: not found or expired

GET /secrets

Returns metadata only — values are never included in list responses.

{
  "secrets": [
    { "key": "DB_URL", "created_at": 1700000000, "expires_at": 1700003600, "max_reads": 1, "read_count": 0, "delete": true }
  ]
}

DELETE /secrets/:key{ "deleted": true }

POST /prune{ "pruned": 3 }


Configuration

Variable Default Description
SIRR_API_KEY auto-generated Protects all authenticated endpoints. Printed at startup if not set — copy and persist it.
SIRR_LICENSE_KEY Required for >100 active secrets
SIRR_PORT 39999 HTTP listen port
SIRR_HOST 0.0.0.0 Bind address
SIRR_DATA_DIR platform default¹ Storage directory
SIRR_CORS_ORIGINS * (all) Comma-separated allowed origins for management endpoints
SIRR_LOG_LEVEL info trace / debug / info / warn / error
SIRR_RATE_LIMIT_PER_SECOND 10 Per-IP request rate (steady-state, all routes)
SIRR_RATE_LIMIT_BURST 30 Per-IP burst allowance
NO_BANNER 0 Set to 1 to suppress the startup banner
NO_SECURITY_BANNER 0 Set to 1 to suppress the auto-generated key notice
ENABLE_PUBLIC_BUCKET true Set to false to disable legacy /secrets routes
SIRR_AUTOINIT false Set to true to auto-create default org on first boot

CORS design note: sirrd is a backend service, not a browser API. GET /secrets/{key} deliberately returns no Access-Control-Allow-Origin header — browsers block cross-origin reads of secret values by design, regardless of SIRR_CORS_ORIGINS. Management endpoints (create, list, delete, keys) do respect SIRR_CORS_ORIGINS so a trusted admin UI on a different origin can talk to them. If you need browser clients to read secrets, run them on the same origin as sirrd or proxy through your own backend.

One of SIRR_MASTER_KEY_FILE or SIRR_MASTER_KEY is required. If both are set, the file takes precedence. File-based key delivery is recommended for production because environment variables are visible via docker inspect and /proc.

CLI / client variables:

Variable Default Description
SIRR_SERVER sirr://localhost:39999 Server base URL (sirr:// → http, sirrs:// → https)
SIRR_API_KEY Same value as server's SIRR_API_KEY (for write ops)

Key rotation variables (used by sirr rotate):

Variable Description
SIRR_NEW_MASTER_KEY_FILE Path to file containing the new master key
SIRR_NEW_MASTER_KEY New master key value (prefer _FILE)

¹ ~/.local/share/sirr/ (Linux), ~/Library/Application Support/sirr/ (macOS), %APPDATA%\sirr\ (Windows). Docker: mount /data and set SIRR_DATA_DIR=/data.


Architecture

CLI / Node SDK / Python SDK / .NET SDK / MCP Server
              ↓  HTTP (optional API key for writes)
         axum REST API (Rust)
              ↓
     redb embedded database (sirr.db)
              ↓
   ChaCha20Poly1305 encrypted values
   (key = random 32 bytes in sirr.key)
  • sirr.key — random 32-byte encryption key, generated on first run, stored beside sirr.db
  • Per-record random 12-byte nonce; value field is encrypted, metadata is not
  • Reads are public (no auth). Writes optionally protected by SIRR_API_KEY

Licensing

Business Source License 1.1

Solo (free) 1 org, 3 principals, unlimited secrets
Team 5 orgs, 25 principals
Business Unlimited orgs and principals
Free All non-production use (dev, staging, CI)
Source available Forks and modifications permitted
Converts to Apache 2.0 February 20, 2028

License keys at sirrlock.com/pricing.

SIRR_LICENSE_KEY=sirr_lic_... ./sirr serve

Roadmap

  • Web UI
  • Webhooks on expiry / burn
  • Team namespaces (multi-tenant orgs)
  • Audit log
  • Kubernetes operator
  • Terraform provider
  • Patchable secrets (update value without changing key)
  • Secret versioning

Package Description
@sirrlock/mcp MCP server for AI assistants
@sirrlock/node Node.js / TypeScript SDK
sirr (PyPI) Python SDK
Sirr.Client (NuGet) .NET SDK
sirr.dev Documentation
sirrlock.com Hosted service + license keys

Secrets that whisper and disappear.

Dependencies

~21–40MB
~504K SLoC