Metadata-Version: 2.4
Name: authzen-policy-backend
Version: 0.1.2
Summary: OpenID AuthZEN policy backend for Microsoft Agent Governance Toolkit
Project-URL: Homepage, https://github.com/EmpowerID/authzen-policy-backend
Project-URL: Documentation, https://github.com/EmpowerID/authzen-policy-backend#readme
Project-URL: Repository, https://github.com/EmpowerID/authzen-policy-backend
Author-email: EmpowerID <engineering@empowerid.com>
License-Expression: MIT
License-File: LICENSE
Keywords: agent-governance,ai-agent,authorization,authzen,mcp,openid,policy
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
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: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: httpx<1,>=0.27
Requires-Dist: pydantic<3,>=2.4
Provides-Extra: dev
Requires-Dist: mypy>=1.13; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: ruff>=0.8; extra == 'dev'
Description-Content-Type: text/markdown

# authzen-policy-backend

[![PyPI](https://img.shields.io/pypi/v/authzen-policy-backend)](https://pypi.org/project/authzen-policy-backend/)
[![Python](https://img.shields.io/pypi/pyversions/authzen-policy-backend)](https://pypi.org/project/authzen-policy-backend/)
[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
[![Typed](https://img.shields.io/badge/typing-typed-blue.svg)](https://peps.python.org/pep-0561/)

**OpenID AuthZEN policy backend for the [Microsoft Agent Governance Toolkit](https://github.com/microsoft/agent-governance-toolkit).**

Connect any [AuthZEN](https://openid.net/specs/openid-authzen-authorization-api-1_0.html)-compliant Policy Decision Point (PDP) to Microsoft's Agent Governance Toolkit as a pluggable `ExternalPolicyBackend` — with one `add_backend()` call.

```
┌────────────────────────────────────────────────────────────────┐
│                    Your AI Agent                                │
│  (LangGraph, OpenAI Agents, AutoGen, custom, ...)              │
└──────────────────────┬─────────────────────────────────────────┘
                       │ evaluate(context)
┌──────────────────────▼─────────────────────────────────────────┐
│            MS Agent Governance Toolkit                          │
│  PolicyEvaluator  →  YAML rules (fast local)                   │
│                   →  AuthZENBackend (this package)              │
└──────────────────────┬─────────────────────────────────────────┘
                       │ POST /access/v1/evaluation
┌──────────────────────▼─────────────────────────────────────────┐
│              AuthZEN PDP                                        │
│  (EmpowerNow ARIA, Cerbos, Aserto, or any compliant PDP)      │
│  → Obligations, Constraints, Advice, Delegation, Budget        │
└────────────────────────────────────────────────────────────────┘
```

## Why

The Agent Governance Toolkit provides in-process YAML policy evaluation and optional OPA/Cedar backends. For enterprise deployments, you need:

- **Centralized policy management** — one PDP governs all agents, not scattered YAML files
- **Standards-based authorization** — OpenID AuthZEN, not proprietary policy formats
- **Rich decisions** — obligations, constraints, budget limits, and denial advice beyond binary allow/deny
- **Delegation-aware identity** — user-bound agents with scoped capabilities
- **Audit-grade decisions** — decision IDs that link to cryptographic receipts

This package bridges that gap with a single `add_backend()` call.

## Installation

```bash
pip install authzen-policy-backend
```

## Quick Start

### Minimal — 3 Lines to Policy Evaluation

```python
from agent_os.policies import PolicyEvaluator
from authzen_backend import AuthZENBackend

evaluator = PolicyEvaluator()
evaluator.add_backend(AuthZENBackend(
    pdp_url="https://pdp.example.com/access/v1/evaluation",
    pdp_application="my-agent-platform",
    token="my-bearer-token",
))

decision = evaluator.evaluate({
    "tool_name": "file_read",
    "agent_id": "travel-agent-1",
    "user_id": "alice",
})

print(decision.allowed)       # True / False
print(decision.reason)        # Human-readable reason from PDP
print(decision.evaluation_ms) # Round-trip time in ms
```

### Client Credentials — Production Auth

```python
from authzen_backend import AuthZENBackend, ClientCredentialsAuth

backend = AuthZENBackend(
    pdp_url="https://pdp.example.com/access/v1/evaluation",
    pdp_application="my-agent-platform",
    client_credentials=ClientCredentialsAuth(
        token_endpoint="https://idp.example.com/oauth/token",
        client_id="agent-policy-client",
        client_secret="...",
        audience="pdp",
    ),
)

# Tokens are cached and refreshed automatically
decision = backend.evaluate({
    "tool_name": "transfer_funds",
    "agent_id": "finance-bot",
    "user_id": "bob",
})
```

### Async Backend — For Async Agent Runtimes

```python
from authzen_backend import AsyncAuthZENBackend

async with AsyncAuthZENBackend(
    pdp_url="https://pdp.example.com/access/v1/evaluation",
    pdp_application="my-agent-platform",
    token="my-bearer-token",
) as backend:
    result = await backend.evaluate({
        "tool_name": "deploy_service",
        "agent_id": "ops-agent",
    })
```

### ARIA-Enhanced Context — Obligations, Constraints, Advice

When connected to an [EmpowerNow ARIA](https://empowerid.com/aria) PDP, extract rich governance context that generic AuthZEN backends don't provide:

```python
from authzen_backend import AuthZENBackend
from authzen_backend.aria import extract_aria_context

backend = AuthZENBackend(
    pdp_url="https://aria-pdp.example.com/access/v1/evaluation",
    pdp_application="my-platform",
    token="...",
)

result = backend.evaluate({
    "tool_name": "transfer_funds",
    "agent_id": "finance-bot",
    "user_id": "bob",
    "amount": 50000,
})

aria = extract_aria_context(result)
print(aria.decision_id)       # Links to a signed receipt in Receipt Vault
print(aria.obligations)       # Actions the caller must perform
print(aria.constraints)       # Parameter-level restrictions
print(aria.advice)            # Structured denial guidance with remediation
print(aria.requires_approval) # True if human-in-the-loop approval needed
```

## How It Works

```mermaid
sequenceDiagram
    participant Agent as Your Agent
    participant PE as PolicyEvaluator
    participant AZB as AuthZENBackend
    participant PDP as AuthZEN PDP

    Agent->>PE: evaluate(context)
    PE->>AZB: evaluate(context)
    Note over AZB: Map flat dict to<br/>AuthZEN 1.0 request
    AZB->>PDP: POST /access/v1/evaluation
    PDP-->>AZB: { decision, context }
    Note over AZB: Extract reason,<br/>obligations, constraints
    AZB-->>PE: BackendDecision
    PE-->>Agent: Final decision
```

## Context Mapping

The toolkit passes a flat `dict` to backends. This package maps it to a structured AuthZEN request:

| Toolkit Context Key | AuthZEN Field | Notes |
|---------------------|---------------|-------|
| `agent_id` | `subject.id` | Normalized to ARN: `auth:agent:agentmesh:{id}` |
| `user_id` | `subject.properties.bound_to_user_id` | User the agent acts for |
| `delegator` | `subject.properties.delegator` | Delegation chain |
| `tool_name` | `action.name`, `resource.id` | Also generates `op:tool:{name}` |
| `action` | `action.name` | Overrides `tool_name` if present |
| `resource_type` | `resource.type` | Defaults to `mcp_tool` |
| `resource_id` | `resource.id` | Defaults to `tool_name` |
| *(all others)* | `context.*` | Forwarded as-is |

`context.pdp_application` and `action.properties.operation_ref_id` are set automatically.

### Custom Mapping

Override field names if your agents use different context keys:

```python
from authzen_backend import AuthZENBackend, FieldMapping

backend = AuthZENBackend(
    pdp_url="https://pdp.example.com/access/v1/evaluation",
    pdp_application="my-platform",
    token="...",
    mapping=FieldMapping(
        agent_id_key="principal",
        tool_name_key="operation",
        user_id_key="end_user",
    ),
)
```

## Fail-Closed vs Fail-Open

```mermaid
flowchart TD
    A[PDP call fails] --> B{fail_closed?}
    B -->|"True (default)"| C["Return deny<br/>error = None"]
    B -->|False| D["Return deny<br/>error = description"]
    C --> E[Toolkit stops — hard deny]
    D --> F[Toolkit tries next backend]
```

- **`fail_closed=True`** (default): PDP errors produce an authoritative deny. The toolkit treats this as final and does not consult other backends. Use this for production.
- **`fail_closed=False`**: PDP errors set the `error` field on the `BackendDecision`. The toolkit skips this backend and tries the next one, or falls back to its default action. Use this for multi-backend setups where another backend can decide.

## Configuration Reference

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `pdp_url` | `str` | *(required)* | AuthZEN evaluation endpoint URL |
| `pdp_application` | `str` | *(required)* | Application ID for policy scoping |
| `token` | `str` | `None` | Static Bearer token (mutually exclusive with `client_credentials`) |
| `client_credentials` | `ClientCredentialsAuth` | `None` | OAuth client credentials config |
| `timeout` | `float` | `5.0` | HTTP timeout in seconds |
| `default_resource_type` | `str` | `"mcp_tool"` | Fallback resource type |
| `default_subject_type` | `str` | `"ai_agent"` | Fallback subject type |
| `mapping` | `FieldMapping` | `DEFAULT_MAPPING` | Context field name overrides |
| `fail_closed` | `bool` | `True` | Hard deny on errors (True) or signal error (False) |
| `http_client` | `httpx.Client` | `None` | Inject a pre-configured HTTP client |

## Security

- **HTTPS enforced** — `pdp_url` must use HTTPS in production. HTTP is allowed only for `localhost` / `127.0.0.1` / `[::1]` during development.
- **Error sanitization** — `httpx` exceptions are reduced to safe descriptions (`HTTP 503`, `PDP request timed out`) so URLs, tokens, and response bodies are never leaked in logs or error messages.
- **No credential logging** — `client_secret` and `token` fields use `repr=False` so they never appear in logs, tracebacks, or `__repr__` output.
- **Injection protection** — `extra_params` cannot override OAuth core fields (`grant_type`, `client_id`, `client_secret`).

See [SECURITY.md](SECURITY.md) for reporting vulnerabilities.

## Testing

70 tests | 100% coverage | mypy strict | ruff

```bash
pip install -e ".[dev]"
pytest tests/ -v
ruff check src/ tests/
mypy src/
```

## Compatibility

| Component | Version |
|-----------|---------|
| **Python** | 3.10+ |
| **Dependencies** | `httpx` >=0.27, `pydantic` v2 |
| **Agent Governance Toolkit** | Any version with `ExternalPolicyBackend` |
| **PDP** | Any OpenID AuthZEN 1.0 compliant PDP |

This package has **zero dependency on `agent-os-kernel`** — it implements the `ExternalPolicyBackend` protocol structurally (duck typing). Install alongside the toolkit or use standalone.

> **Looking for AGT-native integration?** If you're using AGT's `ToolCallInterceptor`, `AuditBackend`, or `PolicyProviderInterface` extension points, see [`aria-agentkit`](https://pypi.org/project/aria-agentkit/) — which builds on this package and provides contract-tested adapters for the full AGT surface.

## Links

- [PyPI](https://pypi.org/project/authzen-policy-backend/)
- [OpenID AuthZEN Specification](https://openid.net/specs/openid-authzen-authorization-api-1_0.html)
- [Microsoft Agent Governance Toolkit](https://github.com/microsoft/agent-governance-toolkit)
- [EmpowerNow ARIA](https://empowerid.com/aria)
- [`aria-agentkit`](https://pypi.org/project/aria-agentkit/) — AGT-native ARIA integration

## License

MIT — see [LICENSE](LICENSE).
