swarmauth 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.
- swarmauth-0.1.0/LICENSE +21 -0
- swarmauth-0.1.0/PKG-INFO +257 -0
- swarmauth-0.1.0/README.md +234 -0
- swarmauth-0.1.0/pyproject.toml +35 -0
- swarmauth-0.1.0/setup.cfg +4 -0
- swarmauth-0.1.0/swarmauth/__init__.py +36 -0
- swarmauth-0.1.0/swarmauth/backends/__init__.py +8 -0
- swarmauth-0.1.0/swarmauth/backends/redis_backend.py +125 -0
- swarmauth-0.1.0/swarmauth/crypto.py +109 -0
- swarmauth-0.1.0/swarmauth/exceptions.py +55 -0
- swarmauth-0.1.0/swarmauth/middleware.py +298 -0
- swarmauth-0.1.0/swarmauth/py.typed +0 -0
- swarmauth-0.1.0/swarmauth/registry.py +80 -0
- swarmauth-0.1.0/swarmauth/token.py +231 -0
- swarmauth-0.1.0/swarmauth.egg-info/PKG-INFO +257 -0
- swarmauth-0.1.0/swarmauth.egg-info/SOURCES.txt +22 -0
- swarmauth-0.1.0/swarmauth.egg-info/dependency_links.txt +1 -0
- swarmauth-0.1.0/swarmauth.egg-info/requires.txt +14 -0
- swarmauth-0.1.0/swarmauth.egg-info/top_level.txt +1 -0
- swarmauth-0.1.0/tests/test_framework_adapters.py +224 -0
- swarmauth-0.1.0/tests/test_middleware.py +86 -0
- swarmauth-0.1.0/tests/test_redis_backend.py +164 -0
- swarmauth-0.1.0/tests/test_registry.py +121 -0
- swarmauth-0.1.0/tests/test_token.py +94 -0
swarmauth-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 SwarmAuth Contributors
|
|
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.
|
swarmauth-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: swarmauth
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: OAuth 2.1 for autonomous AI agent swarms: signed, short-lived, capability-scoped delegation tokens.
|
|
5
|
+
Author: SwarmAuth Contributors
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: ai-agents,security,authorization,multi-agent,llm,ed25519
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Requires-Dist: cryptography>=42.0
|
|
12
|
+
Requires-Dist: pydantic>=2.5
|
|
13
|
+
Provides-Extra: dev
|
|
14
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
15
|
+
Requires-Dist: fakeredis>=2.20; extra == "dev"
|
|
16
|
+
Provides-Extra: frameworks
|
|
17
|
+
Requires-Dist: langchain-core>=0.3; extra == "frameworks"
|
|
18
|
+
Requires-Dist: ag2>=1.0; extra == "frameworks"
|
|
19
|
+
Requires-Dist: mcp>=1.0; extra == "frameworks"
|
|
20
|
+
Provides-Extra: redis
|
|
21
|
+
Requires-Dist: redis>=5.0; extra == "redis"
|
|
22
|
+
Dynamic: license-file
|
|
23
|
+
|
|
24
|
+
# SwarmAuth
|
|
25
|
+
|
|
26
|
+
**The zero-trust authorization standard for multi-agent systems.**
|
|
27
|
+
|
|
28
|
+
SwarmAuth is OAuth 2.1 for autonomous AI swarms: cryptographically signed,
|
|
29
|
+
short-lived (≤300s), capability-scoped delegation tokens for agent-to-agent
|
|
30
|
+
and agent-to-tool calls. It's built on one assumption you should already
|
|
31
|
+
hold — your LLM layer *will* be compromised by prompt injection — and asks a
|
|
32
|
+
different question: when that happens, does the execution boundary stop the
|
|
33
|
+
damage, or does it just trust whatever the model decided?
|
|
34
|
+
|
|
35
|
+
Today, most multi-agent stacks pass raw API keys or unscoped bearer tokens
|
|
36
|
+
between agents. Read the full threat model and protocol details in
|
|
37
|
+
[SPEC.md](SPEC.md).
|
|
38
|
+
|
|
39
|
+
## Why
|
|
40
|
+
|
|
41
|
+
- **Prompt injection is not a solved problem, and won't be soon.** SwarmAuth
|
|
42
|
+
doesn't try to detect or prevent it — it makes the LLM's decision
|
|
43
|
+
irrelevant at the point of execution.
|
|
44
|
+
- **Capability tokens, not credentials.** A token names exactly what it
|
|
45
|
+
authorizes (`capabilities`), against whom (`sub`), and under what limits
|
|
46
|
+
(`constraints`: rate limits, call caps, budget caps, parameter
|
|
47
|
+
allowlists) — never a raw, reusable key.
|
|
48
|
+
- **Tokens expire in seconds.** Max TTL is 300 seconds, enforced by every
|
|
49
|
+
verifier regardless of what an issuer tries to claim.
|
|
50
|
+
- **Zero exotic dependencies.** Ed25519 via `cryptography`, schema via
|
|
51
|
+
`pydantic`. That's the whole dependency tree — `KeyRegistry` needs neither,
|
|
52
|
+
and `RedisUsageTracker` is an opt-in extra, not baked into the core.
|
|
53
|
+
- **Sub-millisecond verification.** See benchmark results below.
|
|
54
|
+
- **Built for more than two agents.** `KeyRegistry` trusts many issuers at
|
|
55
|
+
once and rotates keys without a hard cutover; `RedisUsageTracker`
|
|
56
|
+
enforces rate/budget/call limits atomically across every verifier
|
|
57
|
+
process in the swarm, not just one.
|
|
58
|
+
|
|
59
|
+
## Quickstart
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
import swarmauth
|
|
63
|
+
|
|
64
|
+
finance_keys = swarmauth.KeyPair.generate()
|
|
65
|
+
|
|
66
|
+
@swarmauth.guard("tool:process_payout", issuer_public_key=finance_keys.public_bytes)
|
|
67
|
+
def process_payout(destination_account: str, amount_usd: float):
|
|
68
|
+
... # your real tool logic — only ever reached with a verified, in-scope token
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Issuing a token that will actually pass that check is one line:
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
token = swarmauth.TokenIssuer(finance_keys).issue(
|
|
75
|
+
iss="agent:sales-agent-01", sub="tool:process_payout",
|
|
76
|
+
capabilities=["tool:process_payout"], ttl_seconds=60,
|
|
77
|
+
)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
That's the entire integration surface: `@swarmauth.guard` the tool, issue the
|
|
81
|
+
token. Framework adapters for LangChain, CrewAI, AutoGen, and MCP tools are
|
|
82
|
+
one call each — see [`swarmauth/middleware.py`](swarmauth/middleware.py)
|
|
83
|
+
(`secure_langchain_tool`, `secure_crewai_tool`, `secure_autogen_function`,
|
|
84
|
+
`secure_mcp_tool`), verified against real LangChain, ag2, and MCP installs in
|
|
85
|
+
[`tests/test_framework_adapters.py`](tests/test_framework_adapters.py).
|
|
86
|
+
|
|
87
|
+
## Scaling out: many issuers and many verifier processes
|
|
88
|
+
|
|
89
|
+
The examples above assume one issuer whose public key a verifier already
|
|
90
|
+
has in hand. A real swarm usually has many issuing agents and many verifier
|
|
91
|
+
processes, which needs two more pieces:
|
|
92
|
+
|
|
93
|
+
**`KeyRegistry`** — trust many issuers, and rotate an issuer's key without a
|
|
94
|
+
hard cutover ([SPEC.md §8](SPEC.md#8-key-registry-and-rotation)):
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
from swarmauth.registry import KeyRegistry
|
|
98
|
+
|
|
99
|
+
registry = KeyRegistry()
|
|
100
|
+
registry.register("agent:sales-agent-01", sales_keys.public_bytes)
|
|
101
|
+
registry.register("agent:finance-agent-01", finance_keys.public_bytes)
|
|
102
|
+
|
|
103
|
+
# Rotate sales-agent-01's key later without invalidating in-flight tokens:
|
|
104
|
+
registry.register("agent:sales-agent-01", new_sales_keys.public_bytes, rotate=True)
|
|
105
|
+
registry.revoke("agent:sales-agent-01", sales_keys.public_bytes) # once fully rolled over
|
|
106
|
+
|
|
107
|
+
@swarmauth.guard("tool:process_payout", key_registry=registry)
|
|
108
|
+
def process_payout(destination_account: str, amount_usd: float):
|
|
109
|
+
...
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
**`RedisUsageTracker`** — enforce `max_calls`/`max_amount_usd`/`rate_limit_per_min`
|
|
113
|
+
atomically across multiple verifier processes instead of per-process
|
|
114
|
+
([SPEC.md §9](SPEC.md#9-distributed-usage-tracking)):
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
import redis
|
|
118
|
+
from swarmauth.backends.redis_backend import RedisUsageTracker
|
|
119
|
+
|
|
120
|
+
tracker = RedisUsageTracker(redis.Redis.from_url("redis://localhost:6379/0"))
|
|
121
|
+
|
|
122
|
+
@swarmauth.guard("tool:process_payout", key_registry=registry, tracker=tracker)
|
|
123
|
+
def process_payout(destination_account: str, amount_usd: float):
|
|
124
|
+
...
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Both are optional and additive: `issuer_public_key=` and the default
|
|
128
|
+
in-memory `UsageTracker` still work exactly as in the quickstart above.
|
|
129
|
+
|
|
130
|
+
## Install
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
pip install -e .
|
|
134
|
+
# or, without an editable install:
|
|
135
|
+
pip install cryptography pydantic
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
(Not yet published to PyPI — this is the MVP/open-source launch. `pip
|
|
139
|
+
install -e .` from a clone works today.)
|
|
140
|
+
|
|
141
|
+
For running the full test suite, including the real-framework adapter tests
|
|
142
|
+
and the Redis backend tests (which run against `fakeredis` by default, no
|
|
143
|
+
server required):
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
pip install -e ".[dev,frameworks,redis]"
|
|
147
|
+
pytest
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Architecture
|
|
151
|
+
|
|
152
|
+
```mermaid
|
|
153
|
+
sequenceDiagram
|
|
154
|
+
participant A as Agent A (Sales Agent)
|
|
155
|
+
participant I as SwarmAuth Token Issuer
|
|
156
|
+
participant B as Agent B / Tool (Finance Agent)
|
|
157
|
+
|
|
158
|
+
A->>I: Request capability token (iss=A, sub=B, capabilities=[...], constraints, ttl<=300s)
|
|
159
|
+
I->>I: Evaluate policy — is A allowed to request these capabilities against B?
|
|
160
|
+
I-->>A: Signed JSON Capability Token (JCT)
|
|
161
|
+
A->>B: Tool call, with JCT attached
|
|
162
|
+
B->>B: Verify Ed25519 signature, exp/iat window, audience, capability, constraints
|
|
163
|
+
alt Valid and in scope
|
|
164
|
+
B->>B: Execute tool, record usage against jti
|
|
165
|
+
B-->>A: Result
|
|
166
|
+
else Invalid, expired, wrong audience, missing capability, or over limit
|
|
167
|
+
B-->>A: Reject (CapabilityViolationError, ConstraintViolationError, etc)
|
|
168
|
+
end
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Full claims schema, canonicalization rules, and the complete verification
|
|
172
|
+
algorithm are in [SPEC.md](SPEC.md).
|
|
173
|
+
|
|
174
|
+
## SwarmBench: does this actually stop anything?
|
|
175
|
+
|
|
176
|
+
[`benchmarks/swarmbench.py`](benchmarks/swarmbench.py) runs a runnable
|
|
177
|
+
simulation of a Sales Agent that delegates to a Finance Agent's
|
|
178
|
+
`process_payout` tool, with a prompt injection embedded in inbound customer
|
|
179
|
+
email instructing the Sales Agent to trigger a $50,000 payout to an
|
|
180
|
+
attacker-controlled account. The injection is modeled as **succeeding** at
|
|
181
|
+
the LLM layer in both test cases — SwarmBench isn't testing whether models
|
|
182
|
+
can be fooled (they can); it's testing what happens next.
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
python benchmarks/swarmbench.py
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Results (from an actual run on this machine)
|
|
189
|
+
|
|
190
|
+
| Scenario | Injection succeeds at LLM layer | Unauthorized payout executed | Blocked at execution boundary |
|
|
191
|
+
|---|---|---|---|
|
|
192
|
+
| **Unprotected** agent loop | ✅ Yes | ❌ **Yes — $50,000 sent** | — |
|
|
193
|
+
| **SwarmAuth-protected** agent loop | ✅ Yes | ✅ **No** | ✅ Yes (`CapabilityViolationError`) |
|
|
194
|
+
|
|
195
|
+
| Metric | Value |
|
|
196
|
+
|---|---|
|
|
197
|
+
| Mean token verification overhead | **0.12 ms/op** (target: <1ms) |
|
|
198
|
+
|
|
199
|
+
In the unprotected loop, the Finance Agent's tool trusts whatever arguments
|
|
200
|
+
it's called with — the injection walks straight through. In the protected
|
|
201
|
+
loop, the token the Sales Agent actually holds only grants
|
|
202
|
+
`tool:draft_payout` under a policy-set budget; it does not, and cannot,
|
|
203
|
+
grant `tool:process_payout` no matter what the compromised LLM decided to
|
|
204
|
+
call — so the execution boundary rejects it before the ledger is touched.
|
|
205
|
+
|
|
206
|
+
## Repository layout
|
|
207
|
+
|
|
208
|
+
```
|
|
209
|
+
swarmauth/
|
|
210
|
+
├── SPEC.md # protocol specification
|
|
211
|
+
├── README.md
|
|
212
|
+
├── CONTRIBUTING.md
|
|
213
|
+
├── SECURITY.md
|
|
214
|
+
├── .github/workflows/ci.yml # tests + benchmark + live-Redis job on every push/PR
|
|
215
|
+
├── swarmauth/
|
|
216
|
+
│ ├── __init__.py
|
|
217
|
+
│ ├── crypto.py # Ed25519 keypairs, signing, verification
|
|
218
|
+
│ ├── token.py # CapabilityToken: issue / parse / verify
|
|
219
|
+
│ ├── registry.py # KeyRegistry: multi-issuer trust + key rotation
|
|
220
|
+
│ ├── middleware.py # guard decorator + framework adapters
|
|
221
|
+
│ ├── backends/
|
|
222
|
+
│ │ └── redis_backend.py # RedisUsageTracker (optional `redis` extra)
|
|
223
|
+
│ ├── exceptions.py
|
|
224
|
+
│ └── py.typed
|
|
225
|
+
├── benchmarks/
|
|
226
|
+
│ └── swarmbench.py # runnable unprotected-vs-protected simulation
|
|
227
|
+
└── tests/
|
|
228
|
+
├── test_token.py
|
|
229
|
+
├── test_middleware.py
|
|
230
|
+
├── test_registry.py
|
|
231
|
+
├── test_redis_backend.py # runs against fakeredis, or a real server in CI
|
|
232
|
+
└── test_framework_adapters.py # real LangChain, ag2, and MCP integration tests
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
## Design principles
|
|
236
|
+
|
|
237
|
+
- No cryptographic dependencies beyond `cryptography`; no schema/validation
|
|
238
|
+
dependencies beyond `pydantic`.
|
|
239
|
+
- Strict typing, explicit exception hierarchy (`TokenExpiredError`,
|
|
240
|
+
`CapabilityViolationError`, `ConstraintViolationError`, ...) — callers
|
|
241
|
+
decide how to handle each failure mode, nothing fails silently.
|
|
242
|
+
- Tokens are data, not infrastructure: no required network call, no
|
|
243
|
+
mandatory central service. Self-issuance and centralized-issuer
|
|
244
|
+
deployments use the exact same token format and verification code.
|
|
245
|
+
|
|
246
|
+
## Status
|
|
247
|
+
|
|
248
|
+
MVP / RFC. The token format, claims schema, and verification algorithm are
|
|
249
|
+
considered stable for `0.1.x`. Multi-issuer key rotation (`KeyRegistry`,
|
|
250
|
+
[SPEC.md §8](SPEC.md#8-key-registry-and-rotation)) and distributed usage
|
|
251
|
+
tracking (`RedisUsageTracker`, [SPEC.md §9](SPEC.md#9-distributed-usage-tracking))
|
|
252
|
+
now ship in the SDK; expect the framework adapters to keep evolving as more
|
|
253
|
+
of them get real integration use. Issues and PRs welcome.
|
|
254
|
+
|
|
255
|
+
## License
|
|
256
|
+
|
|
257
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
# SwarmAuth
|
|
2
|
+
|
|
3
|
+
**The zero-trust authorization standard for multi-agent systems.**
|
|
4
|
+
|
|
5
|
+
SwarmAuth is OAuth 2.1 for autonomous AI swarms: cryptographically signed,
|
|
6
|
+
short-lived (≤300s), capability-scoped delegation tokens for agent-to-agent
|
|
7
|
+
and agent-to-tool calls. It's built on one assumption you should already
|
|
8
|
+
hold — your LLM layer *will* be compromised by prompt injection — and asks a
|
|
9
|
+
different question: when that happens, does the execution boundary stop the
|
|
10
|
+
damage, or does it just trust whatever the model decided?
|
|
11
|
+
|
|
12
|
+
Today, most multi-agent stacks pass raw API keys or unscoped bearer tokens
|
|
13
|
+
between agents. Read the full threat model and protocol details in
|
|
14
|
+
[SPEC.md](SPEC.md).
|
|
15
|
+
|
|
16
|
+
## Why
|
|
17
|
+
|
|
18
|
+
- **Prompt injection is not a solved problem, and won't be soon.** SwarmAuth
|
|
19
|
+
doesn't try to detect or prevent it — it makes the LLM's decision
|
|
20
|
+
irrelevant at the point of execution.
|
|
21
|
+
- **Capability tokens, not credentials.** A token names exactly what it
|
|
22
|
+
authorizes (`capabilities`), against whom (`sub`), and under what limits
|
|
23
|
+
(`constraints`: rate limits, call caps, budget caps, parameter
|
|
24
|
+
allowlists) — never a raw, reusable key.
|
|
25
|
+
- **Tokens expire in seconds.** Max TTL is 300 seconds, enforced by every
|
|
26
|
+
verifier regardless of what an issuer tries to claim.
|
|
27
|
+
- **Zero exotic dependencies.** Ed25519 via `cryptography`, schema via
|
|
28
|
+
`pydantic`. That's the whole dependency tree — `KeyRegistry` needs neither,
|
|
29
|
+
and `RedisUsageTracker` is an opt-in extra, not baked into the core.
|
|
30
|
+
- **Sub-millisecond verification.** See benchmark results below.
|
|
31
|
+
- **Built for more than two agents.** `KeyRegistry` trusts many issuers at
|
|
32
|
+
once and rotates keys without a hard cutover; `RedisUsageTracker`
|
|
33
|
+
enforces rate/budget/call limits atomically across every verifier
|
|
34
|
+
process in the swarm, not just one.
|
|
35
|
+
|
|
36
|
+
## Quickstart
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
import swarmauth
|
|
40
|
+
|
|
41
|
+
finance_keys = swarmauth.KeyPair.generate()
|
|
42
|
+
|
|
43
|
+
@swarmauth.guard("tool:process_payout", issuer_public_key=finance_keys.public_bytes)
|
|
44
|
+
def process_payout(destination_account: str, amount_usd: float):
|
|
45
|
+
... # your real tool logic — only ever reached with a verified, in-scope token
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Issuing a token that will actually pass that check is one line:
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
token = swarmauth.TokenIssuer(finance_keys).issue(
|
|
52
|
+
iss="agent:sales-agent-01", sub="tool:process_payout",
|
|
53
|
+
capabilities=["tool:process_payout"], ttl_seconds=60,
|
|
54
|
+
)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
That's the entire integration surface: `@swarmauth.guard` the tool, issue the
|
|
58
|
+
token. Framework adapters for LangChain, CrewAI, AutoGen, and MCP tools are
|
|
59
|
+
one call each — see [`swarmauth/middleware.py`](swarmauth/middleware.py)
|
|
60
|
+
(`secure_langchain_tool`, `secure_crewai_tool`, `secure_autogen_function`,
|
|
61
|
+
`secure_mcp_tool`), verified against real LangChain, ag2, and MCP installs in
|
|
62
|
+
[`tests/test_framework_adapters.py`](tests/test_framework_adapters.py).
|
|
63
|
+
|
|
64
|
+
## Scaling out: many issuers and many verifier processes
|
|
65
|
+
|
|
66
|
+
The examples above assume one issuer whose public key a verifier already
|
|
67
|
+
has in hand. A real swarm usually has many issuing agents and many verifier
|
|
68
|
+
processes, which needs two more pieces:
|
|
69
|
+
|
|
70
|
+
**`KeyRegistry`** — trust many issuers, and rotate an issuer's key without a
|
|
71
|
+
hard cutover ([SPEC.md §8](SPEC.md#8-key-registry-and-rotation)):
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
from swarmauth.registry import KeyRegistry
|
|
75
|
+
|
|
76
|
+
registry = KeyRegistry()
|
|
77
|
+
registry.register("agent:sales-agent-01", sales_keys.public_bytes)
|
|
78
|
+
registry.register("agent:finance-agent-01", finance_keys.public_bytes)
|
|
79
|
+
|
|
80
|
+
# Rotate sales-agent-01's key later without invalidating in-flight tokens:
|
|
81
|
+
registry.register("agent:sales-agent-01", new_sales_keys.public_bytes, rotate=True)
|
|
82
|
+
registry.revoke("agent:sales-agent-01", sales_keys.public_bytes) # once fully rolled over
|
|
83
|
+
|
|
84
|
+
@swarmauth.guard("tool:process_payout", key_registry=registry)
|
|
85
|
+
def process_payout(destination_account: str, amount_usd: float):
|
|
86
|
+
...
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**`RedisUsageTracker`** — enforce `max_calls`/`max_amount_usd`/`rate_limit_per_min`
|
|
90
|
+
atomically across multiple verifier processes instead of per-process
|
|
91
|
+
([SPEC.md §9](SPEC.md#9-distributed-usage-tracking)):
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
import redis
|
|
95
|
+
from swarmauth.backends.redis_backend import RedisUsageTracker
|
|
96
|
+
|
|
97
|
+
tracker = RedisUsageTracker(redis.Redis.from_url("redis://localhost:6379/0"))
|
|
98
|
+
|
|
99
|
+
@swarmauth.guard("tool:process_payout", key_registry=registry, tracker=tracker)
|
|
100
|
+
def process_payout(destination_account: str, amount_usd: float):
|
|
101
|
+
...
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Both are optional and additive: `issuer_public_key=` and the default
|
|
105
|
+
in-memory `UsageTracker` still work exactly as in the quickstart above.
|
|
106
|
+
|
|
107
|
+
## Install
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
pip install -e .
|
|
111
|
+
# or, without an editable install:
|
|
112
|
+
pip install cryptography pydantic
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
(Not yet published to PyPI — this is the MVP/open-source launch. `pip
|
|
116
|
+
install -e .` from a clone works today.)
|
|
117
|
+
|
|
118
|
+
For running the full test suite, including the real-framework adapter tests
|
|
119
|
+
and the Redis backend tests (which run against `fakeredis` by default, no
|
|
120
|
+
server required):
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
pip install -e ".[dev,frameworks,redis]"
|
|
124
|
+
pytest
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Architecture
|
|
128
|
+
|
|
129
|
+
```mermaid
|
|
130
|
+
sequenceDiagram
|
|
131
|
+
participant A as Agent A (Sales Agent)
|
|
132
|
+
participant I as SwarmAuth Token Issuer
|
|
133
|
+
participant B as Agent B / Tool (Finance Agent)
|
|
134
|
+
|
|
135
|
+
A->>I: Request capability token (iss=A, sub=B, capabilities=[...], constraints, ttl<=300s)
|
|
136
|
+
I->>I: Evaluate policy — is A allowed to request these capabilities against B?
|
|
137
|
+
I-->>A: Signed JSON Capability Token (JCT)
|
|
138
|
+
A->>B: Tool call, with JCT attached
|
|
139
|
+
B->>B: Verify Ed25519 signature, exp/iat window, audience, capability, constraints
|
|
140
|
+
alt Valid and in scope
|
|
141
|
+
B->>B: Execute tool, record usage against jti
|
|
142
|
+
B-->>A: Result
|
|
143
|
+
else Invalid, expired, wrong audience, missing capability, or over limit
|
|
144
|
+
B-->>A: Reject (CapabilityViolationError, ConstraintViolationError, etc)
|
|
145
|
+
end
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Full claims schema, canonicalization rules, and the complete verification
|
|
149
|
+
algorithm are in [SPEC.md](SPEC.md).
|
|
150
|
+
|
|
151
|
+
## SwarmBench: does this actually stop anything?
|
|
152
|
+
|
|
153
|
+
[`benchmarks/swarmbench.py`](benchmarks/swarmbench.py) runs a runnable
|
|
154
|
+
simulation of a Sales Agent that delegates to a Finance Agent's
|
|
155
|
+
`process_payout` tool, with a prompt injection embedded in inbound customer
|
|
156
|
+
email instructing the Sales Agent to trigger a $50,000 payout to an
|
|
157
|
+
attacker-controlled account. The injection is modeled as **succeeding** at
|
|
158
|
+
the LLM layer in both test cases — SwarmBench isn't testing whether models
|
|
159
|
+
can be fooled (they can); it's testing what happens next.
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
python benchmarks/swarmbench.py
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Results (from an actual run on this machine)
|
|
166
|
+
|
|
167
|
+
| Scenario | Injection succeeds at LLM layer | Unauthorized payout executed | Blocked at execution boundary |
|
|
168
|
+
|---|---|---|---|
|
|
169
|
+
| **Unprotected** agent loop | ✅ Yes | ❌ **Yes — $50,000 sent** | — |
|
|
170
|
+
| **SwarmAuth-protected** agent loop | ✅ Yes | ✅ **No** | ✅ Yes (`CapabilityViolationError`) |
|
|
171
|
+
|
|
172
|
+
| Metric | Value |
|
|
173
|
+
|---|---|
|
|
174
|
+
| Mean token verification overhead | **0.12 ms/op** (target: <1ms) |
|
|
175
|
+
|
|
176
|
+
In the unprotected loop, the Finance Agent's tool trusts whatever arguments
|
|
177
|
+
it's called with — the injection walks straight through. In the protected
|
|
178
|
+
loop, the token the Sales Agent actually holds only grants
|
|
179
|
+
`tool:draft_payout` under a policy-set budget; it does not, and cannot,
|
|
180
|
+
grant `tool:process_payout` no matter what the compromised LLM decided to
|
|
181
|
+
call — so the execution boundary rejects it before the ledger is touched.
|
|
182
|
+
|
|
183
|
+
## Repository layout
|
|
184
|
+
|
|
185
|
+
```
|
|
186
|
+
swarmauth/
|
|
187
|
+
├── SPEC.md # protocol specification
|
|
188
|
+
├── README.md
|
|
189
|
+
├── CONTRIBUTING.md
|
|
190
|
+
├── SECURITY.md
|
|
191
|
+
├── .github/workflows/ci.yml # tests + benchmark + live-Redis job on every push/PR
|
|
192
|
+
├── swarmauth/
|
|
193
|
+
│ ├── __init__.py
|
|
194
|
+
│ ├── crypto.py # Ed25519 keypairs, signing, verification
|
|
195
|
+
│ ├── token.py # CapabilityToken: issue / parse / verify
|
|
196
|
+
│ ├── registry.py # KeyRegistry: multi-issuer trust + key rotation
|
|
197
|
+
│ ├── middleware.py # guard decorator + framework adapters
|
|
198
|
+
│ ├── backends/
|
|
199
|
+
│ │ └── redis_backend.py # RedisUsageTracker (optional `redis` extra)
|
|
200
|
+
│ ├── exceptions.py
|
|
201
|
+
│ └── py.typed
|
|
202
|
+
├── benchmarks/
|
|
203
|
+
│ └── swarmbench.py # runnable unprotected-vs-protected simulation
|
|
204
|
+
└── tests/
|
|
205
|
+
├── test_token.py
|
|
206
|
+
├── test_middleware.py
|
|
207
|
+
├── test_registry.py
|
|
208
|
+
├── test_redis_backend.py # runs against fakeredis, or a real server in CI
|
|
209
|
+
└── test_framework_adapters.py # real LangChain, ag2, and MCP integration tests
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## Design principles
|
|
213
|
+
|
|
214
|
+
- No cryptographic dependencies beyond `cryptography`; no schema/validation
|
|
215
|
+
dependencies beyond `pydantic`.
|
|
216
|
+
- Strict typing, explicit exception hierarchy (`TokenExpiredError`,
|
|
217
|
+
`CapabilityViolationError`, `ConstraintViolationError`, ...) — callers
|
|
218
|
+
decide how to handle each failure mode, nothing fails silently.
|
|
219
|
+
- Tokens are data, not infrastructure: no required network call, no
|
|
220
|
+
mandatory central service. Self-issuance and centralized-issuer
|
|
221
|
+
deployments use the exact same token format and verification code.
|
|
222
|
+
|
|
223
|
+
## Status
|
|
224
|
+
|
|
225
|
+
MVP / RFC. The token format, claims schema, and verification algorithm are
|
|
226
|
+
considered stable for `0.1.x`. Multi-issuer key rotation (`KeyRegistry`,
|
|
227
|
+
[SPEC.md §8](SPEC.md#8-key-registry-and-rotation)) and distributed usage
|
|
228
|
+
tracking (`RedisUsageTracker`, [SPEC.md §9](SPEC.md#9-distributed-usage-tracking))
|
|
229
|
+
now ship in the SDK; expect the framework adapters to keep evolving as more
|
|
230
|
+
of them get real integration use. Issues and PRs welcome.
|
|
231
|
+
|
|
232
|
+
## License
|
|
233
|
+
|
|
234
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "swarmauth"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "OAuth 2.1 for autonomous AI agent swarms: signed, short-lived, capability-scoped delegation tokens."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.10"
|
|
7
|
+
license = { text = "MIT" }
|
|
8
|
+
authors = [{ name = "SwarmAuth Contributors" }]
|
|
9
|
+
keywords = ["ai-agents", "security", "authorization", "multi-agent", "llm", "ed25519"]
|
|
10
|
+
dependencies = [
|
|
11
|
+
"cryptography>=42.0",
|
|
12
|
+
"pydantic>=2.5",
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
[project.optional-dependencies]
|
|
16
|
+
dev = ["pytest>=8.0", "fakeredis>=2.20"]
|
|
17
|
+
# Only needed to run tests/test_framework_adapters.py against real installs;
|
|
18
|
+
# not required to use swarmauth itself (see swarmauth/middleware.py).
|
|
19
|
+
frameworks = ["langchain-core>=0.3", "ag2>=1.0", "mcp>=1.0"]
|
|
20
|
+
# Only needed for swarmauth.backends.redis_backend.RedisUsageTracker; the
|
|
21
|
+
# default swarmauth.middleware.UsageTracker (in-memory) has no such dependency.
|
|
22
|
+
redis = ["redis>=5.0"]
|
|
23
|
+
|
|
24
|
+
[build-system]
|
|
25
|
+
requires = ["setuptools>=68", "wheel"]
|
|
26
|
+
build-backend = "setuptools.build_meta"
|
|
27
|
+
|
|
28
|
+
[tool.setuptools.packages.find]
|
|
29
|
+
include = ["swarmauth*"]
|
|
30
|
+
|
|
31
|
+
[tool.setuptools.package-data]
|
|
32
|
+
swarmauth = ["py.typed"]
|
|
33
|
+
|
|
34
|
+
[tool.pytest.ini_options]
|
|
35
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""SwarmAuth: OAuth 2.1-style capability delegation for autonomous AI agent swarms."""
|
|
2
|
+
|
|
3
|
+
from swarmauth.crypto import KeyPair
|
|
4
|
+
from swarmauth.exceptions import (
|
|
5
|
+
AudienceMismatchError,
|
|
6
|
+
CapabilityViolationError,
|
|
7
|
+
ConstraintViolationError,
|
|
8
|
+
InvalidSignatureError,
|
|
9
|
+
MalformedTokenError,
|
|
10
|
+
SwarmAuthError,
|
|
11
|
+
TokenExpiredError,
|
|
12
|
+
TokenNotYetValidError,
|
|
13
|
+
)
|
|
14
|
+
from swarmauth.middleware import TokenIssuer, guard
|
|
15
|
+
from swarmauth.token import CapabilityClaims, CapabilityToken, Constraints, check_capability
|
|
16
|
+
|
|
17
|
+
__version__ = "0.1.0"
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"KeyPair",
|
|
21
|
+
"TokenIssuer",
|
|
22
|
+
"guard",
|
|
23
|
+
"CapabilityToken",
|
|
24
|
+
"CapabilityClaims",
|
|
25
|
+
"Constraints",
|
|
26
|
+
"check_capability",
|
|
27
|
+
"SwarmAuthError",
|
|
28
|
+
"MalformedTokenError",
|
|
29
|
+
"InvalidSignatureError",
|
|
30
|
+
"TokenExpiredError",
|
|
31
|
+
"TokenNotYetValidError",
|
|
32
|
+
"CapabilityViolationError",
|
|
33
|
+
"ConstraintViolationError",
|
|
34
|
+
"AudienceMismatchError",
|
|
35
|
+
"__version__",
|
|
36
|
+
]
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""Optional distributed backends for usage tracking.
|
|
2
|
+
|
|
3
|
+
Nothing in this package is imported by `swarmauth.middleware` at module load
|
|
4
|
+
time -- `UsageTracker` (in-memory) remains the zero-dependency default.
|
|
5
|
+
Import from `swarmauth.backends.redis_backend` directly if you need a
|
|
6
|
+
distributed one; it requires the optional `redis` extra
|
|
7
|
+
(`pip install swarmauth[redis]`).
|
|
8
|
+
"""
|