AccessGate 0.2.0a1__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 (68) hide show
  1. accessgate-0.2.0a1/CHANGELOG.md +82 -0
  2. accessgate-0.2.0a1/LICENSE +21 -0
  3. accessgate-0.2.0a1/MANIFEST.in +10 -0
  4. accessgate-0.2.0a1/PKG-INFO +287 -0
  5. accessgate-0.2.0a1/README.md +239 -0
  6. accessgate-0.2.0a1/SECURITY.md +58 -0
  7. accessgate-0.2.0a1/docs/AUDIT_SCOPE.md +30 -0
  8. accessgate-0.2.0a1/docs/POLICY_FORMAT.md +27 -0
  9. accessgate-0.2.0a1/docs/RELEASE.md +22 -0
  10. accessgate-0.2.0a1/docs/SECURE_DEVELOPMENT.md +27 -0
  11. accessgate-0.2.0a1/docs/SECURITY_ASSURANCE.md +47 -0
  12. accessgate-0.2.0a1/docs/THREAT_MODEL.md +63 -0
  13. accessgate-0.2.0a1/docs/abac.md +156 -0
  14. accessgate-0.2.0a1/docs/architecture.md +93 -0
  15. accessgate-0.2.0a1/docs/customization.md +84 -0
  16. accessgate-0.2.0a1/fuzz/corpus/mcp/request.json +1 -0
  17. accessgate-0.2.0a1/fuzz/corpus/policy/valid.json +1 -0
  18. accessgate-0.2.0a1/fuzz/fuzz_mcp_json.py +28 -0
  19. accessgate-0.2.0a1/fuzz/fuzz_policy_json.py +29 -0
  20. accessgate-0.2.0a1/mcp/README.md +37 -0
  21. accessgate-0.2.0a1/mcp/agent-contract.json +419 -0
  22. accessgate-0.2.0a1/mcp/server.py +14 -0
  23. accessgate-0.2.0a1/pyproject.toml +102 -0
  24. accessgate-0.2.0a1/requirements/build.in +5 -0
  25. accessgate-0.2.0a1/requirements/build.txt +596 -0
  26. accessgate-0.2.0a1/setup.cfg +4 -0
  27. accessgate-0.2.0a1/src/AccessGate.egg-info/PKG-INFO +287 -0
  28. accessgate-0.2.0a1/src/AccessGate.egg-info/SOURCES.txt +66 -0
  29. accessgate-0.2.0a1/src/AccessGate.egg-info/dependency_links.txt +1 -0
  30. accessgate-0.2.0a1/src/AccessGate.egg-info/entry_points.txt +2 -0
  31. accessgate-0.2.0a1/src/AccessGate.egg-info/requires.txt +28 -0
  32. accessgate-0.2.0a1/src/AccessGate.egg-info/top_level.txt +1 -0
  33. accessgate-0.2.0a1/src/access_gate/__init__.py +118 -0
  34. accessgate-0.2.0a1/src/access_gate/abac.py +1081 -0
  35. accessgate-0.2.0a1/src/access_gate/adapters/__init__.py +5 -0
  36. accessgate-0.2.0a1/src/access_gate/adapters/django.py +177 -0
  37. accessgate-0.2.0a1/src/access_gate/engine.py +224 -0
  38. accessgate-0.2.0a1/src/access_gate/exceptions.py +13 -0
  39. accessgate-0.2.0a1/src/access_gate/integrity.py +262 -0
  40. accessgate-0.2.0a1/src/access_gate/mcp/__init__.py +1 -0
  41. accessgate-0.2.0a1/src/access_gate/mcp/agent-contract.json +419 -0
  42. accessgate-0.2.0a1/src/access_gate/mcp/server.py +378 -0
  43. accessgate-0.2.0a1/src/access_gate/policies.py +242 -0
  44. accessgate-0.2.0a1/src/access_gate/protocols.py +51 -0
  45. accessgate-0.2.0a1/src/access_gate/py.typed +0 -0
  46. accessgate-0.2.0a1/src/access_gate/schema/abac-policy-v1.json +113 -0
  47. accessgate-0.2.0a1/src/access_gate/strategies.py +28 -0
  48. accessgate-0.2.0a1/src/access_gate/types.py +315 -0
  49. accessgate-0.2.0a1/tests/fixtures/policies/v1/trusted-device.json +22 -0
  50. accessgate-0.2.0a1/tests/reference_authorization_model.py +29 -0
  51. accessgate-0.2.0a1/tests/test_abac.py +486 -0
  52. accessgate-0.2.0a1/tests/test_abac_edges.py +375 -0
  53. accessgate-0.2.0a1/tests/test_authorization_benchmark.py +41 -0
  54. accessgate-0.2.0a1/tests/test_django_adapter.py +176 -0
  55. accessgate-0.2.0a1/tests/test_engine.py +361 -0
  56. accessgate-0.2.0a1/tests/test_fuzz_harnesses.py +16 -0
  57. accessgate-0.2.0a1/tests/test_integrity.py +290 -0
  58. accessgate-0.2.0a1/tests/test_mcp_server.py +358 -0
  59. accessgate-0.2.0a1/tests/test_policies.py +177 -0
  60. accessgate-0.2.0a1/tests/test_properties.py +169 -0
  61. accessgate-0.2.0a1/tests/test_release_tools.py +61 -0
  62. accessgate-0.2.0a1/tests/test_types.py +119 -0
  63. accessgate-0.2.0a1/tools/__init__.py +1 -0
  64. accessgate-0.2.0a1/tools/authorization_benchmark.py +145 -0
  65. accessgate-0.2.0a1/tools/check_mutation_results.py +25 -0
  66. accessgate-0.2.0a1/tools/generate_sbom.py +173 -0
  67. accessgate-0.2.0a1/tools/normalize_sdist.py +63 -0
  68. accessgate-0.2.0a1/tools/verify_reproducible_build.py +78 -0
