Skip to main content

Python client for the Sirr ephemeral secret vault

Project description

sirr (Python)

PyPI PyPI downloads CI Python License: MIT GitHub stars Last commit

Ephemeral secrets for Python AI agents. Credentials that delete themselves.

sirr is the Python client for Sirr — a self-hosted vault where every secret expires by read count, by time, or both. Built for the Python AI ecosystem: LangChain, CrewAI, AutoGen, LlamaIndex, and any framework that needs to hand credentials to agents without leaving them lying around forever.


The Problem It Solves

Python dominates the AI/ML landscape. That means Python is where credentials get handed to agents, embedded in tool calls, interpolated into prompts, and logged by frameworks. Every time an agent reads a database URL or API key, you have limited visibility into what that framework stored, logged, or will use for fine-tuning.

The standard answer — "rotate your secrets after every session" — doesn't scale. You forget. It's tedious. It requires manual IAM work.

Sirr gives you a better primitive: credentials that enforce their own expiry. An agent reads it once. The server deletes it. You don't have to remember to clean anything up.

# Push a dead drop — returns a one-time URL
result = sirr.push(api_key, reads=1, ttl=600)
print(result.url)  # → https://sirr.sirrlock.com/s/abc123

# Or set an org-scoped named secret
sirr.set("OPENAI_KEY", api_key, org="acme", reads=1, ttl=600)
# Agent calls sirr.get("OPENAI_KEY", org="acme") → gets the value → record deleted

Install

pip install sirr

Requires Python 3.10+. Supports sync and async.


Usage

import os
from sirr import SirrClient, SecretExistsError

sirr = SirrClient(
    server=os.environ.get("SIRR_SERVER", "https://sirr.sirrlock.com"),
    token=os.environ["SIRR_TOKEN"],
)

# Push a public dead drop — returns { id, url }
result = sirr.push("sk-...", reads=1, ttl=3600)
print(result.url)  # → https://sirr.sirrlock.com/s/abc123

# Set an org-scoped named secret — raises SecretExistsError on 409
sirr.set("DB_URL", "postgres://...", org="acme", ttl=86400, reads=3)

# Retrieve — routes by org presence
value = sirr.get(result.id)                     # dead drop by ID
db_url = sirr.get("DB_URL", org="acme")         # org-scoped by key

# Handle conflicts
try:
    sirr.set("DB_URL", "postgres://new...", org="acme")
except SecretExistsError:
    print("Key already exists — delete first or use a different key")

# Inspect metadata without consuming a read
meta = sirr.head("DB_URL")
# → SecretHead(key='DB_URL', read_count=0, reads_remaining=3, ...)

# Pull all secrets into a dict
secrets = sirr.pull_all()
# → {"DB_URL": "postgres://..."}

# Inject as environment variables for the duration of a block
with sirr.env():
    # os.environ["DB_URL"] is set here
    run_agent_task()
    # restored on exit, even on exception

# Delete immediately
sirr.delete("DB_URL")

# List active secrets (metadata only — no values)
entries = sirr.list()

# Prune expired secrets
pruned = sirr.prune()

# Check server health
alive = sirr.health()  # True / False

Sealed Secrets

A secret with reads=N, delete=False is sealed once the read limit is exhausted — it stays on the server but can no longer be read. get() will raise SirrSealed (a subclass of SirrError) in this case. Use head() to inspect a sealed secret without triggering an error:

from sirr import SirrSealed

sirr.set("AUDIT_KEY", value, org="acme", reads=3, delete=False)

try:
    value = sirr.get("AUDIT_KEY", org="acme")
except SirrSealed:
    meta = sirr.head("AUDIT_KEY")
    print(f"sealed — read {meta.read_count} times, created at {meta.created_at}")

Async

from sirr import AsyncSirrClient, SecretExistsError

async with AsyncSirrClient(server=..., token=...) as sirr:
    # Push a dead drop
    result = await sirr.push("sk-...", reads=1, ttl=3600)
    value = await sirr.get(result.id)

    # Set an org-scoped secret
    await sirr.set("API_KEY", "sk-...", org="acme", reads=1)
    value = await sirr.get("API_KEY", org="acme")
    meta = await sirr.head("API_KEY")

AI Workflows

LangChain tool with scoped credential

from langchain.tools import tool

@tool
def query_production_db(sql: str) -> str:
    """Run a SQL query against the production database."""
    conn_str = sirr.get("AGENT_DB")
    if conn_str is None:
        raise ValueError("DB credential expired or already used")
    return run_query(conn_str, sql)

CrewAI agent with burn-after-use credential

from crewai import Agent, Task, Crew

# Set before the crew runs — burns after first read
sirr.set("STRIPE_KEY", stripe_key, org="acme", reads=1, ttl=600)

analyst = Agent(
    role="Data Analyst",
    goal="Fetch and analyze payment data",
    tools=[stripe_tool],  # tool calls sirr.get("STRIPE_KEY", org="acme") internally
)

crew = Crew(agents=[analyst], tasks=[analysis_task])
crew.kickoff()
# STRIPE_KEY was read once by the tool — already deleted

