apparitor 0.1.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. apparitor-0.1.1/.gitignore +30 -0
  2. apparitor-0.1.1/CHANGELOG.md +242 -0
  3. apparitor-0.1.1/CONTRIBUTING.md +116 -0
  4. apparitor-0.1.1/LICENSE +201 -0
  5. apparitor-0.1.1/NOTICE +8 -0
  6. apparitor-0.1.1/PKG-INFO +409 -0
  7. apparitor-0.1.1/README.md +358 -0
  8. apparitor-0.1.1/ROADMAP.md +159 -0
  9. apparitor-0.1.1/SECURITY.md +71 -0
  10. apparitor-0.1.1/docs/architecture.md +119 -0
  11. apparitor-0.1.1/docs/audit-log.md +264 -0
  12. apparitor-0.1.1/docs/eu-ai-act.md +112 -0
  13. apparitor-0.1.1/docs/requirements.md +194 -0
  14. apparitor-0.1.1/docs/security-review.md +224 -0
  15. apparitor-0.1.1/docs/setup.md +183 -0
  16. apparitor-0.1.1/examples/README.md +30 -0
  17. apparitor-0.1.1/examples/avp/README.md +6 -0
  18. apparitor-0.1.1/examples/cedar/README.md +137 -0
  19. apparitor-0.1.1/examples/cedar/docker-compose.yml +18 -0
  20. apparitor-0.1.1/examples/cedar/entities.json +19 -0
  21. apparitor-0.1.1/examples/cedar/gateway/Dockerfile +18 -0
  22. apparitor-0.1.1/examples/cedar/gateway/gateway.py +214 -0
  23. apparitor-0.1.1/examples/cedar/policies.cedar +19 -0
  24. apparitor-0.1.1/examples/cedar/smoke.sh +67 -0
  25. apparitor-0.1.1/examples/gateway/README.md +65 -0
  26. apparitor-0.1.1/examples/gateway/demo.py +198 -0
  27. apparitor-0.1.1/examples/mock_pdp/README.md +37 -0
  28. apparitor-0.1.1/examples/mock_pdp/mock_pdp.py +116 -0
  29. apparitor-0.1.1/examples/opa/README.md +140 -0
  30. apparitor-0.1.1/examples/opa/data.json +7 -0
  31. apparitor-0.1.1/examples/opa/docker-compose.yml +20 -0
  32. apparitor-0.1.1/examples/opa/gateway/Dockerfile +19 -0
  33. apparitor-0.1.1/examples/opa/gateway/gateway.py +224 -0
  34. apparitor-0.1.1/examples/opa/policy.rego +17 -0
  35. apparitor-0.1.1/examples/opa/smoke.sh +67 -0
  36. apparitor-0.1.1/examples/openfga/README.md +85 -0
  37. apparitor-0.1.1/examples/openfga/docker-compose.yml +22 -0
  38. apparitor-0.1.1/examples/openfga/model.json +19 -0
  39. apparitor-0.1.1/examples/openfga/smoke.sh +56 -0
  40. apparitor-0.1.1/examples/openfga/tuples.json +4 -0
  41. apparitor-0.1.1/examples/scenarios/README.md +47 -0
  42. apparitor-0.1.1/examples/scenarios/run.py +282 -0
  43. apparitor-0.1.1/examples/three-peps/README.md +51 -0
  44. apparitor-0.1.1/examples/three-peps/demo.py +216 -0
  45. apparitor-0.1.1/pyproject.toml +190 -0
  46. apparitor-0.1.1/src/apparitor/__init__.py +172 -0
  47. apparitor-0.1.1/src/apparitor/a2a.py +293 -0
  48. apparitor-0.1.1/src/apparitor/adapters.py +137 -0
  49. apparitor-0.1.1/src/apparitor/backends.py +153 -0
  50. apparitor-0.1.1/src/apparitor/cache.py +82 -0
  51. apparitor-0.1.1/src/apparitor/cedar.py +158 -0
  52. apparitor-0.1.1/src/apparitor/client.py +270 -0
  53. apparitor-0.1.1/src/apparitor/config.py +117 -0
  54. apparitor-0.1.1/src/apparitor/decision.py +205 -0
  55. apparitor-0.1.1/src/apparitor/engine.py +548 -0
  56. apparitor-0.1.1/src/apparitor/errors.py +70 -0
  57. apparitor-0.1.1/src/apparitor/fastmcp.py +487 -0
  58. apparitor-0.1.1/src/apparitor/mapping.py +381 -0
  59. apparitor-0.1.1/src/apparitor/metrics.py +99 -0
  60. apparitor-0.1.1/src/apparitor/models.py +163 -0
  61. apparitor-0.1.1/src/apparitor/nemo.py +204 -0
  62. apparitor-0.1.1/src/apparitor/py.typed +0 -0
  63. apparitor-0.1.1/src/apparitor/scanner.py +136 -0
  64. apparitor-0.1.1/tests/conformance/README.md +52 -0
  65. apparitor-0.1.1/tests/conformance/cases.json +151 -0
  66. apparitor-0.1.1/tests/conformance/interop_todo_cases.json +452 -0
  67. apparitor-0.1.1/tests/conformance/test_conformance.py +116 -0
  68. apparitor-0.1.1/tests/conformance/test_interop_todo.py +157 -0
  69. apparitor-0.1.1/tests/conftest.py +93 -0
  70. apparitor-0.1.1/tests/integration/__init__.py +0 -0
  71. apparitor-0.1.1/tests/integration/_helpers.py +191 -0
  72. apparitor-0.1.1/tests/integration/test_cedar.py +85 -0
  73. apparitor-0.1.1/tests/integration/test_opa.py +85 -0
  74. apparitor-0.1.1/tests/integration/test_opa_native.py +77 -0
  75. apparitor-0.1.1/tests/integration/test_openfga.py +125 -0
  76. apparitor-0.1.1/tests/unit/test_a2a_executor.py +765 -0
  77. apparitor-0.1.1/tests/unit/test_adapters.py +90 -0
  78. apparitor-0.1.1/tests/unit/test_backends.py +369 -0
  79. apparitor-0.1.1/tests/unit/test_cache.py +80 -0
  80. apparitor-0.1.1/tests/unit/test_cedar_backend.py +314 -0
  81. apparitor-0.1.1/tests/unit/test_cedar_gateway.py +213 -0
  82. apparitor-0.1.1/tests/unit/test_client.py +302 -0
  83. apparitor-0.1.1/tests/unit/test_decision.py +209 -0
  84. apparitor-0.1.1/tests/unit/test_engine.py +1173 -0
  85. apparitor-0.1.1/tests/unit/test_fastmcp_middleware.py +910 -0
  86. apparitor-0.1.1/tests/unit/test_golden.py +41 -0
  87. apparitor-0.1.1/tests/unit/test_log_contract.py +285 -0
  88. apparitor-0.1.1/tests/unit/test_mapping.py +351 -0
  89. apparitor-0.1.1/tests/unit/test_metrics.py +239 -0
  90. apparitor-0.1.1/tests/unit/test_mock_pdp.py +67 -0
  91. apparitor-0.1.1/tests/unit/test_models.py +63 -0
  92. apparitor-0.1.1/tests/unit/test_nemo_backend.py +179 -0
  93. apparitor-0.1.1/tests/unit/test_opa_gateway.py +253 -0
  94. apparitor-0.1.1/tests/unit/test_scanner.py +106 -0
  95. apparitor-0.1.1/tests/unit/test_security.py +123 -0
  96. apparitor-0.1.1/tests/unit/test_smoke.py +201 -0
