Skip to main content

SwarmAuth

PyPI CI License: MIT Python

The zero-trust authorization standard for multi-agent systems.

SwarmAuth is OAuth 2.1 for autonomous AI swarms: cryptographically signed, short-lived (≤300s), capability-scoped delegation tokens for agent-to-agent and agent-to-tool calls. It's built on one assumption you should already hold — your LLM layer will be compromised by prompt injection — and asks a different question: when that happens, does the execution boundary stop the damage, or does it just trust whatever the model decided?

Today, most multi-agent stacks pass raw API keys or unscoped bearer tokens between agents. Read the full threat model and protocol details in SPEC.md.

UCAN, Biscuit, and macaroons are the tools for open-ended multi-hop attenuation. MCP authorization and agent-to-agent protocols are where session and transport auth are heading. SwarmAuth is the check in front of the tool: one Ed25519 algorithm, a 300-second ceiling the verifier enforces, an audience bound to that tool, and call, budget, and parameter limits that fail closed. A worker can narrow a grant once, when the parent token names them. The private key and the grant policy stay in a process the model cannot call.

SwarmAuth has no opinion about what your agents do — a capability is just a string your policy defines, and @guard() wraps any Python callable. The examples below use a finance payout because unauthorized money movement makes the stakes obvious, but the same call protects any tool an LLM can decide to invoke: a support agent issuing a refund, a DevOps agent running an infra command, a coding agent merging or deploying, a research agent spending against a paid API, a healthcare agent writing to a record, an ETL agent triggering a pipeline run — anywhere an autonomous decision meets an action with real consequences.

Why

  • Prompt injection is not a solved problem, and won't be soon. SwarmAuth doesn't try to detect or prevent it — it makes the LLM's decision irrelevant at the point of execution.
  • Capability tokens, not credentials. A token names exactly what it authorizes (capabilities), against whom (sub), and under what limits (constraints: rate limits, call caps, budget caps, parameter allowlists) — never a raw, reusable key.
  • Tokens expire in seconds. Max TTL is 300 seconds, enforced by every verifier regardless of what an issuer tries to claim.
  • Zero exotic dependencies. Ed25519 via cryptography, schema via pydantic. That's the whole dependency tree — KeyRegistry needs neither, and RedisUsageTracker is an opt-in extra, not baked into the core.
  • Sub-millisecond verification. See benchmark results below.
  • Built for more than two agents. KeyRegistry trusts many issuers at once and rotates keys without a hard cutover; RedisUsageTracker enforces rate/budget/call limits atomically across every verifier process in the swarm, not just one.

Quickstart

The signing key and the grant live in the issuer process. The tool process holds the public key, a usage tracker, and the guard. The model never sees the token; the runtime attaches it with use_token before the framework invokes the tool.

import swarmauth

issuer_keys = swarmauth.KeyPair.generate()
policy = swarmauth.IssuerPolicy([
    swarmauth.Grant(
        iss="agent:requester-01",
        sub="tool:process_payout",
        capabilities=("tool:process_payout",),
        max_ttl_seconds=60,
        max_calls=1,
        max_amount_usd=1000.0,
    )
])
issuer = swarmauth.TokenIssuer(issuer_keys, policy=policy)
token = issuer.issue(
    iss="agent:requester-01",
    sub="tool:process_payout",
    capabilities=["tool:process_payout"],
    constraints=swarmauth.Constraints(max_calls=1, max_amount_usd=1000.0),
    ttl_seconds=60,
)

tracker = swarmauth.UsageTracker()

@swarmauth.guard(
    "tool:process_payout",
    issuer_public_key=issuer_keys.public_bytes,
    tracker=tracker,
    amount_kwarg="amount_usd",
)
def process_payout(destination_account: str, amount_usd: float):
    ...  # reached only with a verified, in-scope token

with swarmauth.use_token(token):
    process_payout(destination_account="acct_123", amount_usd=50.0)

issuer.issue(...) raises PolicyViolationError for any capability, audience, ttl, or ceiling outside that grant. @guard binds the audience to the capability string unless you pass audience=. A token that declares max_calls, max_amount_usd, or rate_limit_per_min is rejected when no tracker is passed. examples/execution_boundary.py runs this shape, including one delegation hop.

Framework adapters for LangChain, CrewAI, AutoGen, and MCP tools are one call each — see swarmauth/middleware.py (secure_langchain_tool, secure_crewai_tool, secure_autogen_function, secure_mcp_tool). use_token is what makes tool.invoke(...) succeed; calling .func(..., token=...) is only the direct path. The adapters are verified against real LangChain, ag2, and MCP installs in tests/test_framework_adapters.py.

Scaling out: many issuers and many verifier processes

The examples above assume one issuer whose public key a verifier already has in hand. A real swarm usually has many issuing agents and many verifier processes, which needs two more pieces:

KeyRegistry — trust many issuers, and rotate an issuer's key without a hard cutover (SPEC.md §8):

from swarmauth.registry import KeyRegistry

