Skip to main content

envoys

PyPI Python License

Cryptographic identity for AI agents. Ed25519 keypairs, RFC 9421 HTTP Message Signatures, self-resolving keyids.

Sign every HTTP request an agent makes, so the receiver can cryptographically verify which agent sent it — no shared secret, no central broker. Each agent hosts its own public key at a resolvable URL; verifiers fetch it and check the signature.

Python SDK, requires Python 3.10+. Mirrors @envoys/sdk (Node) — same surface, same wire format, byte-compatible against the envoys-rfc9421 §14 test vectors.

Install

pip install envoys

Quick start

Register an agent

import asyncio
from envoys import Envoys, RegisterOptions

async def main():
    client, result = await Envoys.register(RegisterOptions(
        account_key = "ak_...",        # from envoys.me dashboard
        name        = "scout",
    ))
    print(result.address)       # scout@your-handle.envoys.me
    print(result.private_key)   # PEM PKCS8 — store securely, shown once

asyncio.run(main())

Sign an outgoing HTTP request

from_env() reads the agent's credentials from environment variables — populate them from register's result above, or from your envoys.me dashboard.

from envoys import Envoys
import httpx

envoys = Envoys.from_env()   # ENVOYS_AGENT_KEY, ENVOYS_ADDRESS, ENVOYS_PUBLIC_KEY, ENVOYS_PRIVATE_KEY

body    = {"task": "summarize", "url": "https://example.com/doc"}
headers = envoys.sign_request("POST", "/api/task", body)

with httpx.Client() as http:
    r = http.post("https://other-agent.example/api/task",
                  headers={**headers, "Content-Type": "application/json"},
                  json=body)

Verify an incoming request

import asyncio
from envoys import Envoys, VerifyRequestOptions

async def verify(method, path, headers, body):
    result = await Envoys.verify_request(
        method, path, headers, body,
        VerifyRequestOptions(allowlist=["scout@trusted.envoys.me"]),
    )
    if not result.verified:
        return None, result.error
    return result.address, None

The verifier enforces component coverage per spec §5.5: signatures must cover @method and @path, plus content-digest whenever the request has a body — a signature that leaves the body uncovered is rejected even if cryptographically valid (digest-downgrade protection, since 0.2.0).

Optional: bind the signature to the target host (@authority)

headers = envoys.sign_request("POST", "/rpc", body, authority="receiver.example.com")

Covers RFC 9421 @authority so the signature is scoped to one receiving service — relayed to any other host it fails verification. The verifier reconstructs the value from VerifyRequestOptions(authority=...) (set this behind a proxy that rewrites Host) or the request's Host header. Opt-in: verifiers older than 0.2.0 reject signatures covering components they don't reconstruct. Spec v1.6.0 §4.2.

keyid resolution is SSRF-guarded (since 0.3.0)

The keyid is sender-controlled, so resolving it is an untrusted outbound request. The verifier (verify_request, verify_agent_card, and the resolve_* helpers) enforces, per spec v1.6.3 §5.4: https only, rejection of hosts that are or resolve to loopback/private/link-local (incl. the 169.254.169.254 cloud-metadata range)/CGNAT/non-global addresses, no redirects, a 16 KB response cap, and a 5 s timeout. Override via ResolverGuardOptions:

from envoys import ResolverGuardOptions, VerifyRequestOptions

await Envoys.verify_request(method, path, headers, body, VerifyRequestOptions(
    resolver=ResolverGuardOptions(timeout_s=3.0, max_response_bytes=8192),
))
# Local testing only — re-enables private hosts / http:
await Envoys.resolve_key_from_keyid(
    url, ResolverGuardOptions(allow_private_hosts=True, allow_insecure_http=True),
)

DNS rebinding is closed too: resolution is pinned to the validated public address at connect time (a custom httpx transport resolves once, rejects any non-public address, and connects to that IP while preserving the original Host header and TLS SNI), so the address connected to is the address validated. For hard isolation, also constrain egress at the network layer.

Surface

Mirrors @envoys/sdk one-to-one with Python-idiomatic naming. Methods shown with await are coroutines and must be awaited; the rest are synchronous.

Operation Method
Register a new agent await Envoys.register(opts)
Construct from environment Envoys.from_env()
Key rotation sync await client.sync_keys()
Sign arbitrary payload client.sign(payload)
Sign HTTP request (RFC 9421) client.sign_request(method, path, body, *, tag=None)
Sign Agent Card (JWS EdDSA) client.sign_agent_card(card)
Verify HTTP request await Envoys.verify_request(method, path, headers, body, options)
Verify Agent Card await Envoys.verify_agent_card(jws)
Verify payload signature Envoys.verify(payload, signature, public_key)
Resolve public key by address await Envoys.resolve_public_key(address)
Resolve dual-shape keyid await Envoys.resolve_key_from_keyid(keyid_url)
Resolve did:web await Envoys.resolve_did_web(domain)
Reset pin Envoys.reset_pin(address)
Clear caches (testing) Envoys.clear_pins(), Envoys.clear_replay_cache(), Envoys.clear_key_cache()

Spec & conformance

The wire format is published at https://envoys.me/specs/signature/v1. Every operation in this SDK byte-matches the §14 reference vectors at envoys-rfc9421, and cross-implementation interop is verified bidirectionally against @envoys/sdk (Node) — Python signs / Node verifies, and Node signs / Python verifies. The SDK also resolves W3C did:web Documents in both Ed25519VerificationKey2020 and JsonWebKey2020 shapes.

License

Apache-2.0

Metadata

Release files for envoys 0.3.0

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

Source distribution (sdist)

Source distribution for envoys 0.3.0
File Size Uploaded Provenance Actions
envoys-0.3.0.tar.gz 29.4 kB Without provenance Details for envoys-0.3.0.tar.gz

Built distribution (wheel)

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

Total release size: 51.3 kB

Release files / envoys-0.3.0.tar.gz

Download URL envoys-0.3.0.tar.gz
Size 29.4 kB
Tags Source
SHA-256 checksum
How to use checksums
092635f02b391ec2ee8d1a9fea0cbd5b48cf8ec1ef4c9c6b5ad481192cf9d908
BLAKE2b-256 checksum
How to use checksums
89bb48549c3a0a217d5a610b95c2690a948265b34a18463c7e9d1eb8f5212336
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.10

Release files / envoys-0.3.0-py3-none-any.whl

Download URL envoys-0.3.0-py3-none-any.whl
Size 21.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
5b4c41a00dae603ab1173ee328bb23af5ffb918d25b25f62dada9f29328bfe15
BLAKE2b-256 checksum
How to use checksums
2486537152b7d5aa94c6ca8014a6e74949673000233952f7ce557bda57c0a7ef
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.10

Security for envoys 0.3.0

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.

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