@@ -0,0 +1,30 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ .eggs/
6
+ build/
7
+ dist/
8
+ sbom/
9
+ .venv/
10
+ venv/
11
+
12
+ # Test / coverage
13
+ .pytest_cache/
14
+ .mypy_cache/
15
+ .ruff_cache/
16
+ .hypothesis/
17
+ .coverage
18
+ .coverage.*
19
+ htmlcov/
20
+ coverage.xml
21
+
22
+ # Secrets — never commit real credentials or env files
23
+ .env
24
+ .env.*
25
+ !.env.example
26
+
27
+ # Editors / OS
28
+ .idea/
29
+ .vscode/
30
+ .DS_Store
@@ -0,0 +1,242 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project aims to follow
5
+ [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [0.1.1] - 2026-06-16
8
+
9
+ ### Added
10
+ - **`request_context_scope(context)` context manager** (`apparitor.mapping`, exported from
11
+ `apparitor`). Mirrors `subject_scope`: binds `current_request_context` for the duration
12
+ of the `with` block and resets it in `finally`, so a host that forgets to reset cannot
13
+ leak a prior request's injected context into a later request on a reused task/loop. This
14
+ is now the preferred pattern over calling `.set()/.reset()` directly (documented in
15
+ `docs/setup.md`).
16
+ - **Security posture & assurance document** (`docs/security-review.md`): standing
17
+ invariants, threat-model coverage, test citations, known limitations, and re-review
18
+ triggers. A continuously scannable baseline.
19
+
20
+ ### Security
21
+ - **PDP response parsing now rejects duplicate JSON keys (§3.6), failing closed with
22
+ `MalformedPDPResponseError`.** An `object_pairs_hook` in the new `_strict_json` helper
23
+ raises `MalformedPDPResponseError` on any duplicate key within a JSON object, closing a
24
+ response-validation gap at the PDP trust boundary. The fix applies to both the AuthZEN
25
+ transport and the OPA backend. Sibling objects in a batch response (multiple `"decision"`
26
+ fields in distinct array entries) are correctly not flagged; the check is per-object.
27
+ - **Error-path `VerdictResult.reason` is now always a generic string (§3.10).** All
28
+ error-path verdicts use the fixed `_DENY_REASON` constant, ensuring internal detail
29
+ (exception text, transport state) is never exposed to callers. Exception detail continues
30
+ to appear in operator logs for diagnostics. The fix also adds log lines on early-return
31
+ paths that previously had none (unparseable tool call, 4xx client error).
32
+ - **`asyncio.CancelledError` is now recorded in metrics before re-propagating.** A
33
+ dedicated `except asyncio.CancelledError` clause in `_evaluate_guarded` records a
34
+ `block/error` decision metric and immediately re-raises, preserving
35
+ structured-concurrency invariants. The public `evaluate_normalized` and
36
+ `evaluate_requests` docstrings note that `CancelledError` propagates and callers must
37
+ treat an unfinished scan as non-authorized.
38
+ - **Cedar `_entity_uid` hardened to reject backslash and control characters.** The
39
+ rejection is now explicit at the validation seam (covering double-quote, backslash, and
40
+ control characters), so invalid identifiers are caught before Cedar processes them. The
41
+ engine already failed closed on a Cedar parse error (NoDecision becomes BLOCK); this makes
42
+ the boundary explicit.
43
+ - **`.env` added to `.gitignore`; `llamafirewall` extra capped at `<1`.** Prevents
44
+ accidental credential commits and pins the audited major version of the ML stack.
45
+
46
+ ### Changed
47
+ - **`AuthZENConfigError` now also inherits `ValueError` for backward compatibility.**
48
+ Adapter constructors previously raised plain `ValueError` for misconfiguration (missing
49
+ PDP URL, reserved subject type, invalid label, now all four adapters, including the
50
+ scanner). Code catching `except ValueError` still catches these; code that was already
51
+ catching `except AuthZENConfigError` is unaffected. Pre-1.0 decision: keeping the mixin
52
+ avoids a breaking change for callers that only imported the public error hierarchy after
53
+ the fact. Update existing `except ValueError` handlers that handle only configuration
54
+ errors to catch `AuthZENConfigError` instead for precision. `build_engine` now also
55
+ rejects providing both `pdp_url` and `config` simultaneously (previously config silently
56
+ won; now a `AuthZENConfigError` is raised).
57
+ - **Inline/gateway SKIP semantics and batch-item merge logic unified across all adapters.**
58
+ `is_allowed_inline`, `is_allowed_gateway`, `record_pre_engine_refusal`, and
59
+ `DUAL_PRINCIPAL_CACHE_WARNING` are shared helpers in `apparitor.decision` (lowest-common
60
+ import, engine-free and host-SDK-free to avoid import cycles); their divergent SKIP
61
+ behaviour and the `is not None` batch-item merge semantics are pinned by tests.
62
+
63
+ ## [0.1.0] - 2026-06-11
64
+
65
+ ### Added
66
+ - **Audit-log schema frozen as a stability contract (`docs/audit-log.md`).** Logger,
67
+ levels, C1/C2/C3 line grammar (format strings, field tables, rendered examples),
68
+ fingerprint derivation, parsing guidance, timestamp/clock requirements, an EU
69
+ regulatory mapping (AI Act record-keeping and retention, GDPR handling, transfer
70
+ routing), and stability policy. Pinned by
71
+ `tests/unit/test_log_contract.py`. A failure there is a breaking log-schema change
72
+ requiring a CHANGELOG "Update log parsers" entry and a version bump.
73
+ - **Runnable MCP authorization-gateway example (`examples/gateway/`).** A FastMCP proxy
74
+ fronting an unmodifiable upstream, middleware enforcing at the chokepoint: denied calls
75
+ proven never to reach the upstream, listing filtered to hide what the subject may not
76
+ call. Exercised on both supported fastmcp lines by the `gateway-demo` CI job.
77
+ - **Runnable scenario walk-through (`examples/scenarios/`).** Dependency-free (core install
78
+ only) self-asserting script over the mock PDP covering seven scenarios: allow, deny
79
+ (2-part deny key), unparseable input fails closed, PDP unreachable with `on_error=deny`,
80
+ PDP unreachable with `on_error=human_review`, batch all-or-nothing aggregation, and
81
+ dual-principal (user AND agent boundary) evaluation. Gated in CI by the `scenarios` job.
82
+ Also: the mock PDP now supports a subject-scoped **3-part deny form**
83
+ (`"<subject_id>:<action>:<resource_id>"`) in addition to the existing 2-part form.
84
+ - **Dual-principal evaluation (`DualPrincipalMapper`).** Emits two requests per tool
85
+ call (the end user's grant and the agent's own permission boundary), ANDed by the
86
+ engine's all-allow-or-block aggregation, so an agent can never exercise a permission
87
+ its boundary denies even when the human holds it. The user leg is request-scoped only
88
+ (no `agent_id` fallback, which would collapse the AND); either principal unresolvable
89
+ fails closed; `"workload"` stays reserved. Works via the `mapper=` seam in the scanner,
90
+ the NeMo rail and the FastMCP middleware (tools + listing). Note: dual calls always
91
+ evaluate as a batch, so the opt-in ALLOW cache is not consulted.
92
+ - **Dual-principal boundary for adapter-shaped paths (closes
93
+ [#39](https://github.com/jhawlwut/apparitor/issues/39)).** `A2AAuthorizationExecutor`
94
+ and `FastMCPAuthorizationMiddleware` now accept a `boundary_subject` constructor parameter.
95
+ When set, every invocation (A2A) or every `resources/read` / `prompts/get` request (FastMCP)
96
+ is evaluated as a two-request batch (the resolved caller leg AND the boundary leg) using
97
+ the same AND/all-allow-or-block semantics as `DualPrincipalMapper`. For FastMCP,
98
+ `boundary_subject` covers the resource/prompt adapter paths only; use
99
+ `mapper=DualPrincipalMapper(config)` for `tools/call` and listing. A full deployment sets
100
+ both. The same fail-closed guards apply: `"workload"` boundary rejected at construction,
101
+ collapse (boundary == caller) refused before any PDP call, cache warning emitted when
102
+ `cache_enabled=True`. Shared helper `build_boundary_leg` in `apparitor.mapping` (exported
103
+ from the package) powers both adapters.
104
+ - `ToolCallMapper.map()` may now return a **sequence** of requests that must all be
105
+ allowed (backward compatible; `None`/empty sequence abstains, since an empty group can
106
+ never read as an allow, on either the aggregate or the per-item path).
107
+ - **A2A agent-executor enforcement point (`apparitor.a2a`, optional `[a2a]` extra).**
108
+ `A2AAuthorizationExecutor` wraps a deployment's `AgentExecutor` and authorizes every
109
+ agent-to-agent invocation before it runs (action `agent.invoke`; resource
110
+ `{type: "a2a_agent", id: <agent_label>}`, or `a2a_skill` with a `"<agent>/<skill>"` key
111
+ via the `skill_resolver` hook; segments validated, skill kept verbatim). The subject is
112
+ the server's **authenticated peer** (`Subject(type="agent", id=<user_name>)` by
113
+ default), falling back to a subject injected per request via
114
+ `ServerCallContext.state["subject"]` and then the opt-in static `agent_id`. An
115
+ unauthenticated caller is refused, and ambient contextvars are deliberately ignored
116
+ (the SDK's detached producer task snapshots them, so they go stale across turns). The request's A2A `tenant` is forwarded in the
117
+ AuthZEN context for multi-tenant policies (a caller-supplied claim for policies to
118
+ cross-check, not proof). Verdicts map fail-closed: only a clean
119
+ `ALLOW` reaches the wrapped executor; everything else raises a deliberately generic A2A
120
+ error (the rich reason stays in the operator log). `cancel` passes through ungated in
121
+ v1 (documented). Built on `AuthorizationEngine.evaluate_requests`, no core changes.
122
+ - **Complete MCP enforcement scope for the FastMCP middleware** (closes
123
+ [#32](https://github.com/jhawlwut/apparitor/issues/32),
124
+ [#33](https://github.com/jhawlwut/apparitor/issues/33),
125
+ [#34](https://github.com/jhawlwut/apparitor/issues/34)):
126
+ - `resources/read` and `prompts/get` are now **gated by default** (actions
127
+ `resource.read` / `prompt.get`; resource ids: the URI verbatim for resources with the
128
+ server label as a property, `"<server>/<prompt>"` for prompts), with the same subject
129
+ chain, generic refusals (`ResourceError`/`PromptError`) and fail-closed verdict
130
+ mapping as tool calls; `gate_resources=False` / `gate_prompts=False` opt a hook out.
131
+ **Breaking (pre-alpha):** deployments without resource/prompt policies will see those
132
+ requests denied; write policies or opt out.
133
+ - Opt-in `filter_listings=True` hides tools from `tools/list` whose `tools/call` the
134
+ subject would be denied. One batch PDP round trip, advisory only (`tools/call`
135
+ remains the enforcement invariant), and fail-closed (no subject or any fault hides
136
+ everything).
137
+ - Opt-in `allow_workload_subject=True` authorizes verified client-credentials tokens
138
+ (no `sub` claim) as the distinct `Subject(type="workload", id=<client_id>)`, never
139
+ coerced into a user subject; off by default, such tokens keep refusing. The
140
+ `"workload"` subject type is reserved (the constructor rejects it for claim-derived
141
+ and static subjects). Only `tools/list` is filtered; `resources/list` and
142
+ `prompts/list` still advertise names/URIs even though reads/gets are gated.
143
+ - `AuthorizationEngine.evaluate_requests()` (pre-mapped requests, e.g. adapter-shaped
144
+ resource/prompt tuples) and `AuthorizationEngine.evaluate_each()` (positional per-item
145
+ verdicts over one batch round trip, for visibility filtering, fail-closed per item).
146
+ - **Three-PEP portability demo (`examples/three-peps/`).** The same vendored Cedar policy
147
+ (`examples/cedar/policies.cedar`, deny-override on `destructive == true`) enforced
148
+ identically at the LlamaFirewall scanner, the NeMo Guardrails rail, and the FastMCP
149
+ middleware over the in-process Cedar backend: no Docker, no network. Self-asserting
150
+ (exits non-zero on any verdict mismatch) and gated in CI by the `three-pep-demo` job,
151
+ which installs all three enforcement-point extras and requires every lane to run.
152
+ - **FastMCP server-middleware enforcement point (`apparitor.fastmcp`, optional `[fastmcp]`
153
+ extra).** `FastMCPAuthorizationMiddleware` authorizes every MCP `tools/call` server-side,
154
+ before the tool executes, over the same `AuthorizationEngine` as the LlamaFirewall scanner
155
+ and the NeMo rail. The subject comes from the **validated** OAuth access token
156
+ (`claims["sub"]`, configurable) and outranks host-asserted subjects; a token without a
157
+ usable claim refuses, and the static `agent_id` fallback is gated behind an explicit
158
+ `allow_static_subject=True` opt-in (local/stdio only). Verdicts map fail-closed onto MCP:
159
+ only a clean `ALLOW` executes; `BLOCK` / `HUMAN_REVIEW` / mapper abstention / errors raise
160
+ a deliberately generic `ToolError` (the rich reason stays in the operator log). Supports
161
+ fastmcp 2.14 and 3.x (both exercised in CI).
162
+ - `AuthorizationEngine.evaluate_normalized()`: a public seam for enforcement points that
163
+ receive tool calls in structured form (no provider-shape adapter detection).
164
+ - `MCPResourceMapper` can now resolve its server label per call from
165
+ `request_context[MCP_SERVER_LABEL_KEY]`; the tool segment gets the same case/whitespace
166
+ normalisation as the default mapper, and `mcp_resource_id` rejects embedded `/` (an
167
+ ambiguous policy key), fail-closed.
168
+ - `DecisionCache` is bounded: new `cache_max_entries` config (default 10 000, FIFO
169
+ eviction) so per-token subject cardinality cannot grow the ALLOW cache without limit.
170
+ - **Docker-free OpenFGA integration backend (linux/amd64).** Set `APPARITOR_OPENFGA_NATIVE=1` to run the
171
+ OpenFGA E2E against a pinned, **SHA-256-verified** OpenFGA release binary launched directly
172
+ (only `github.com` egress needed) instead of a container, so the real-PDP test works where
173
+ the Docker registry is unreachable. Same vendored model + tuples and assertions; a
174
+ `workflow_dispatch` `integration-native` CI job exercises it.
175
+ - **Working scan pipeline (M1).** `AuthZENScanner.scan()` authorizes an agent's tool
176
+ calls end-to-end: extract → map → evaluate → decide.
177
+ - LlamaFirewall-free `AuthorizationEngine` holding all logic, so the pipeline is fully
178
+ unit-testable without the ML stack. The scanner is a thin adapter that converts the
179
+ verdict to a `ScanResult`.
180
+ - `AuthZENClient`: async httpx transport with explicit timeouts, a request budget,
181
+ bounded retries (429/5xx/transport only) with jittered backoff, an SSRF/TLS guard on
182
+ `pdp_url`, bring-your-own-client support, and httpx→typed-error mapping.
183
+ - `DefaultToolCallMapper` / `MCPResourceMapper`: request-scoped subject resolution
184
+ (never from model output), argument redaction/size-caps, MCP server-scoped resource ids.
185
+ - Opt-in, ALLOW-only TTL `DecisionCache` with a full-tuple SHA-256 key.
186
+ - Batch evaluation with all-allow-or-block aggregation; `review_predicate` escalation
187
+ (never downgrade); fail-closed `on_error` (deny / human_review).
188
+ - AuthZEN 1.0 pydantic models; provider-aware tool-call adapters (OpenAI / Anthropic /
189
+ LangChain); `ScannerConfig` with secure defaults; exception hierarchy.
190
+ - A dependency-free mock AuthZEN PDP for demos/tests.
191
+ - Test suite: 90+ unit tests, 98% line+branch coverage on the LlamaFirewall-free
192
+ modules (90% gate enforced), including the security invariants.
193
+ - **Real PDP examples (M3).** Runnable OpenFGA example (native, experimental AuthZEN API;
194
+ vendored model + relationship tuples) and Cedar example (policy-as-code behind a local
195
+ AuthZEN → Cedar gateway running the official Cedar CLI), each with a `smoke.sh` and a
196
+ Docker-gated `testcontainers` integration test that skips when no daemon is present.
197
+ - `integration` optional-dependency group (`testcontainers`) and a `workflow_dispatch`
198
+ CI job that runs the integration suite.
199
+ - **Observability (M2).** A dependency-free metrics sink (`MetricsSink` protocol with a
200
+ default `InMemoryMetrics` and a `NoopMetrics` opt-out): a decision-latency histogram and
201
+ a cache-hit/miss counter, surfaced on the engine and scanner. Structured audit logs now
202
+ carry the verdict, status, subject (decision principal), correlation id, and an argument
203
+ *fingerprint*; raw arguments and tokens are never logged (arguments are fingerprinted).
204
+ - **AuthZEN 1.0 wire-conformance suite** (`tests/conformance/`): vendored canonical
205
+ request/response payloads driven through the models and client to prove wire
206
+ compatibility (request shapes, response decisions, batch aggregation, and malformed
207
+ responses failing closed).
208
+
209
+ ### Changed
210
+
211
+ - **Audit-log message prefixes normalized `authzen` → `apparitor`** to match the logger
212
+ name and project name. Field names and grammar are unchanged. **Update log parsers**
213
+ anchoring on the old `authzen decision`, `authzen batch`, or `authzen per-item`
214
+ prefixes: replace with `apparitor decision`, `apparitor batch`,
215
+ `apparitor per-item`.
216
+ - The structured decision log's resource-id field is now `resources=` (was `tools=`).
217
+ It can carry resource URIs and prompt keys, not just tool names. Update log parsers.
218
+ - The structured decision log now records **every distinct principal** as `subjects=`
219
+ (was `subject=` with only the first leg). Under dual-principal evaluation the audit
220
+ trail must name both the user and the agent, and `resources=` / `fingerprints=`
221
+ carry one entry per evaluation request, so dual legs appear twice. Update log parsers.
222
+ - `DefaultToolCallMapper.map()`'s return annotation is widened to the full
223
+ `ToolCallMapper` union; a typed subclass that delegates to `super().map()` must
224
+ accept the widened return (runtime behaviour of the default mapper is unchanged).
225
+ - **Renamed to `apparitor` and repositioned.** The project is now a vendor-neutral
226
+ authorization layer that aggregates policy engines (AuthZEN, Cedar, OpenFGA, Rego) across
227
+ agentic firewalls (LlamaFirewall today; NeMo Guardrails planned), rather than an
228
+ AuthZEN-scanner-for-LlamaFirewall only. The Python import package and PyPI distribution
229
+ are now **`apparitor`** (was `authzen_llamafirewall` / `authzen-llamafirewall-scanner`):
230
+ `from apparitor import AuthZENScanner`. Breaking import change, acceptable pre-alpha, no
231
+ published release affected. Public API names (`AuthZENScanner`, `AuthorizationEngine`, …)
232
+ are unchanged.
233
+ - **Spec fix:** the batch options field is now `evaluations_semantic` (plural), matching
234
+ AuthZEN 1.0; it was previously serialised as `evaluation_semantic`. Renames
235
+ `EvaluationsOptions.evaluation_semantic` → `evaluations_semantic` (pre-alpha, breaking).
236
+ - `DefaultToolCallMapper._resource` takes the request context (so `MCPResourceMapper` can
237
+ resolve a per-call server label). A custom mapper subclass overriding the protected
238
+ `_resource` must adopt the new signature; the public `ToolCallMapper.map` contract is
239
+ unchanged.
240
+
241
+ ## [0.0.1a0]
242
+ - Initial pre-alpha scaffold.
@@ -0,0 +1,116 @@
1
+ # Contributing
2
+
3
+ Thanks for your interest! This is an Apache-2.0, public-standards-only project. Please keep
4
+ contributions free of proprietary or confidential material.
5
+
6
+ ## Development setup
7
+
8
+ ```bash
9
+ python -m venv .venv && source .venv/bin/activate
10
+ pip install -e ".[dev]" # AuthZEN client/models + tooling
11
+ pip install -e ".[dev,llamafirewall]" # add the scanner path (pulls the LlamaFirewall ML stack)
12
+ ```
13
+
14
+ The `[llamafirewall]` extra is heavy (it brings PromptGuard's ML dependencies). The
15
+ AuthZEN-free modules (`models`, `client`, `adapters`, `mapping`, `cache`, `config`,
16
+ `errors`) develop and test fine **without** it.
17
+
18
+ ## Checks
19
+
20
+ ```bash
21
+ ruff check . # lint
22
+ ruff format . # format
23
+ mypy src/ # types (strict)
24
+ pytest # unit suite (integration excluded by default)
25
+ pytest -m integration # integration (needs Docker; auto-skips when absent)
26
+ python -m build && twine check dist/* # packaging
27
+ ```
28
+
29
+ CI runs lint, types, the unit suite on Python 3.10 to 3.13, and a packaging check. The
30
+ coverage gate (≥90% line+branch on the LlamaFirewall-free modules) turns on with the
31
+ behavioural suite.
32
+
33
+ ## Branch naming
34
+
35
+ Format: **`<type>/<short-kebab-summary>`**, optionally with an issue number:
36
+ `<type>/<issue>-<summary>`. Lowercase, hyphen-separated, 3 to 5 words.
37
+
38
+ ```
39
+ feat/authzen-client
40
+ fix/cache-ttl-clamp
41
+ docs/setup-guide
42
+ feat/42-batch-evaluation
43
+ ```
44
+
45
+ `<type>` matches the commit types below (`feat`, `fix`, `docs`, `refactor`, `test`,
46
+ `chore`, `ci`, `perf`). Automated agent sessions may push to tool-managed branches like
47
+ `claude/<slug>-<id>`. The trailing id is intentional collision-avoidance, not a naming
48
+ choice. Human-driven branches should use the convention above.
49
+
50
+ ## Commit messages: [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/)
51
+
52
+ ```
53
+ <type>(<optional scope>): <imperative summary> # ≤72 chars, lowercase, no trailing period
54
+
55
+ <body: explain *why*, not what; wrapped at 72 cols>
56
+
57
+ <footers: e.g. "Closes #123", "BREAKING CHANGE: ...">
58
+ ```
59
+
60
+ - **Types:** `feat` (new feature), `fix` (bug fix), `docs`, `refactor`, `perf`, `test`,
61
+ `build`, `ci`, `chore`, `revert`.
62
+ - **Scope** (optional) names the area, e.g. `feat(client):`, `fix(cache):`.
63
+ - **Breaking changes:** append `!` (`feat(config)!: ...`) and/or add a
64
+ `BREAKING CHANGE:` footer.
65
+ - Imperative mood ("add", not "added"/"adds"). The subject says *what*; the body says
66
+ *why*.
67
+
68
+ ## Pull requests
69
+
70
+ - One logical change per PR; keep them small and reviewable. Open as **draft** early.
71
+ - PR **title** uses the Conventional Commits format (it usually becomes the squash-merge
72
+ subject).
73
+ - PR **body** covers: context/why, what changed, how it was tested, and linked issues
74
+ (`Closes #N`).
75
+ - CI must be green. Run the full check suite (see above) before pushing.
76
+ - Add or update tests for behavioural changes; update docs in the same PR.
77
+
78
+ ## AI-assisted contributions
79
+
80
+ AI coding assistants are **welcome** here. Many of this project's own changes are
81
+ agent-assisted. The bar is the same as for any contribution, and a few expectations keep it
82
+ useful rather than noisy:
83
+
84
+ - **You are the author of record.** By submitting, you certify you understand, have
85
+ reviewed, and have tested the change: you can explain and stand behind every line,
86
+ whoever (or whatever) drafted it. Signing off (`git commit -s`, see below) makes that
87
+ explicit.
88
+ - **Same quality bar.** The full check suite must pass, behavioural changes need tests, and
89
+ the [anti-slop guidance in `AGENTS.md`](AGENTS.md) applies: smallest change that solves
90
+ the problem, no drive-by churn, comments that explain *why*. Unreviewed or bulk
91
+ machine-generated PRs (mass "fix-up" sweeps, output you haven't read) will be closed.
92
+ - **Disclosure is encouraged, not required.** If an assistant did meaningful work, note it
93
+ with a provenance trailer so reviewers know where to look: `Assisted-by:` when a human
94
+ authored with help, `Generated-by:` when a tool produced essentially all of a change
95
+ (e.g. `Assisted-by: <tool/model>`). The trailer names a *tool*, never an author: an
96
+ assistant is never listed as `Signed-off-by:` or `Co-authored-by:`. The DCO sign-off and
97
+ authorship are yours alone, and the change is attributed to you. (Don't name the assistant
98
+ anywhere else in the commit or PR text.)
99
+ - **Agent-instruction files are security-sensitive.** Changes to `AGENTS.md`, `CLAUDE.md`,
100
+ `.claude/**`, and the CI/release workflows are reviewed with extra scrutiny. Coding agents
101
+ treat instruction files as trusted context and a compromised workflow could leak secrets,
102
+ so both are an injection / supply-chain surface. See [`SECURITY.md`](SECURITY.md).
103
+
104
+ Working through an agent? [`AGENTS.md`](AGENTS.md) is the canonical guide (tool-specific
105
+ files point to it), and reusable workflows live in [`.claude/skills/`](.claude/skills/).
106
+
107
+ ## Licensing & sign-off
108
+
109
+ By contributing you agree your work is licensed under Apache-2.0. DCO-style sign-off is
110
+ welcome: `git commit -s`.
111
+
112
+ ## Design
113
+
114
+ Read [`docs/requirements.md`](docs/requirements.md) before proposing behavioural changes.
115
+ Several decisions (no fail-open, subject never from message content, ALLOW-only caching)
116
+ are deliberate security invariants, not oversights.
@@ -0,0 +1,201 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright [yyyy] [name of copyright owner]
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
apparitor-0.1.1/NOTICE ADDED
@@ -0,0 +1,8 @@
1
+ apparitor
2
+ Copyright 2026 The apparitor authors
3
+
4
+ This product is licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this product except in compliance with the License. You may obtain
6
+ a copy of the License in the LICENSE file or at:
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0