registry = KeyRegistry()
registry.register("agent:issuer-a", keys_a.public_bytes)
registry.register("agent:issuer-b", keys_b.public_bytes)

# Rotate issuer-a's key later without invalidating in-flight tokens:
registry.register("agent:issuer-a", new_keys_a.public_bytes, rotate=True)
registry.revoke("agent:issuer-a", keys_a.public_bytes)  # once fully rolled over

@swarmauth.guard("tool:process_payout", key_registry=registry)
def process_payout(destination_account: str, amount_usd: float):
    ...

RedisUsageTracker — enforce max_calls/max_amount_usd/rate_limit_per_min atomically across multiple verifier processes instead of per-process (SPEC.md §9):

import redis
from swarmauth.backends.redis_backend import RedisUsageTracker

tracker = RedisUsageTracker(redis.Redis.from_url("redis://localhost:6379/0"))

@swarmauth.guard("tool:process_payout", key_registry=registry, tracker=tracker)
def process_payout(destination_account: str, amount_usd: float):
    ...

RevocationStore — reject one specific, otherwise-valid token before its signed expiry, for incident response or agent/session offboarding (SPEC.md §9):

from swarmauth.revocation import InMemoryRevocationStore

revocation_store = InMemoryRevocationStore()

@swarmauth.guard("tool:process_payout", key_registry=registry, revocation_store=revocation_store)
def process_payout(destination_account: str, amount_usd: float):
    ...

# Compromised session detected -- reject its token immediately, without
# waiting out the rest of its (short) TTL:
revocation_store.revoke(claims.jti, expires_at=claims.exp)

Use swarmauth.backends.redis_backend.RedisRevocationStore in place of InMemoryRevocationStore to share revocations across multiple verifier processes, the same way RedisUsageTracker shares usage state.

All three are optional and additive: issuer_public_key= and the default in-memory UsageTracker still work exactly as in the quickstart above.

Install

pip install swarmauth
npm install swarmauth

The JavaScript package lives in js/ and documents the same protocol in its README.

Or from a clone, for local development:

pip install -e .

For running the full test suite, including the real-framework adapter tests and the Redis backend tests (which run against fakeredis by default, no server required):

pip install -e ".[dev,frameworks,redis]"
pytest

Architecture

sequenceDiagram
    participant A as Agent A (Requester)
    participant I as SwarmAuth Token Issuer
    participant B as Agent B / Tool (Capability Owner)

    A->>I: Request capability token (iss=A, sub=B, capabilities=[...], constraints, ttl<=300s)
    I->>I: Evaluate policy — is A allowed to request these capabilities against B?
    I-->>A: Signed JSON Capability Token (JCT)
    A->>B: Tool call, with JCT attached
    B->>B: Verify Ed25519 signature, exp/iat window, audience, capability, constraints
    alt Valid and in scope
        B->>B: Execute tool, record usage against jti
        B-->>A: Result
    else Invalid, expired, wrong audience, missing capability, or over limit
        B-->>A: Reject (CapabilityViolationError, ConstraintViolationError, etc)
    end

Full claims schema, canonicalization rules, and the complete verification algorithm are in SPEC.md.

Implementing JCT in another language? spec/test-vectors/ gives byte-exact signed tokens and expected outcomes (including the temporal/expiry edge cases) so you can verify your implementation matches this one without needing to ask. This Python SDK enforces itself against the same file on every test run.

SwarmBench: does this actually stop anything?

benchmarks/swarmbench.py runs a runnable simulation of a Requesting Agent that delegates to a Payments Tool's process_payout tool, with a prompt injection embedded in an inbound customer message instructing the Requesting Agent to trigger a $50,000 payout to an attacker-controlled account. The injection is modeled as succeeding at the LLM layer in both test cases — SwarmBench isn't testing whether models can be fooled (they can); it's testing what happens next.

This particular scenario is a payout because a wrongly-executed $50,000 transfer is a visceral, easy-to-grade failure — the mechanism it exercises (capability check + constraint check at the execution boundary, before any side effect happens) is identical regardless of what the tool does. Swap process_payout for issue_refund, run_shell_command, merge_pull_request, send_email, or write_patient_record and the boundary behaves the same way, because @guard() never inspects what the wrapped function does.

python benchmarks/swarmbench.py

Results

SwarmBench does not call a model. compromised_llm_call is a fixed function, so the table below is a scripted demonstration of the boundary, regenerated by python benchmarks/swarmbench.py. The protected case also refuses to mint tool:process_payout: the issuer policy only allows tool:draft_payout. examples/execution_boundary.py is the deployment shape to copy.

Scenario Injection succeeds at LLM layer Unauthorized payout executed Blocked at execution boundary
Unprotected agent loop ✅ Yes ❌ Yes — $50,000 sent —
SwarmAuth-protected agent loop ✅ Yes ✅ No ✅ Yes (CapabilityViolationError)
Metric Value
Mean token verification overhead 0.12 ms/op (target: <1ms)

