From 439b02eab2571809ce268c16c844d5aa233b61ac Mon Sep 17 00:00:00 2001 From: Arunima Chaudhuri Date: Sun, 27 Sep 2026 22:59:33 +0530 Subject: [PATCH 1/3] fix(sdk): make AgentSigAuth compatible with httpx2 clients --- agentauth/sdk.py | 19 ++++++++++++++++--- tests/test_agentauth.py | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/agentauth/sdk.py b/agentauth/sdk.py index cbf6885..4e0b107 100644 --- a/agentauth/sdk.py +++ b/agentauth/sdk.py @@ -184,7 +184,13 @@ def load(cls, directory: Path, name: str, passphrase: Optional[str] = None, reco class AgentSigAuth(httpx.Auth): - """httpx auth hook: signs every outgoing request with the agent's key.""" + """Signs every outgoing request with the agent's key (and presents a grant chain, if any). + + Works with both `httpx` and `httpx2` clients: httpx uses it as an + `httpx.Auth` (via `auth_flow`), while httpx2 — which doesn't recognise + httpx's Auth class — accepts any callable that takes and returns a request + (via `__call__`). + """ requires_request_body = True @@ -192,13 +198,20 @@ def __init__(self, identity: AgentIdentity, grants: Optional[GrantChain] = None) self.identity = identity self.grants = grants.encode() if grants is not None else "" - def auth_flow(self, request: httpx.Request): + def sign(self, request): + """Add the AgentSig (and AgentGrant) headers to an httpx or httpx2 request, in place.""" if self.grants: request.headers[GRANT_HEADER] = self.grants request.headers["Authorization"] = self.identity.authorization( request.method, str(request.url), request.content, self.grants ) - yield request + return request + + def auth_flow(self, request: httpx.Request): + yield self.sign(request) + + def __call__(self, request): + return self.sign(request) class RegistryError(Exception): diff --git a/tests/test_agentauth.py b/tests/test_agentauth.py index e47e51f..2a70866 100644 --- a/tests/test_agentauth.py +++ b/tests/test_agentauth.py @@ -272,3 +272,38 @@ def test_save_load_encrypted_and_split(tmp_path, registry): admin = AgentIdentity.load(keys, "persist", passphrase="hunter2", recovery_dir=vault) reg.rotate(admin) assert reg.whoami(admin)["kid"].endswith("#key-1") + + +# --------------------------------------------------------------------------- # +# client compatibility +# --------------------------------------------------------------------------- # + + +def test_auth_works_with_plain_httpx_client(): + """TestClient uses httpx2 when installed; make sure plain httpx clients are still supported.""" + import httpx + + ident, _ = AgentIdentity.create(name="plain") + verifier = RequestVerifier(lambda d: (ident.current_key.public_multikey, ident.kid), audiences={"svc.example"}) + + def handler(request: httpx.Request) -> httpx.Response: + agent = verifier.verify( + authorization=request.headers["authorization"], + method=request.method, + path=request.url.raw_path.decode(), + body=request.content, + ) + return httpx.Response(200, json={"did": agent.did}) + + with httpx.Client(transport=httpx.MockTransport(handler)) as client: + r = client.post("http://svc.example/do?x=1", json={"a": 1}, auth=ident.httpx_auth()) + assert r.json() == {"did": ident.did} + + +def test_auth_is_callable_for_httpx2_style_clients(): + ident, _ = AgentIdentity.create(name="callable") + import httpx + + req = httpx.Request("GET", "http://svc.example/x") + signed = ident.httpx_auth()(req) + assert signed is req and req.headers["authorization"].startswith("AgentSig ") From ce6a428df3f37ae434e6595a075878feb8807a11 Mon Sep 17 00:00:00 2001 From: Arunima Chaudhuri Date: Sun, 27 Sep 2026 22:59:33 +0530 Subject: [PATCH 2/3] chore(deps): add httpx2 to dev extras for starlette TestClient --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 1dba27b..690bbaa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,7 @@ dependencies = ["cryptography>=42", "httpx>=0.27"] [project.optional-dependencies] server = ["fastapi>=0.110", "uvicorn>=0.29"] -dev = ["agentauth[server]", "pytest>=8"] +dev = ["agentauth[server]", "pytest>=8", "httpx2>=2.13"] # httpx2: used by starlette.testclient [project.scripts] agentauth = "agentauth.cli:main" From 910033f8b961bcc6fc49fc824bb4be82b670e5bd Mon Sep 17 00:00:00 2001 From: Arunima Chaudhuri Date: Sun, 27 Sep 2026 22:59:34 +0530 Subject: [PATCH 3/3] ci: add GitHub Actions test matrix; use python -m pytest in docs --- .github/workflows/ci.yml | 59 ++++++++++++++++++++++++++++++++++++++++ README.md | 4 ++- 2 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..f99646f --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,59 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + +permissions: + contents: read + +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + +jobs: + test: + name: Python ${{ matrix.python-version }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ["3.10", "3.11", "3.12", "3.13"] + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + cache: pip + cache-dependency-path: pyproject.toml + + - name: Install + run: | + python -m pip install --upgrade pip + python -m pip install -e ".[dev]" + + - name: Test + run: python -m pytest -q + + # The SDK must keep working for callers on plain httpx (no httpx2 installed). + test-httpx-only: + name: Python 3.12 (httpx only) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + cache: pip + cache-dependency-path: pyproject.toml + + - name: Install without httpx2 + run: | + python -m pip install --upgrade pip + python -m pip install -e ".[server]" pytest + + - name: Test + run: python -m pytest -q diff --git a/README.md b/README.md index c46796b..0083943 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # AgentAuth +[![CI](https://github.com/invarislabs/agent-auth/actions/workflows/ci.yml/badge.svg)](https://github.com/invarislabs/agent-auth/actions/workflows/ci.yml) + Cryptographic identities and delegated authority for autonomous AI agents. Each agent gets a **self-certifying DID** backed by an Ed25519 keypair. It proves who it is by **signing every request**, so no API key or bearer token ever crosses the wire. Keys can be **rotated** safely, and an agent can be **killed** by itself or by the human or org that owns it. Anyone can check all of this without trusting the registry. @@ -130,7 +132,7 @@ Amounts are integers in the smallest unit (such as cents); floats are never sign ```bash python3 -m venv .venv && . .venv/bin/activate pip install -e ".[dev]" -pytest # 74 tests +python -m pytest # 76 tests (use `python -m` so the venv's pytest runs) agentauth serve --db agentauth.db & # registry on 127.0.0.1:8000