AutoGen multi-agent with isolated credentials

import autogen

# Each agent gets its own scoped, expiring credential
sirr.set("AGENT_1_DB", db_url_1, org="acme", reads=5, ttl=3600)
sirr.set("AGENT_2_DB", db_url_2, org="acme", reads=5, ttl=3600)

# Agents run — their credential budgets are enforced server-side
# No agent can exceed its read limit even if the framework retries

Inject all secrets into a subprocess

with sirr.env():
    # All Sirr secrets set as os.environ
    subprocess.run(["python", "agent_script.py"])
# Env restored after block

pytest fixture for CI secrets

import pytest
from sirr import SirrClient

@pytest.fixture(autouse=True)
def inject_test_secrets():
    sirr = SirrClient(server=os.environ["SIRR_SERVER"], token=os.environ["SIRR_TOKEN"])
    with sirr.env():
        yield
    # Credentials cleaned from env after each test

Multi-Tenant / Org Mode

When running against a multi-tenant Sirr server, pass the org parameter to scope all secret, audit, webhook, and prune operations to that organization:

from sirr import SirrClient

sirr = SirrClient(
    server="https://sirr.sirrlock.com",
    token=os.environ["SIRR_TOKEN"],
)

# Org is now per-call on set() and get()
sirr.set("DB_URL", "postgres://...", org="acme", reads=3)
value = sirr.get("DB_URL", org="acme")

# Audit, list, and webhook calls still support org at the client level
sirr_acme = SirrClient(server="https://sirr.sirrlock.com", token=..., org="acme")
events = sirr_acme.get_audit_log()

/me endpoints

Inspect or update the currently authenticated principal:

info = sirr.me()                          # GET /me → MeInfo
sirr.update_me(metadata={"env": "prod"}) # PATCH /me → MeInfo
key = sirr.create_key("ci")              # POST /me/keys → ApiKeyCreateResult
print(key.key)                            # sirr_key_... (shown once)
sirr.delete_key(key.id)                  # DELETE /me/keys/:id

Admin endpoints

Manage orgs, principals, and roles (requires admin privileges):

# Orgs
org = sirr.create_org("Acme", metadata={"env": "prod"})
orgs = sirr.list_orgs()
sirr.delete_org(org.id)

# Principals
p = sirr.create_principal(org.id, "alice", "writer")
principals = sirr.list_principals(org.id)
sirr.delete_principal(org.id, p.id)

# Roles
role = sirr.create_role(org.id, "writer", permissions="rRlL")
roles = sirr.list_roles(org.id)
sirr.delete_role(org.id, role.name)

Permission strings use single letters: r read-own, R read-org, l list-own, L list-org, c create, C create-on-behalf, p patch-own, P patch-org, a account-read, A account-read-org, m account-manage, M manage-org, S sirr-admin, d delete-own, D delete-org.

Async

All multi-tenant, /me, and admin methods are also available on AsyncSirrClient:

from sirr import AsyncSirrClient

async with AsyncSirrClient(server=..., token=...) as sirr:
    await sirr.set("KEY", "value", org="acme")
    info = await sirr.me()
    org = await sirr.create_org("NewOrg")
    p = await sirr.create_principal(org.id, "bob", "reader")

Related

Package Description
sirr Rust monorepo: sirrd server + sirr CLI
@sirrlock/mcp MCP server for AI assistants
@sirrlock/node Node.js / TypeScript SDK
Sirr.Client (NuGet) .NET SDK
sirr.dev Documentation
sirrlock.com Managed cloud + license keys

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

sirr-1.0.20.tar.gz (11.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

sirr-1.0.20-py3-none-any.whl (11.1 kB view details)

Uploaded Python 3

File details

Details for the file sirr-1.0.20.tar.gz.

File metadata

  • Download URL: sirr-1.0.20.tar.gz
  • Upload date:
  • Size: 11.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for sirr-1.0.20.tar.gz
Algorithm Hash digest
SHA256 c6aa1034d5b304a368b1bad5038bb22f19dc571687b382ce0e1c7c96126e02e3
MD5 96389226618509fdfbac8a6056191be7
BLAKE2b-256 095314b0cb379a488aad93a3faa617a009d9596e191b261406517772f5a7e959

See more details on using hashes here.

Provenance

The following attestation bundles were made for sirr-1.0.20.tar.gz:

Publisher: ci.yml on sirrlock/python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file sirr-1.0.20-py3-none-any.whl.

File metadata

  • Download URL: sirr-1.0.20-py3-none-any.whl
  • Upload date:
  • Size: 11.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for sirr-1.0.20-py3-none-any.whl
Algorithm Hash digest
SHA256 613db47bd6cedde8b45557bc07acce93e8b286bd9b209eb2ba343cdcd959d134
MD5 925378542a50397e9db706b064902ee1
BLAKE2b-256 932073566493daa29097fd216b89b9efe2becc7d871c56355f1ad2a6dde87dcc

See more details on using hashes here.

Provenance

The following attestation bundles were made for sirr-1.0.20-py3-none-any.whl:

Publisher: ci.yml on sirrlock/python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page