In the unprotected loop, the Payments Tool trusts whatever arguments it's called with — the injection walks straight through. In the protected loop, the token the Requesting Agent actually holds only grants tool:draft_payout under a policy-set budget; it does not, and cannot, grant tool:process_payout no matter what the compromised LLM decided to call — so the execution boundary rejects it before the ledger is touched.

Repository layout

swarmauth/
├── SPEC.md                        # protocol specification
├── README.md
├── CONTRIBUTING.md
├── SECURITY.md
├── tox.ini                          # reproduce every CI job locally: tox -e lint / py312 / frameworks / redis-live
├── .github/workflows/ci.yml        # tests + benchmark + live-Redis job on every push/PR
├── scripts/
│   ├── audit_deps.py                # pip-audit wrapper used by CI's `lint` job and `tox -e lint`
│   ├── generate_test_vectors.py      # regenerates spec/test-vectors/vectors.json
│   └── verify_release_artifact.py    # required before tagging a release -- see CONTRIBUTING.md
├── spec/
│   └── test-vectors/                # byte-exact cross-language interop vectors; see its own README
├── swarmauth/
│   ├── __init__.py
│   ├── crypto.py                   # Ed25519 keypairs, signing, verification
│   ├── token.py                     # CapabilityToken: issue / parse / verify
│   ├── registry.py                   # KeyRegistry: multi-issuer trust + key rotation
│   ├── policy.py                    # IssuerPolicy: what the signing key is allowed to mint
│   ├── middleware.py                  # guard decorator, use_token, framework adapters
│   ├── revocation.py                   # RevocationStore + InMemoryRevocationStore
│   ├── backends/
│   │   └── redis_backend.py            # RedisUsageTracker, RedisRevocationStore (optional `redis` extra)
│   ├── exceptions.py
│   └── py.typed
├── examples/
│   └── execution_boundary.py        # policy, runtime token, guard, one delegation hop
├── js/                              # JavaScript SDK, verified against spec/test-vectors
├── benchmarks/
│   └── swarmbench.py                # runnable unprotected-vs-protected simulation
└── tests/
    ├── test_token.py
    ├── test_middleware.py
    ├── test_registry.py
    ├── test_revocation.py
    ├── test_public_api.py             # every swarmauth.exceptions.* class must be exported at top level
    ├── test_spec_vectors.py            # enforces spec/test-vectors/vectors.json against this implementation
    ├── test_redis_backend.py         # runs against fakeredis, or a real server in CI
    └── test_framework_adapters.py    # real LangChain, ag2, and MCP integration tests

Design principles

  • No cryptographic dependencies beyond cryptography; no schema/validation dependencies beyond pydantic.
  • Strict typing, explicit exception hierarchy (TokenExpiredError, CapabilityViolationError, ConstraintViolationError, ...) — callers decide how to handle each failure mode, nothing fails silently.
  • Tokens are data, not infrastructure: no required network call, no mandatory central service. Self-issuance and centralized-issuer deployments use the exact same token format and verification code.

Status

MVP / RFC. The token format, claims schema, and verification algorithm are considered stable for 0.x. Multi-issuer key rotation (KeyRegistry, SPEC.md §8), token revocation (RevocationStore, SPEC.md §9), distributed usage tracking (RedisUsageTracker, SPEC.md §10), issuer grants (IssuerPolicy), runtime token attachment (use_token), and one-hop attenuation (SPEC.md §12) ship in the SDK. Expect the framework adapters to keep evolving as more of them get real integration use. Issues and PRs welcome.

License

MIT — see LICENSE.

Metadata

Release files for swarmauth 0.1.7

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for swarmauth 0.1.7
File Size Uploaded Provenance Actions
swarmauth-0.1.7.tar.gz 45.9 kB Without provenance Details for swarmauth-0.1.7.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for swarmauth 0.1.7
File Interpreter ABI Platform Actions
swarmauth-0.1.7-py3-none-any.whl Python 3 none any Details for swarmauth-0.1.7-py3-none-any.whl

Total release size: 76.8 kB

Release files / swarmauth-0.1.7.tar.gz

Download URL swarmauth-0.1.7.tar.gz
Size 45.9 kB
Tags Source
SHA-256 checksum
How to use checksums
6ab7247f92ddef3ce19b7ae631f6954974450402cb1e6ef336f357f66c39774b
BLAKE2b-256 checksum
How to use checksums
a3a11227b9c8b6b80d78337bffe8d146c1d353d9f06176d27ebf114aac646693
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.0

Release files / swarmauth-0.1.7-py3-none-any.whl

Download URL swarmauth-0.1.7-py3-none-any.whl
Size 30.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
044b14e737dbef57f11349dac7810c73e077f4e9ae48489a93aa84f9b46d66bd
BLAKE2b-256 checksum
How to use checksums
2bc9002492fa1d68954d8122dcd8e120ebf98ea73964489099a0bc7b74589f10
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.0

Security for swarmauth 0.1.7

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.7 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page