AAC sidecar-to-agent invoke-push authentication (AAC1-HMAC-SHA256) — signing and verification core, with FastAPI mounting surfaces.
Project description
aac-invoke-auth
Authenticate /invoke pushes from your paired AAC sidecar.
When your agent runs behind an AAC sidecar, the sidecar verifies the
cross-organizational authority chain and then POSTs the verified context
to your agent's /invoke endpoint. The X-AAC-* headers on that push
are your agent's entire view of the verified chain — so your agent
must be able to tell a genuine sidecar push from a forged one. This
package is that check: the sidecar HMAC-signs every push with a pairing
secret shared with exactly one agent, and your agent verifies the
signature before trusting anything.
The verification core is standard-library only (hmac, hashlib) and
framework-agnostic. FastAPI mounting surfaces ship behind an extra:
pip install aac-invoke-auth[fastapi]
Quick start (FastAPI)
Generate the pairing secret once and share the file with your sidecar
(the sidecar's sidecar.agent_invoke_auth.secret_file points at the
same file; one secret per sidecar–agent pair, never shared across
pairs):
openssl rand -hex 32 > /etc/aac/invoke-auth/invoke-auth.secret
Point your agent at it and mount the middleware:
export AAC_INVOKE_AUTH_SECRET_FILE=/etc/aac/invoke-auth/invoke-auth.secret
from fastapi import FastAPI
from aac_invoke_auth.fastapi import InvokeAuthGuard, InvokeAuthMiddleware
app = FastAPI()
app.add_middleware(
InvokeAuthMiddleware,
guard=InvokeAuthGuard.from_env(),
protected_paths=("/invoke",), # required, exact-match
)
That's the whole integration. Unsigned, tampered, replayed-stale, or
wrong-secret pushes get a 401 with a generic detail and never reach
your business logic; your handlers read request.json() unchanged.
Prefer route-scoped protection? The guard is also a plain FastAPI dependency:
from fastapi import Depends
guard = InvokeAuthGuard.from_env()
@app.post("/invoke", dependencies=[Depends(guard)])
async def invoke(request: Request): ...
Both surfaces call the same verifier — pick whichever fits your app.
Fail-fast startup, by design
InvokeAuthGuard.from_env() refuses to start when
AAC_INVOKE_AUTH_SECRET_FILE is unset. A missing secret is a security
downgrade; it should fail your deployment loudly at boot, not degrade
into a log line nobody reads.
The single escape hatch — for extraordinary cases only, such as tenant test/pilot environments or debugging a suspected signing mismatch — is setting
export AAC_INVOKE_AUTH_ALLOW_UNAUTHENTICATED=true
explicitly (only the exact value true; a typo still fails the boot).
An opted-out agent accepts every push unverified and logs a prominent
warning at startup so the posture is visible in your logs. Never run
this in production: the sidecar side has a symmetric dev_mode
interlock, and both exist to make "unauthenticated" impossible to reach
by accident.
Testing your agent
aac_invoke_auth.testing ships with the package:
from aac_invoke_auth.testing import signing_test_client
client = signing_test_client(app) # wraps FastAPI's TestClient
response = client.post("/invoke", headers=aac_headers, json=payload)
post() calls to protected paths are signed exactly the way the paired
sidecar signs them (secret read from AAC_INVOKE_AUTH_SECRET_FILE);
everything else passes through. So your test suite exercises the real
production verification path instead of opting out of it.
Wire format
AAC1-HMAC-SHA256 over method, path, timestamp, a SHA-256 body digest,
and every X-AAC-* header (derived by rule, so header
insertion/removal after signing fails verification). Freshness window
±30s. The normative canonicalization text lives in the AAC Engineering
Specification §III.2.4a; frozen known-answer vectors live in this
package's test suite — a change that breaks a vector is a wire-format
break, not a refactor.
Ports of this middleware for other stacks (Express, Spring, Go net/http) follow the same model; the core stays standard-library-only in every language precisely to keep those ports mechanical.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file aac_invoke_auth-0.1.0.tar.gz.
File metadata
- Download URL: aac_invoke_auth-0.1.0.tar.gz
- Upload date:
- Size: 26.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9a0894860dfcb5b1f459f1e0d8005f646edccb697d41304cb3455890c2b3b5f6
|
|
| MD5 |
9d97c5df82b730d5812d34f5d110df9f
|
|
| BLAKE2b-256 |
26e0b6b2e2c8a1c99a21b93fbe2b7581d8b7f68a73129e910362630f0d97a8ee
|
File details
Details for the file aac_invoke_auth-0.1.0-py3-none-any.whl.
File metadata
- Download URL: aac_invoke_auth-0.1.0-py3-none-any.whl
- Upload date:
- Size: 20.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f36c7945d90699dbedfb65108604ea7c52fc27543aeac566eee81d1f20f85470
|
|
| MD5 |
8ea2d61325bf95439f01da06935c5d59
|
|
| BLAKE2b-256 |
90a0f8420ad3be12caeb64f8e7cdb949da7cf3084155a34a0f34c004dad91509
|