Metadata-Version: 2.4
Name: aac-invoke-auth
Version: 0.1.0
Summary: AAC sidecar-to-agent invoke-push authentication (AAC1-HMAC-SHA256) — signing and verification core, with FastAPI mounting surfaces.
Author: Agent Authority Cloud Project
License-Expression: Apache-2.0
Keywords: aac,agent,authorization,hmac,middleware,fastapi,sidecar
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Security
Classifier: Framework :: FastAPI
Classifier: Typing :: Typed
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: fastapi
Requires-Dist: fastapi>=0.115; extra == "fastapi"
Provides-Extra: test
Requires-Dist: pytest>=8.0; extra == "test"
Requires-Dist: fastapi>=0.115; extra == "test"
Requires-Dist: httpx>=0.28; extra == "test"
Dynamic: license-file

# 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:

```bash
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):

```bash
openssl rand -hex 32 > /etc/aac/invoke-auth/invoke-auth.secret
```

Point your agent at it and mount the middleware:

```bash
export AAC_INVOKE_AUTH_SECRET_FILE=/etc/aac/invoke-auth/invoke-auth.secret
```

```python
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:

```python
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

```bash
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:

```python
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.