@@ -0,0 +1,82 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ### Added
11
+
12
+ - Deterministic policy and ordered policy-set fingerprints, externally supplied
13
+ signed-policy verification, and engine-owned policy-set decision identifiers.
14
+ - Property/differential tests, parser fuzz targets, scheduled mutation testing,
15
+ authorization performance budgets, and golden policy-format fixtures.
16
+ - Threat model, assurance case, immutable audit scope, secure-development,
17
+ strict policy migration, and evidence-preserving release procedures.
18
+ - Reproducible release verification, hash-locked build tools, SPDX artifact SBOM,
19
+ checksums, GitHub artifact attestations, and OpenSSF Scorecard analysis.
20
+ - Native async Django view support, including awaitable adapter resolvers and
21
+ denial handlers.
22
+ - Bounded policy documents, condition depth, condition count, MCP messages, and
23
+ collection evaluation workloads.
24
+ - Fail-closed decision-strategy error handling with an explicit
25
+ `strategy_error_mode`.
26
+
27
+ ### Changed
28
+
29
+ - Authorization inputs, literal values, and decision metadata are recursively
30
+ snapshotted into immutable containers.
31
+ - Operator registries attached to policies are frozen against later mutation.
32
+ - Collection operators now classify invalid operand shapes as indeterminate.
33
+ - Primitive collection intersection and subset checks use linear-time set
34
+ comparisons, and decisive condition branches short-circuit safely.
35
+ - Policies declaring immutable action targets are indexed by action while
36
+ preserving configured evaluation order; `decide_many()` provides ordered
37
+ batch evaluation with per-request auditing.
38
+ - GitHub Actions are commit-pinned, vulnerability auditing is blocking, and
39
+ package building is isolated from the PyPI publishing identity.
40
+
41
+ ### Security
42
+
43
+ - Policy literals are excluded from decision metadata by default; diagnostic
44
+ inclusion is an explicit opt-in.
45
+ - Signed envelopes reject tampering, unknown fields, unsupported versions,
46
+ non-canonical signatures, invalid verifier returns, and verifier failures.
47
+ - Empty role requirements and malformed policy names/actions are rejected at
48
+ construction time.
49
+ - Invalid policy results, invalid error-mode configuration, and strategy
50
+ exceptions can no longer create ambiguous authorization behavior.
51
+ - Invalid or oversized JSON-RPC messages no longer terminate the MCP server.
52
+
53
+ ## [0.2.0a1] - 2026-08-11
54
+
55
+ ### Added
56
+
57
+ - First-class ABAC across subject, resource, action, and context namespaces.
58
+ - Tri-state conditions with fail-closed missing-data and operator-error handling.
59
+ - Exact equality, membership, set-relation, numeric, and existence operators.
60
+ - Nested `AllOf`, `AnyOf`, and indeterminate-preserving `Not` conditions.
61
+ - Policy-local custom operator registries and custom operand protocol support.
62
+ - Versioned `access_gate.policy.v1` serialization and packaged JSON Schema.
63
+ - Strict serialized-policy parsing with unknown-field and duplicate-action rejection.
64
+ - Django action-attribute resolver support.
65
+ - Dependency-free MCP server with resources, prompts, operator discovery, schema
66
+ discovery, and real read-only policy validation.
67
+
68
+ ### Changed
69
+
70
+ - Package version advanced to `0.2.0a1`.
71
+
72
+ ## [0.1.0a1] - 2026-08-11
73
+
74
+ ### Added
75
+
76
+ - Framework-neutral subject, resource, request, and decision value objects.
77
+ - Default-deny, deny-overrides authorization engine.
78
+ - Configurable fail-closed policy and audit error handling.
79
+ - Policy, decision strategy, resolver, and audit sink extension protocols.
80
+ - Built-in authentication, role, ownership, and tenant-boundary policies.
81
+ - Optional Django view decorator adapter with replaceable resolvers and denial handler.
82
+ - Test, security, and trusted-publishing workflows.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Tunet Ltd
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,10 @@
1
+ include CHANGELOG.md
2
+ include LICENSE
3
+ include README.md
4
+ include SECURITY.md
5
+ recursive-include docs *.md
6
+ recursive-include fuzz *.json *.py
7
+ recursive-include mcp *.json *.md *.py
8
+ recursive-include requirements *.in *.txt
9
+ recursive-include tests *.json *.py
10
+ recursive-include tools *.py
@@ -0,0 +1,287 @@
1
+ Metadata-Version: 2.4
2
+ Name: AccessGate
3
+ Version: 0.2.0a1
4
+ Summary: Composable, default-deny authorization for Python applications.
5
+ Author-email: Tunet Ltd <alex@tunet.xyz>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/Tunet-xyz/access_gate
8
+ Project-URL: Documentation, https://github.com/Tunet-xyz/access_gate#readme
9
+ Project-URL: Repository, https://github.com/Tunet-xyz/access_gate
10
+ Project-URL: Issues, https://github.com/Tunet-xyz/access_gate/issues
11
+ Project-URL: Changelog, https://github.com/Tunet-xyz/access_gate/blob/main/CHANGELOG.md
12
+ Keywords: authorization,access-control,rbac,abac,security,policy
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Security
21
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
+ Requires-Python: >=3.10
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Provides-Extra: django
26
+ Requires-Dist: Django>=4.2; extra == "django"
27
+ Provides-Extra: dev
28
+ Requires-Dist: Django>=4.2; extra == "dev"
29
+ Requires-Dist: bandit>=1.7; extra == "dev"
30
+ Requires-Dist: build>=1.2; extra == "dev"
31
+ Requires-Dist: django-stubs>=4.2; extra == "dev"
32
+ Requires-Dist: hypothesis<7,>=6.112; extra == "dev"
33
+ Requires-Dist: mypy>=1.10; extra == "dev"
34
+ Requires-Dist: pip-audit>=2.7; extra == "dev"
35
+ Requires-Dist: pytest>=8.0; extra == "dev"
36
+ Requires-Dist: pytest-cov>=5.0; extra == "dev"
37
+ Requires-Dist: ruff>=0.5; extra == "dev"
38
+ Requires-Dist: setuptools>=68; extra == "dev"
39
+ Requires-Dist: tomli>=2; python_version < "3.11" and extra == "dev"
40
+ Requires-Dist: wheel; extra == "dev"
41
+ Provides-Extra: assurance
42
+ Requires-Dist: hypothesis<7,>=6.112; extra == "assurance"
43
+ Requires-Dist: mutmut==3.7.0; extra == "assurance"
44
+ Requires-Dist: pytest>=8.0; extra == "assurance"
45
+ Provides-Extra: fuzz
46
+ Requires-Dist: atheris==3.0.0; extra == "fuzz"
47
+ Dynamic: license-file
48
+
49
+ # AccessGate
50
+
51
+ Composable, default-deny RBAC, ABAC, and relationship-aware authorization for
52
+ Python applications.
53
+
54
+ AccessGate answers one question: **may this subject perform this action on
55
+ this resource in this context?** It provides a small framework-neutral kernel,
56
+ secure decision defaults, and extension interfaces for application-specific
57
+ identity, policy, audit, and framework integration.
58
+
59
+ It is complementary to authentication and session-security packages such as
60
+ SessionArmor. Those establish who the caller is and whether the session can be
61
+ trusted; AccessGate decides what that caller is allowed to do.
62
+
63
+ > **Status:** early alpha. Security controls and assurance evidence are being
64
+ > developed rigorously, but the public API may evolve before 1.0. This package
65
+ > has not received an independent audit or any government/NATO accreditation.
66
+
67
+ ## Design guarantees
68
+
69
+ - **Default deny:** abstention never grants access.
70
+ - **Deny overrides:** any explicit denial wins over grants by default.
71
+ - **Fail closed:** policy and audit failures deny access by default.
72
+ - **Exact matching:** built-in role, action, owner, and tenant comparisons never
73
+ use substring matching.
74
+ - **Safe ABAC:** missing attributes and operator failures are indeterminate,
75
+ cannot be negated into access, and deny by default.
76
+ - **Immutable inputs:** request attributes, literal values, and decision metadata
77
+ are recursively snapshotted before evaluation or audit.
78
+ - **Bounded evaluation:** serialized policies, condition trees, MCP messages, and
79
+ collection workloads have explicit denial-of-service limits.
80
+ - **Verifiable policy identity:** canonical fingerprints, ordered policy-set
81
+ fingerprints, externally verified signed envelopes, and engine-owned policy
82
+ deployment identifiers support change control without embedding key custody.
83
+ - **No expression evaluation:** declarative policies use registered operators
84
+ and mapping-only attribute traversal—never Python `eval` or object traversal.
85
+ - **Framework-neutral core:** no runtime dependencies and no ORM, token, or user
86
+ model assumptions.
87
+ - **Custom from day one:** policies, decision strategies, identity/resource
88
+ resolvers, audit sinks, and framework adapters are public extension points.
89
+
90
+ `Subject`, `Resource`, `AuthorizationRequest`, and `Decision` expose `to_dict()`
91
+ for JSON-friendly snapshots of their otherwise immutable data.
92
+
93
+ ## Installation
94
+
95
+ ```bash
96
+ pip install AccessGate
97
+ ```
98
+
99
+ For the optional Django adapter:
100
+
101
+ ```bash
102
+ pip install "AccessGate[django]"
103
+ ```
104
+
105
+ ## Quick start
106
+
107
+ ```python
108
+ from access_gate import (
109
+ AuthorizationEngine,
110
+ Resource,
111
+ RolePolicy,
112
+ Subject,
113
+ TenantBoundaryPolicy,
114
+ )
115
+
116
+ engine = AuthorizationEngine(
117
+ [
118
+ TenantBoundaryPolicy(actions={"incident.read"}),
119
+ RolePolicy({"incident_manager"}, actions={"incident.read"}),
120
+ ]
121
+ )
122
+
123
+ decision = engine.authorize(
124
+ Subject(
125
+ "user-42",
126
+ roles=frozenset({"incident_manager"}),
127
+ attributes={"tenant_id": "acme"},
128
+ ),
129
+ "incident.read",
130
+ Resource("incident", "INC-123", {"tenant_id": "acme"}),
131
+ )
132
+
133
+ assert decision.allowed
134
+ ```
135
+
136
+ Tenant boundaries deliberately do not grant access. They abstain when the
137
+ tenant matches and deny when it is missing or different; another policy must
138
+ positively allow the action.
139
+
140
+ ## Attribute-based access control
141
+
142
+ ABAC policies can compare subject, resource, action, and context attributes:
143
+
144
+ ```python
145
+ from access_gate import (
146
+ ActionAttribute,
147
+ AllOf,
148
+ AttributePolicy,
149
+ ContextAttribute,
150
+ ResourceAttribute,
151
+ SubjectAttribute,
152
+ )
153
+
154
+ document_access = AttributePolicy(
155
+ AllOf(
156
+ SubjectAttribute("department").equals(ResourceAttribute("department")),
157
+ SubjectAttribute("clearance").greater_than_or_equal(
158
+ ResourceAttribute("classification")
159
+ ),
160
+ ActionAttribute("risk").less_than_or_equal(3),
161
+ ContextAttribute("device.trusted").equals(True),
162
+ ),
163
+ actions={"document.read"},
164
+ name="document_access",
165
+ )
166
+ ```
167
+
168
+ Conditions support `AllOf`, `AnyOf`, and `Not`, multi-valued attributes, exact
169
+ membership and set relations, numeric comparisons, and explicit existence
170
+ checks. `Not` preserves indeterminate outcomes, so a missing attribute never
171
+ becomes a grant merely because a condition was negated.
172
+
173
+ Policies serialize to the versioned `access_gate.policy.v1` format:
174
+
175
+ ```python
176
+ from access_gate import dumps_policy, loads_policy
177
+
178
+ document = dumps_policy(document_access)
179
+ restored = loads_policy(document)
180
+ ```
181
+
182
+ For controlled deployments, identify the exact ordered policy set and pass that
183
+ identifier into the engine so every final and audited decision is attributable:
184
+
185
+ ```python
186
+ from access_gate import AuthorizationEngine, policy_set_fingerprint
187
+
188
+ policy_set_id = policy_set_fingerprint([document_access])
189
+ engine = AuthorizationEngine([document_access], policy_set_id=policy_set_id)
190
+ ```
191
+
192
+ `dumps_signed_policy()` and `loads_signed_policy()` accept application-supplied
193
+ signer/verifier protocols. AccessGate deliberately does not choose algorithms,
194
+ store keys, or implement a trust store. See
195
+ [`docs/POLICY_FORMAT.md`](docs/POLICY_FORMAT.md).
196
+
197
+ See [`docs/abac.md`](docs/abac.md) for the full data model, operator semantics,
198
+ customization rules, JSON format, and rollout guidance.
199
+
200
+ ## Application-defined policies
201
+
202
+ Implement the `Policy` protocol directly or wrap a function:
203
+
204
+ ```python
205
+ from access_gate import Decision, FunctionPolicy
206
+
207
+ def published_incidents(request):
208
+ resource = request.resource
209
+ if resource and resource.attributes.get("published") is True:
210
+ return Decision.allow("The incident is public.", policy="published")
211
+ return None
212
+
213
+ policy = FunctionPolicy("published", published_incidents)
214
+ ```
215
+
216
+ A policy returns an allow/deny `Decision`, or `None` to abstain. Custom decision
217
+ strategies can replace deny-overrides where a domain needs different semantics.
218
+ Policies exposing a validated `frozenset` in an `actions` attribute are indexed
219
+ by the engine and skipped for unrelated actions. `FunctionPolicy` supports this
220
+ directly through its `actions` argument. Use `decide_many()` for ordered batch
221
+ evaluation; every request still receives its own decision and audit event.
222
+
223
+ For mixed RBAC/ABAC grants, configure alternative grant policies to abstain on
224
+ a miss. Explicit boundary denials still override every grant.
225
+
226
+ ## Django adapter
227
+
228
+ ```python
229
+ from access_gate import AuthorizationEngine, RolePolicy
230
+ from access_gate.adapters.django import DjangoAuthorizer
231
+
232
+ authorizer = DjangoAuthorizer(
233
+ AuthorizationEngine([RolePolicy({"admin"}, actions={"users.list"})])
234
+ )
235
+
236
+ @authorizer.require("users.list")
237
+ def user_list(request):
238
+ ...
239
+ ```
240
+
241
+ The default Django subject resolver only reads `pk`, `is_authenticated`,
242
+ `access_gate_roles`, and `access_gate_attributes`. Real applications should
243
+ normally provide a resolver that validates and maps their exact identity claims.
244
+ The adapter also accepts custom context and action-attribute resolvers for ABAC.
245
+ Synchronous and asynchronous Django views are supported. Async views may use
246
+ awaitable subject, resource, context, action-attribute, and denial resolvers.
247
+
248
+ ## MCP server
249
+
250
+ AccessGate includes a dependency-free stdio MCP server:
251
+
252
+ ```bash
253
+ accessgate-mcp
254
+ ```
255
+
256
+ From a source checkout, run `python mcp/server.py`. The server exposes package
257
+ capabilities, authorization-model guidance, the ABAC JSON Schema, exact built-in
258
+ operator discovery, and read-only policy validation. It does not authenticate
259
+ users, evaluate production identity data, or change application policy.
260
+
261
+ ## Project boundary
262
+
263
+ AccessGate does not authenticate users, validate JWTs, secure sessions, query
264
+ application permissions, or decide how tenants and resources are represented.
265
+ Those are application and adapter responsibilities. See
266
+ [`docs/architecture.md`](docs/architecture.md) and
267
+ [`docs/customization.md`](docs/customization.md). AccessGate provides its own
268
+ policy format but does not claim wire compatibility with XACML, Cedar, Rego, or
269
+ other policy languages.
270
+
271
+ ## Development
272
+
273
+ ```bash
274
+ python -m pip install -e ".[dev]"
275
+ ruff check src tests
276
+ mypy src/access_gate
277
+ pytest --cov=access_gate --cov-report=term-missing
278
+ ```
279
+
280
+ Security reviewers should begin with the
281
+ [`threat model`](docs/THREAT_MODEL.md),
282
+ [`assurance case`](docs/SECURITY_ASSURANCE.md), and
283
+ [`audit scope`](docs/AUDIT_SCOPE.md).
284
+
285
+ ## License
286
+
287
+ MIT (c) Tunet Ltd.
@@ -0,0 +1,239 @@
1
+ # AccessGate
2
+
3
+ Composable, default-deny RBAC, ABAC, and relationship-aware authorization for
4
+ Python applications.
5
+
6
+ AccessGate answers one question: **may this subject perform this action on
7
+ this resource in this context?** It provides a small framework-neutral kernel,
8
+ secure decision defaults, and extension interfaces for application-specific
9
+ identity, policy, audit, and framework integration.
10
+
11
+ It is complementary to authentication and session-security packages such as
12
+ SessionArmor. Those establish who the caller is and whether the session can be
13
+ trusted; AccessGate decides what that caller is allowed to do.
14
+
15
+ > **Status:** early alpha. Security controls and assurance evidence are being
16
+ > developed rigorously, but the public API may evolve before 1.0. This package
17
+ > has not received an independent audit or any government/NATO accreditation.
18
+
19
+ ## Design guarantees
20
+
21
+ - **Default deny:** abstention never grants access.
22
+ - **Deny overrides:** any explicit denial wins over grants by default.
23
+ - **Fail closed:** policy and audit failures deny access by default.
24
+ - **Exact matching:** built-in role, action, owner, and tenant comparisons never
25
+ use substring matching.
26
+ - **Safe ABAC:** missing attributes and operator failures are indeterminate,
27
+ cannot be negated into access, and deny by default.
28
+ - **Immutable inputs:** request attributes, literal values, and decision metadata
29
+ are recursively snapshotted before evaluation or audit.
30
+ - **Bounded evaluation:** serialized policies, condition trees, MCP messages, and
31
+ collection workloads have explicit denial-of-service limits.
32
+ - **Verifiable policy identity:** canonical fingerprints, ordered policy-set
33
+ fingerprints, externally verified signed envelopes, and engine-owned policy
34
+ deployment identifiers support change control without embedding key custody.
35
+ - **No expression evaluation:** declarative policies use registered operators
36
+ and mapping-only attribute traversal—never Python `eval` or object traversal.
37
+ - **Framework-neutral core:** no runtime dependencies and no ORM, token, or user
38
+ model assumptions.
39
+ - **Custom from day one:** policies, decision strategies, identity/resource
40
+ resolvers, audit sinks, and framework adapters are public extension points.
41
+
42
+ `Subject`, `Resource`, `AuthorizationRequest`, and `Decision` expose `to_dict()`
43
+ for JSON-friendly snapshots of their otherwise immutable data.
44
+
45
+ ## Installation
46
+
47
+ ```bash
48
+ pip install AccessGate
49
+ ```
50
+
51
+ For the optional Django adapter:
52
+
53
+ ```bash
54
+ pip install "AccessGate[django]"
55
+ ```
56
+
57
+ ## Quick start
58
+
59
+ ```python
60
+ from access_gate import (
61
+ AuthorizationEngine,
62
+ Resource,
63
+ RolePolicy,
64
+ Subject,
65
+ TenantBoundaryPolicy,
66
+ )
67
+
68
+ engine = AuthorizationEngine(
69
+ [
70
+ TenantBoundaryPolicy(actions={"incident.read"}),
71
+ RolePolicy({"incident_manager"}, actions={"incident.read"}),
72
+ ]
73
+ )
74
+
75
+ decision = engine.authorize(
76
+ Subject(
77
+ "user-42",
78
+ roles=frozenset({"incident_manager"}),
79
+ attributes={"tenant_id": "acme"},
80
+ ),
81
+ "incident.read",
82
+ Resource("incident", "INC-123", {"tenant_id": "acme"}),
83
+ )
84
+
85
+ assert decision.allowed
86
+ ```
87
+
88
+ Tenant boundaries deliberately do not grant access. They abstain when the
89
+ tenant matches and deny when it is missing or different; another policy must
90
+ positively allow the action.
91
+
92
+ ## Attribute-based access control
93
+
94
+ ABAC policies can compare subject, resource, action, and context attributes:
95
+
96
+ ```python
97
+ from access_gate import (
98
+ ActionAttribute,
99
+ AllOf,
100
+ AttributePolicy,
101
+ ContextAttribute,
102
+ ResourceAttribute,
103
+ SubjectAttribute,
104
+ )
105
+
106
+ document_access = AttributePolicy(
107
+ AllOf(
108
+ SubjectAttribute("department").equals(ResourceAttribute("department")),
109
+ SubjectAttribute("clearance").greater_than_or_equal(
110
+ ResourceAttribute("classification")
111
+ ),
112
+ ActionAttribute("risk").less_than_or_equal(3),
113
+ ContextAttribute("device.trusted").equals(True),
114
+ ),
115
+ actions={"document.read"},
116
+ name="document_access",
117
+ )
118
+ ```
119
+
120
+ Conditions support `AllOf`, `AnyOf`, and `Not`, multi-valued attributes, exact
121
+ membership and set relations, numeric comparisons, and explicit existence
122
+ checks. `Not` preserves indeterminate outcomes, so a missing attribute never
123
+ becomes a grant merely because a condition was negated.
124
+
125
+ Policies serialize to the versioned `access_gate.policy.v1` format:
126
+
127
+ ```python
128
+ from access_gate import dumps_policy, loads_policy
129
+
130
+ document = dumps_policy(document_access)
131
+ restored = loads_policy(document)
132
+ ```
133
+
134
+ For controlled deployments, identify the exact ordered policy set and pass that
135
+ identifier into the engine so every final and audited decision is attributable:
136
+
137
+ ```python
138
+ from access_gate import AuthorizationEngine, policy_set_fingerprint
139
+
140
+ policy_set_id = policy_set_fingerprint([document_access])
141
+ engine = AuthorizationEngine([document_access], policy_set_id=policy_set_id)
142
+ ```
143
+
144
+ `dumps_signed_policy()` and `loads_signed_policy()` accept application-supplied
145
+ signer/verifier protocols. AccessGate deliberately does not choose algorithms,
146
+ store keys, or implement a trust store. See
147
+ [`docs/POLICY_FORMAT.md`](docs/POLICY_FORMAT.md).
148
+
149
+ See [`docs/abac.md`](docs/abac.md) for the full data model, operator semantics,
150
+ customization rules, JSON format, and rollout guidance.
151
+
152
+ ## Application-defined policies
153
+
154
+ Implement the `Policy` protocol directly or wrap a function:
155
+
156
+ ```python
157
+ from access_gate import Decision, FunctionPolicy
158
+
159
+ def published_incidents(request):
160
+ resource = request.resource
161
+ if resource and resource.attributes.get("published") is True:
162
+ return Decision.allow("The incident is public.", policy="published")
163
+ return None
164
+
165
+ policy = FunctionPolicy("published", published_incidents)
166
+ ```
167
+
168
+ A policy returns an allow/deny `Decision`, or `None` to abstain. Custom decision
169
+ strategies can replace deny-overrides where a domain needs different semantics.
170
+ Policies exposing a validated `frozenset` in an `actions` attribute are indexed
171
+ by the engine and skipped for unrelated actions. `FunctionPolicy` supports this
172
+ directly through its `actions` argument. Use `decide_many()` for ordered batch
173
+ evaluation; every request still receives its own decision and audit event.
174
+
175
+ For mixed RBAC/ABAC grants, configure alternative grant policies to abstain on
176
+ a miss. Explicit boundary denials still override every grant.
177
+
178
+ ## Django adapter
179
+
180
+ ```python
181
+ from access_gate import AuthorizationEngine, RolePolicy
182
+ from access_gate.adapters.django import DjangoAuthorizer
183
+
184
+ authorizer = DjangoAuthorizer(
185
+ AuthorizationEngine([RolePolicy({"admin"}, actions={"users.list"})])
186
+ )
187
+
188
+ @authorizer.require("users.list")
189
+ def user_list(request):
190
+ ...
191
+ ```
192
+
193
+ The default Django subject resolver only reads `pk`, `is_authenticated`,
194
+ `access_gate_roles`, and `access_gate_attributes`. Real applications should
195
+ normally provide a resolver that validates and maps their exact identity claims.
196
+ The adapter also accepts custom context and action-attribute resolvers for ABAC.
197
+ Synchronous and asynchronous Django views are supported. Async views may use
198
+ awaitable subject, resource, context, action-attribute, and denial resolvers.
199
+
200
+ ## MCP server
201
+
202
+ AccessGate includes a dependency-free stdio MCP server:
203
+
204
+ ```bash
205
+ accessgate-mcp
206
+ ```
207
+
208
+ From a source checkout, run `python mcp/server.py`. The server exposes package
209
+ capabilities, authorization-model guidance, the ABAC JSON Schema, exact built-in
210
+ operator discovery, and read-only policy validation. It does not authenticate
211
+ users, evaluate production identity data, or change application policy.
212
+
213
+ ## Project boundary
214
+
215
+ AccessGate does not authenticate users, validate JWTs, secure sessions, query
216
+ application permissions, or decide how tenants and resources are represented.
217
+ Those are application and adapter responsibilities. See
218
+ [`docs/architecture.md`](docs/architecture.md) and
219
+ [`docs/customization.md`](docs/customization.md). AccessGate provides its own
220
+ policy format but does not claim wire compatibility with XACML, Cedar, Rego, or
221
+ other policy languages.
222
+
223
+ ## Development
224
+
225
+ ```bash
226
+ python -m pip install -e ".[dev]"
227
+ ruff check src tests
228
+ mypy src/access_gate
229
+ pytest --cov=access_gate --cov-report=term-missing
230
+ ```
231
+
232
+ Security reviewers should begin with the
233
+ [`threat model`](docs/THREAT_MODEL.md),
234
+ [`assurance case`](docs/SECURITY_ASSURANCE.md), and
235
+ [`audit scope`](docs/AUDIT_SCOPE.md).
236
+
237
+ ## License
238
+
239
+ MIT (c) Tunet Ltd.