sirr 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sirr-0.1.0/.github/workflows/ci.yml +49 -0
- sirr-0.1.0/.gitignore +15 -0
- sirr-0.1.0/CLAUDE.md +84 -0
- sirr-0.1.0/LICENSE +21 -0
- sirr-0.1.0/PKG-INFO +203 -0
- sirr-0.1.0/README.md +173 -0
- sirr-0.1.0/pyproject.toml +54 -0
- sirr-0.1.0/src/sirr/__init__.py +25 -0
- sirr-0.1.0/src/sirr/_async_client.py +167 -0
- sirr-0.1.0/src/sirr/_client.py +165 -0
- sirr-0.1.0/src/sirr/_exceptions.py +10 -0
- sirr-0.1.0/src/sirr/_models.py +122 -0
- sirr-0.1.0/src/sirr/_transport.py +32 -0
- sirr-0.1.0/src/sirr/py.typed +0 -0
- sirr-0.1.0/tests/conftest.py +27 -0
- sirr-0.1.0/tests/test_async_client.py +107 -0
- sirr-0.1.0/tests/test_client.py +182 -0
- sirr-0.1.0/tests/test_models.py +40 -0
- sirr-0.1.0/tests/test_new_async_methods.py +105 -0
- sirr-0.1.0/tests/test_new_methods.py +121 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
tags: ["v*"]
|
|
7
|
+
pull_request:
|
|
8
|
+
branches: [main]
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
lint:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
- uses: actions/setup-python@v5
|
|
16
|
+
with:
|
|
17
|
+
python-version: "3.13"
|
|
18
|
+
- run: pip install ruff
|
|
19
|
+
- run: ruff check src/ tests/
|
|
20
|
+
- run: ruff format --check src/ tests/
|
|
21
|
+
|
|
22
|
+
test:
|
|
23
|
+
runs-on: ubuntu-latest
|
|
24
|
+
strategy:
|
|
25
|
+
matrix:
|
|
26
|
+
python-version: ["3.10", "3.11", "3.12", "3.13"]
|
|
27
|
+
steps:
|
|
28
|
+
- uses: actions/checkout@v4
|
|
29
|
+
- uses: actions/setup-python@v5
|
|
30
|
+
with:
|
|
31
|
+
python-version: ${{ matrix.python-version }}
|
|
32
|
+
- run: pip install -e ".[dev]"
|
|
33
|
+
- run: pytest --cov=sirr --cov-report=term-missing
|
|
34
|
+
|
|
35
|
+
publish:
|
|
36
|
+
needs: [lint, test]
|
|
37
|
+
runs-on: ubuntu-latest
|
|
38
|
+
if: startsWith(github.ref, 'refs/tags/v')
|
|
39
|
+
permissions:
|
|
40
|
+
id-token: write
|
|
41
|
+
environment: release
|
|
42
|
+
steps:
|
|
43
|
+
- uses: actions/checkout@v4
|
|
44
|
+
- uses: actions/setup-python@v5
|
|
45
|
+
with:
|
|
46
|
+
python-version: "3.13"
|
|
47
|
+
- run: pip install build
|
|
48
|
+
- run: python -m build
|
|
49
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
sirr-0.1.0/.gitignore
ADDED
sirr-0.1.0/CLAUDE.md
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# sirr Python Client — Claude Development Guide
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
Python HTTP client for the Sirr API. Published to PyPI as `sirr`.
|
|
6
|
+
Supports both sync and async usage.
|
|
7
|
+
|
|
8
|
+
## Architecture
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
src/sirr/
|
|
12
|
+
├── __init__.py # Re-exports: SirrClient, AsyncSirrClient, SecretMeta, SirrError
|
|
13
|
+
├── _exceptions.py # SirrError(status, message)
|
|
14
|
+
├── _models.py # SecretMeta frozen dataclass with from_dict()
|
|
15
|
+
├── _transport.py # Shared: build_headers, normalize_server, handle_response
|
|
16
|
+
├── _client.py # SirrClient (sync, httpx.Client)
|
|
17
|
+
├── _async_client.py # AsyncSirrClient (async, httpx.AsyncClient)
|
|
18
|
+
└── py.typed # PEP 561 marker
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
**DRY strategy**: `_transport.py` handles headers, URL normalization, and response parsing.
|
|
22
|
+
Both clients call into it. No base class — the duplication across 6 methods is minimal.
|
|
23
|
+
|
|
24
|
+
## API Surface
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
class SirrClient:
|
|
28
|
+
def __init__(self, server: str, token: str): ...
|
|
29
|
+
|
|
30
|
+
# Sync
|
|
31
|
+
def push(self, key: str, value: str, *, ttl: int | None = None, reads: int | None = None) -> None
|
|
32
|
+
def get(self, key: str) -> str | None # None if burned/expired
|
|
33
|
+
def delete(self, key: str) -> None
|
|
34
|
+
def list(self) -> list[SecretMeta]
|
|
35
|
+
def pull_all(self) -> dict[str, str]
|
|
36
|
+
def prune(self) -> int
|
|
37
|
+
def env(self) -> ContextManager # injects into os.environ
|
|
38
|
+
|
|
39
|
+
class AsyncSirrClient:
|
|
40
|
+
# Same surface but all methods are async
|
|
41
|
+
# pull_all() uses asyncio.gather for concurrent fetches
|
|
42
|
+
# env() is an @asynccontextmanager
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Stack
|
|
46
|
+
|
|
47
|
+
- Python 3.10+
|
|
48
|
+
- `httpx` for HTTP (supports both sync and async)
|
|
49
|
+
- `pytest` + `pytest-asyncio` + `respx` for tests
|
|
50
|
+
- `ruff` for linting and formatting
|
|
51
|
+
- `hatchling` build backend
|
|
52
|
+
- Published via `pyproject.toml` (no setup.py)
|
|
53
|
+
|
|
54
|
+
## Key Rules
|
|
55
|
+
|
|
56
|
+
- `get()` returns `None` on 404 — do not raise
|
|
57
|
+
- All other non-2xx responses raise `SirrError`
|
|
58
|
+
- Never log secret values
|
|
59
|
+
- `env()` context manager must restore original env on exit (even on exception)
|
|
60
|
+
- Keys are URL-encoded in paths (`urllib.parse.quote(key, safe='')`)
|
|
61
|
+
|
|
62
|
+
## Commands
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
# Install (editable + dev deps)
|
|
66
|
+
pip install -e ".[dev]"
|
|
67
|
+
|
|
68
|
+
# Lint
|
|
69
|
+
ruff check src/ tests/
|
|
70
|
+
ruff format --check src/ tests/
|
|
71
|
+
|
|
72
|
+
# Test
|
|
73
|
+
pytest --cov=sirr --cov-report=term-missing
|
|
74
|
+
|
|
75
|
+
# Build
|
|
76
|
+
python -m build
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Pre-Commit Checklist
|
|
80
|
+
|
|
81
|
+
Before every commit and push, review and update if needed:
|
|
82
|
+
|
|
83
|
+
1. **README.md** — Does it reflect new methods or behavior?
|
|
84
|
+
2. **CLAUDE.md** — New constraints or API decisions worth recording?
|
sirr-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 sirrlock
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
sirr-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: sirr
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python client for the Sirr ephemeral secret vault
|
|
5
|
+
Project-URL: Homepage, https://github.com/sirrlock/python
|
|
6
|
+
Project-URL: Repository, https://github.com/sirrlock/python
|
|
7
|
+
Author: sirrlock
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: agents,ai,ephemeral,secrets,vault
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Typing :: Typed
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Requires-Dist: httpx<1,>=0.27
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: build; extra == 'dev'
|
|
24
|
+
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
|
|
25
|
+
Requires-Dist: pytest-cov>=6; extra == 'dev'
|
|
26
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
27
|
+
Requires-Dist: respx>=0.22; extra == 'dev'
|
|
28
|
+
Requires-Dist: ruff>=0.9; extra == 'dev'
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# sirr (Python)
|
|
32
|
+
|
|
33
|
+
[](https://github.com/sirrlock/python/actions/workflows/ci.yml)
|
|
34
|
+
[](https://pypi.org/project/sirr/)
|
|
35
|
+
[](https://pypi.org/project/sirr/)
|
|
36
|
+
[](LICENSE)
|
|
37
|
+
|
|
38
|
+
**Ephemeral secrets for Python AI agents. Credentials that delete themselves.**
|
|
39
|
+
|
|
40
|
+
`sirr` is the Python client for [Sirr](https://github.com/sirrlock/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.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## The Problem It Solves
|
|
45
|
+
|
|
46
|
+
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.
|
|
47
|
+
|
|
48
|
+
The standard answer — "rotate your secrets after every session" — doesn't scale. You forget. It's tedious. It requires manual IAM work.
|
|
49
|
+
|
|
50
|
+
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.
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
# Give a CrewAI agent exactly one read of your API key
|
|
54
|
+
sirr.push("OPENAI_KEY", api_key, reads=1, ttl=600)
|
|
55
|
+
|
|
56
|
+
# Agent calls sirr.get("OPENAI_KEY") → gets the value → record deleted
|
|
57
|
+
# Even if CrewAI logs the value to its trace file, it's already dead on your server
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Install
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
pip install sirr
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Requires Python 3.10+. Supports sync and async.
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Usage
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
import os
|
|
76
|
+
from sirr import SirrClient
|
|
77
|
+
|
|
78
|
+
sirr = SirrClient(
|
|
79
|
+
server=os.environ.get("SIRR_SERVER", "http://localhost:8080"),
|
|
80
|
+
token=os.environ["SIRR_TOKEN"],
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
# Push a one-time secret
|
|
84
|
+
sirr.push("API_KEY", "sk-...", reads=1, ttl=3600)
|
|
85
|
+
|
|
86
|
+
# Retrieve — None if burned or expired
|
|
87
|
+
value = sirr.get("API_KEY")
|
|
88
|
+
|
|
89
|
+
# Pull all secrets into a dict
|
|
90
|
+
secrets = sirr.pull_all()
|
|
91
|
+
# → {"API_KEY": "sk-...", "DB_URL": "postgres://..."}
|
|
92
|
+
|
|
93
|
+
# Inject as environment variables for the duration of a block
|
|
94
|
+
with sirr.env():
|
|
95
|
+
# os.environ["API_KEY"] is set here
|
|
96
|
+
run_agent_task()
|
|
97
|
+
# restored on exit, even on exception
|
|
98
|
+
|
|
99
|
+
# Delete immediately
|
|
100
|
+
sirr.delete("API_KEY")
|
|
101
|
+
|
|
102
|
+
# List active secrets (metadata only — no values)
|
|
103
|
+
entries = sirr.list()
|
|
104
|
+
|
|
105
|
+
# Prune expired secrets
|
|
106
|
+
pruned = sirr.prune()
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Async
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
from sirr import AsyncSirrClient
|
|
113
|
+
|
|
114
|
+
async with AsyncSirrClient(server=..., token=...) as sirr:
|
|
115
|
+
await sirr.push("API_KEY", "sk-...", reads=1, ttl=3600)
|
|
116
|
+
value = await sirr.get("API_KEY")
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## AI Workflows
|
|
122
|
+
|
|
123
|
+
### LangChain tool with scoped credential
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
from langchain.tools import tool
|
|
127
|
+
|
|
128
|
+
@tool
|
|
129
|
+
def query_production_db(sql: str) -> str:
|
|
130
|
+
"""Run a SQL query against the production database."""
|
|
131
|
+
conn_str = sirr.get("AGENT_DB")
|
|
132
|
+
if conn_str is None:
|
|
133
|
+
raise ValueError("DB credential expired or already used")
|
|
134
|
+
return run_query(conn_str, sql)
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### CrewAI agent with burn-after-use credential
|
|
138
|
+
|
|
139
|
+
```python
|
|
140
|
+
from crewai import Agent, Task, Crew
|
|
141
|
+
|
|
142
|
+
# Push before the crew runs — burns after first read
|
|
143
|
+
sirr.push("STRIPE_KEY", stripe_key, reads=1, ttl=600)
|
|
144
|
+
|
|
145
|
+
analyst = Agent(
|
|
146
|
+
role="Data Analyst",
|
|
147
|
+
goal="Fetch and analyze payment data",
|
|
148
|
+
tools=[stripe_tool], # tool calls sirr.get("STRIPE_KEY") internally
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
crew = Crew(agents=[analyst], tasks=[analysis_task])
|
|
152
|
+
crew.kickoff()
|
|
153
|
+
# STRIPE_KEY was read once by the tool — already deleted
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### AutoGen multi-agent with isolated credentials
|
|
157
|
+
|
|
158
|
+
```python
|
|
159
|
+
import autogen
|
|
160
|
+
|
|
161
|
+
# Each agent gets its own scoped, expiring credential
|
|
162
|
+
sirr.push("AGENT_1_DB", db_url_1, reads=5, ttl=3600)
|
|
163
|
+
sirr.push("AGENT_2_DB", db_url_2, reads=5, ttl=3600)
|
|
164
|
+
|
|
165
|
+
# Agents run — their credential budgets are enforced server-side
|
|
166
|
+
# No agent can exceed its read limit even if the framework retries
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Inject all secrets into a subprocess
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
with sirr.env():
|
|
173
|
+
# All Sirr secrets set as os.environ
|
|
174
|
+
subprocess.run(["python", "agent_script.py"])
|
|
175
|
+
# Env restored after block
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### pytest fixture for CI secrets
|
|
179
|
+
|
|
180
|
+
```python
|
|
181
|
+
import pytest
|
|
182
|
+
from sirr import SirrClient
|
|
183
|
+
|
|
184
|
+
@pytest.fixture(autouse=True)
|
|
185
|
+
def inject_test_secrets():
|
|
186
|
+
sirr = SirrClient(server=os.environ["SIRR_SERVER"], token=os.environ["SIRR_TOKEN"])
|
|
187
|
+
with sirr.env():
|
|
188
|
+
yield
|
|
189
|
+
# Credentials cleaned from env after each test
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## Related
|
|
195
|
+
|
|
196
|
+
| Package | Description |
|
|
197
|
+
|---------|-------------|
|
|
198
|
+
| [sirr](https://github.com/sirrlock/sirr) | Rust monorepo: `sirrd` server + `sirr` CLI |
|
|
199
|
+
| [@sirrlock/mcp](https://github.com/sirrlock/mcp) | MCP server for AI assistants |
|
|
200
|
+
| [@sirrlock/node](https://github.com/sirrlock/node) | Node.js / TypeScript SDK |
|
|
201
|
+
| [Sirr.Client (NuGet)](https://github.com/sirrlock/dotnet) | .NET SDK |
|
|
202
|
+
| [sirr.dev](https://sirr.dev) | Documentation |
|
|
203
|
+
| [sirrlock.com](https://sirrlock.com) | Managed cloud + license keys |
|
sirr-0.1.0/README.md
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# sirr (Python)
|
|
2
|
+
|
|
3
|
+
[](https://github.com/sirrlock/python/actions/workflows/ci.yml)
|
|
4
|
+
[](https://pypi.org/project/sirr/)
|
|
5
|
+
[](https://pypi.org/project/sirr/)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
|
|
8
|
+
**Ephemeral secrets for Python AI agents. Credentials that delete themselves.**
|
|
9
|
+
|
|
10
|
+
`sirr` is the Python client for [Sirr](https://github.com/sirrlock/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.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## The Problem It Solves
|
|
15
|
+
|
|
16
|
+
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.
|
|
17
|
+
|
|
18
|
+
The standard answer — "rotate your secrets after every session" — doesn't scale. You forget. It's tedious. It requires manual IAM work.
|
|
19
|
+
|
|
20
|
+
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.
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
# Give a CrewAI agent exactly one read of your API key
|
|
24
|
+
sirr.push("OPENAI_KEY", api_key, reads=1, ttl=600)
|
|
25
|
+
|
|
26
|
+
# Agent calls sirr.get("OPENAI_KEY") → gets the value → record deleted
|
|
27
|
+
# Even if CrewAI logs the value to its trace file, it's already dead on your server
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Install
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pip install sirr
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Requires Python 3.10+. Supports sync and async.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Usage
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
import os
|
|
46
|
+
from sirr import SirrClient
|
|
47
|
+
|
|
48
|
+
sirr = SirrClient(
|
|
49
|
+
server=os.environ.get("SIRR_SERVER", "http://localhost:8080"),
|
|
50
|
+
token=os.environ["SIRR_TOKEN"],
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
# Push a one-time secret
|
|
54
|
+
sirr.push("API_KEY", "sk-...", reads=1, ttl=3600)
|
|
55
|
+
|
|
56
|
+
# Retrieve — None if burned or expired
|
|
57
|
+
value = sirr.get("API_KEY")
|
|
58
|
+
|
|
59
|
+
# Pull all secrets into a dict
|
|
60
|
+
secrets = sirr.pull_all()
|
|
61
|
+
# → {"API_KEY": "sk-...", "DB_URL": "postgres://..."}
|
|
62
|
+
|
|
63
|
+
# Inject as environment variables for the duration of a block
|
|
64
|
+
with sirr.env():
|
|
65
|
+
# os.environ["API_KEY"] is set here
|
|
66
|
+
run_agent_task()
|
|
67
|
+
# restored on exit, even on exception
|
|
68
|
+
|
|
69
|
+
# Delete immediately
|
|
70
|
+
sirr.delete("API_KEY")
|
|
71
|
+
|
|
72
|
+
# List active secrets (metadata only — no values)
|
|
73
|
+
entries = sirr.list()
|
|
74
|
+
|
|
75
|
+
# Prune expired secrets
|
|
76
|
+
pruned = sirr.prune()
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Async
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
from sirr import AsyncSirrClient
|
|
83
|
+
|
|
84
|
+
async with AsyncSirrClient(server=..., token=...) as sirr:
|
|
85
|
+
await sirr.push("API_KEY", "sk-...", reads=1, ttl=3600)
|
|
86
|
+
value = await sirr.get("API_KEY")
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## AI Workflows
|
|
92
|
+
|
|
93
|
+
### LangChain tool with scoped credential
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
from langchain.tools import tool
|
|
97
|
+
|
|
98
|
+
@tool
|
|
99
|
+
def query_production_db(sql: str) -> str:
|
|
100
|
+
"""Run a SQL query against the production database."""
|
|
101
|
+
conn_str = sirr.get("AGENT_DB")
|
|
102
|
+
if conn_str is None:
|
|
103
|
+
raise ValueError("DB credential expired or already used")
|
|
104
|
+
return run_query(conn_str, sql)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### CrewAI agent with burn-after-use credential
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
from crewai import Agent, Task, Crew
|
|
111
|
+
|
|
112
|
+
# Push before the crew runs — burns after first read
|
|
113
|
+
sirr.push("STRIPE_KEY", stripe_key, reads=1, ttl=600)
|
|
114
|
+
|
|
115
|
+
analyst = Agent(
|
|
116
|
+
role="Data Analyst",
|
|
117
|
+
goal="Fetch and analyze payment data",
|
|
118
|
+
tools=[stripe_tool], # tool calls sirr.get("STRIPE_KEY") internally
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
crew = Crew(agents=[analyst], tasks=[analysis_task])
|
|
122
|
+
crew.kickoff()
|
|
123
|
+
# STRIPE_KEY was read once by the tool — already deleted
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### AutoGen multi-agent with isolated credentials
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
import autogen
|
|
130
|
+
|
|
131
|
+
# Each agent gets its own scoped, expiring credential
|
|
132
|
+
sirr.push("AGENT_1_DB", db_url_1, reads=5, ttl=3600)
|
|
133
|
+
sirr.push("AGENT_2_DB", db_url_2, reads=5, ttl=3600)
|
|
134
|
+
|
|
135
|
+
# Agents run — their credential budgets are enforced server-side
|
|
136
|
+
# No agent can exceed its read limit even if the framework retries
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Inject all secrets into a subprocess
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
with sirr.env():
|
|
143
|
+
# All Sirr secrets set as os.environ
|
|
144
|
+
subprocess.run(["python", "agent_script.py"])
|
|
145
|
+
# Env restored after block
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### pytest fixture for CI secrets
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
import pytest
|
|
152
|
+
from sirr import SirrClient
|
|
153
|
+
|
|
154
|
+
@pytest.fixture(autouse=True)
|
|
155
|
+
def inject_test_secrets():
|
|
156
|
+
sirr = SirrClient(server=os.environ["SIRR_SERVER"], token=os.environ["SIRR_TOKEN"])
|
|
157
|
+
with sirr.env():
|
|
158
|
+
yield
|
|
159
|
+
# Credentials cleaned from env after each test
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Related
|
|
165
|
+
|
|
166
|
+
| Package | Description |
|
|
167
|
+
|---------|-------------|
|
|
168
|
+
| [sirr](https://github.com/sirrlock/sirr) | Rust monorepo: `sirrd` server + `sirr` CLI |
|
|
169
|
+
| [@sirrlock/mcp](https://github.com/sirrlock/mcp) | MCP server for AI assistants |
|
|
170
|
+
| [@sirrlock/node](https://github.com/sirrlock/node) | Node.js / TypeScript SDK |
|
|
171
|
+
| [Sirr.Client (NuGet)](https://github.com/sirrlock/dotnet) | .NET SDK |
|
|
172
|
+
| [sirr.dev](https://sirr.dev) | Documentation |
|
|
173
|
+
| [sirrlock.com](https://sirrlock.com) | Managed cloud + license keys |
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "sirr"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Python client for the Sirr ephemeral secret vault"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
authors = [{ name = "sirrlock" }]
|
|
13
|
+
keywords = ["secrets", "vault", "ephemeral", "ai", "agents"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3.10",
|
|
20
|
+
"Programming Language :: Python :: 3.11",
|
|
21
|
+
"Programming Language :: Python :: 3.12",
|
|
22
|
+
"Programming Language :: Python :: 3.13",
|
|
23
|
+
"Typing :: Typed",
|
|
24
|
+
]
|
|
25
|
+
dependencies = ["httpx>=0.27,<1"]
|
|
26
|
+
|
|
27
|
+
[project.optional-dependencies]
|
|
28
|
+
dev = [
|
|
29
|
+
"pytest>=8",
|
|
30
|
+
"pytest-asyncio>=0.24",
|
|
31
|
+
"respx>=0.22",
|
|
32
|
+
"ruff>=0.9",
|
|
33
|
+
"pytest-cov>=6",
|
|
34
|
+
"build",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
[project.urls]
|
|
38
|
+
Homepage = "https://github.com/sirrlock/python"
|
|
39
|
+
Repository = "https://github.com/sirrlock/python"
|
|
40
|
+
|
|
41
|
+
[tool.hatch.build.targets.wheel]
|
|
42
|
+
packages = ["src/sirr"]
|
|
43
|
+
|
|
44
|
+
[tool.ruff]
|
|
45
|
+
target-version = "py310"
|
|
46
|
+
line-length = 100
|
|
47
|
+
src = ["src", "tests"]
|
|
48
|
+
|
|
49
|
+
[tool.ruff.lint]
|
|
50
|
+
select = ["E", "F", "I", "UP", "B", "SIM", "RUF"]
|
|
51
|
+
|
|
52
|
+
[tool.pytest.ini_options]
|
|
53
|
+
asyncio_mode = "auto"
|
|
54
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""sirr — Python client for the Sirr ephemeral secret vault."""
|
|
2
|
+
|
|
3
|
+
from sirr._async_client import AsyncSirrClient
|
|
4
|
+
from sirr._client import SirrClient
|
|
5
|
+
from sirr._exceptions import SirrError
|
|
6
|
+
from sirr._models import (
|
|
7
|
+
ApiKey,
|
|
8
|
+
ApiKeyCreateResult,
|
|
9
|
+
AuditEvent,
|
|
10
|
+
SecretMeta,
|
|
11
|
+
Webhook,
|
|
12
|
+
WebhookCreateResult,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"ApiKey",
|
|
17
|
+
"ApiKeyCreateResult",
|
|
18
|
+
"AsyncSirrClient",
|
|
19
|
+
"AuditEvent",
|
|
20
|
+
"SecretMeta",
|
|
21
|
+
"SirrClient",
|
|
22
|
+
"SirrError",
|
|
23
|
+
"Webhook",
|
|
24
|
+
"WebhookCreateResult",
|
|
25
|
+
]
|