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.
- accessgate-0.2.0a1/CHANGELOG.md +82 -0
- accessgate-0.2.0a1/LICENSE +21 -0
- accessgate-0.2.0a1/MANIFEST.in +10 -0
- accessgate-0.2.0a1/PKG-INFO +287 -0
- accessgate-0.2.0a1/README.md +239 -0
- accessgate-0.2.0a1/SECURITY.md +58 -0
- accessgate-0.2.0a1/docs/AUDIT_SCOPE.md +30 -0
- accessgate-0.2.0a1/docs/POLICY_FORMAT.md +27 -0
- accessgate-0.2.0a1/docs/RELEASE.md +22 -0
- accessgate-0.2.0a1/docs/SECURE_DEVELOPMENT.md +27 -0
- accessgate-0.2.0a1/docs/SECURITY_ASSURANCE.md +47 -0
- accessgate-0.2.0a1/docs/THREAT_MODEL.md +63 -0
- accessgate-0.2.0a1/docs/abac.md +156 -0
- accessgate-0.2.0a1/docs/architecture.md +93 -0
- accessgate-0.2.0a1/docs/customization.md +84 -0
- accessgate-0.2.0a1/fuzz/corpus/mcp/request.json +1 -0
- accessgate-0.2.0a1/fuzz/corpus/policy/valid.json +1 -0
- accessgate-0.2.0a1/fuzz/fuzz_mcp_json.py +28 -0
- accessgate-0.2.0a1/fuzz/fuzz_policy_json.py +29 -0
- accessgate-0.2.0a1/mcp/README.md +37 -0
- accessgate-0.2.0a1/mcp/agent-contract.json +419 -0
- accessgate-0.2.0a1/mcp/server.py +14 -0
- accessgate-0.2.0a1/pyproject.toml +102 -0
- accessgate-0.2.0a1/requirements/build.in +5 -0
- accessgate-0.2.0a1/requirements/build.txt +596 -0
- accessgate-0.2.0a1/setup.cfg +4 -0
- accessgate-0.2.0a1/src/AccessGate.egg-info/PKG-INFO +287 -0
- accessgate-0.2.0a1/src/AccessGate.egg-info/SOURCES.txt +66 -0
- accessgate-0.2.0a1/src/AccessGate.egg-info/dependency_links.txt +1 -0
- accessgate-0.2.0a1/src/AccessGate.egg-info/entry_points.txt +2 -0
- accessgate-0.2.0a1/src/AccessGate.egg-info/requires.txt +28 -0
- accessgate-0.2.0a1/src/AccessGate.egg-info/top_level.txt +1 -0
- accessgate-0.2.0a1/src/access_gate/__init__.py +118 -0
- accessgate-0.2.0a1/src/access_gate/abac.py +1081 -0
- accessgate-0.2.0a1/src/access_gate/adapters/__init__.py +5 -0
- accessgate-0.2.0a1/src/access_gate/adapters/django.py +177 -0
- accessgate-0.2.0a1/src/access_gate/engine.py +224 -0
- accessgate-0.2.0a1/src/access_gate/exceptions.py +13 -0
- accessgate-0.2.0a1/src/access_gate/integrity.py +262 -0
- accessgate-0.2.0a1/src/access_gate/mcp/__init__.py +1 -0
- accessgate-0.2.0a1/src/access_gate/mcp/agent-contract.json +419 -0
- accessgate-0.2.0a1/src/access_gate/mcp/server.py +378 -0
- accessgate-0.2.0a1/src/access_gate/policies.py +242 -0
- accessgate-0.2.0a1/src/access_gate/protocols.py +51 -0
- accessgate-0.2.0a1/src/access_gate/py.typed +0 -0
- accessgate-0.2.0a1/src/access_gate/schema/abac-policy-v1.json +113 -0
- accessgate-0.2.0a1/src/access_gate/strategies.py +28 -0
- accessgate-0.2.0a1/src/access_gate/types.py +315 -0
- accessgate-0.2.0a1/tests/fixtures/policies/v1/trusted-device.json +22 -0
- accessgate-0.2.0a1/tests/reference_authorization_model.py +29 -0
- accessgate-0.2.0a1/tests/test_abac.py +486 -0
- accessgate-0.2.0a1/tests/test_abac_edges.py +375 -0
- accessgate-0.2.0a1/tests/test_authorization_benchmark.py +41 -0
- accessgate-0.2.0a1/tests/test_django_adapter.py +176 -0
- accessgate-0.2.0a1/tests/test_engine.py +361 -0
- accessgate-0.2.0a1/tests/test_fuzz_harnesses.py +16 -0
- accessgate-0.2.0a1/tests/test_integrity.py +290 -0
- accessgate-0.2.0a1/tests/test_mcp_server.py +358 -0
- accessgate-0.2.0a1/tests/test_policies.py +177 -0
- accessgate-0.2.0a1/tests/test_properties.py +169 -0
- accessgate-0.2.0a1/tests/test_release_tools.py +61 -0
- accessgate-0.2.0a1/tests/test_types.py +119 -0
- accessgate-0.2.0a1/tools/__init__.py +1 -0
- accessgate-0.2.0a1/tools/authorization_benchmark.py +145 -0
- accessgate-0.2.0a1/tools/check_mutation_results.py +25 -0
- accessgate-0.2.0a1/tools/generate_sbom.py +173 -0
- accessgate-0.2.0a1/tools/normalize_sdist.py +63 -0
- 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.
|