RBACX
Universal RBAC/ABAC/ReBAC policy engine for Python with a clean core, policy sets, a compact condition DSL (including time ops), and adapters for common web frameworks.
Features
- Algorithms:
deny-overrides(default),permit-overrides,first-applicable - Conditions:
==,!=,<,<=,>,>=,contains,in,hasAll,hasAny,startsWith,endsWith,before,after,between - Role shorthand:
"roles": ["admin", "editor"]on any rule — sugar forhasAnyonsubject.roles - Explainability:
decision,reason,rule_id/last_rule_id,obligations - Policy sets: combine multiple policies with the same algorithms
- Hot reload: file/HTTP/S3 sources with ETag and a polling manager
- Types & lint: mypy-friendly core, Ruff-ready
- AI Policy Authoring (
rbacx[ai]): generate, refine, and explain policies using any OpenAI-compatible LLM
Installation
pip install rbacx
Optional extras include adapters, metrics, YAML support, and AI policy authoring.
Quickstart
from rbacx import Action, Context, Guard, Subject, Resource
policy = {
"algorithm": "deny-overrides",
"rules": [
{
"id": "doc_read",
"effect": "permit",
"actions": ["read"],
"resource": {"type": "doc", "attrs": {"visibility": ["public", "internal"]}},
"roles": ["reader", "admin"], # shorthand: hasAny on subject.roles
# Or using the explicit condition syntax:
# "condition": {"hasAny": [ {"attr": "subject.roles"}, ["reader", "admin"] ]},
"obligations": [ {"type": "require_mfa"} ]
},
{"id": "doc_deny_archived", "effect": "deny", "actions": ["*"],
"resource": {"type": "doc", "attrs": {"archived": True}}}
],
}
g = Guard(policy)
d = g.evaluate_sync(
subject=Subject(id="u1", roles=["reader"]),
action=Action("read"),
resource=Resource(type="doc", id="42", attrs={"visibility": "public"}),
context=Context(attrs={"mfa": True}),
)
assert d.allowed is True
assert d.effect == "permit"
print(d.reason, d.rule_id) # "matched", "doc_read"
Decision schema
decision:"permit"or"deny"reason: one of"matched","explicit_deny","action_mismatch","condition_mismatch","condition_type_mismatch","resource_mismatch","no_match","obligation_failed"rule_idandlast_rule_id(both included for compatibility;last_rule_idis the matched rule id)policy_id(present for policy sets;Nonefor single policies)obligations: list passed to the obligation checker (if a permit was gated)- (optional)
challenge: present when an authentication/step-up is required (e.g., for MFA); may be used to return401with the appropriate challenge header
Policy sets
Default algorithm is:
from rbacx.core.policyset import decide as decide_policyset
policyset = {"algorithm":"deny-overrides", "policies":[ policy, {"rules":[...]} ]}
result = decide_policyset(policyset, {"subject":..., "action":"read", "resource":...})
If you want to test, try this:
from rbacx.core.policyset import decide as decide_policyset
# example set of policies
policyset = {
"algorithm": "deny-overrides",
"policies": [
{"rules": [
{"id": "allow_public_read", "effect": "permit", "actions": ["read"],
"resource": {"type": "doc", "attrs": {"visibility": ["public"]}}}
]},
{"rules": [
{"id": "deny_archived", "effect": "deny", "actions": ["*"],
"resource": {"type": "doc", "attrs": {"archived": True}}}
]},
],
}
# example request
req = {
"subject": {"id": "u1", "roles": ["reader"]},
"action": "read",
"resource": {"type": "doc", "id": "42", "attrs": {"visibility": "public", "archived": False}}, # can try: would be `deny` if archived `True`
"context": {},
}
res = decide_policyset(policyset, req)
print(res.get("decision", res)) # -> "permit"
Hot reloading
Default algorithm is:
from rbacx import Guard, HotReloader
from rbacx.store import FilePolicySource
guard = Guard(policy={})
mgr = HotReloader(guard, FilePolicySource("policy.json"), initial_load=...)
mgr.check_and_reload() # initial load
mgr.start(10) # background polling thread
If you want to test, try this:
⚠️ Important: this example creates a file on disk. You also can rewrite it with TempFile (tempfile.NamedTemporaryFile)
import json
import time
from rbacx import Action, Context, Guard, HotReloader, Resource, Subject
from rbacx.store import FilePolicySource
# create a tiny policy file next to the script
policy_path = "policy.json"
json.dump({
"algorithm": "deny-overrides",
"rules": [{
"id": "allow_public_read", "effect": "permit", "actions": ["read"],
"resource": {"type": "doc", "attrs": {"visibility": ["public"]}}
}]
}, open(policy_path, "w", encoding="utf-8"))
guard = Guard({})
mgr = HotReloader(guard, FilePolicySource(policy_path), initial_load=True)
mgr.check_and_reload() # initial load
print(guard.evaluate_sync(
subject=Subject(id="u1", roles=["reader"]),
action=Action("read"),
resource=Resource(type="doc", id="1", attrs={"visibility": "public"}),
context=Context(),
).effect) # -> "permit"
# update policy and wait 3 second for reload
json.dump({
"algorithm": "deny-overrides",
"rules": [{"id": "deny_all", "effect": "deny", "actions": ["*"], "resource": {"type": "doc"}}]
}, open(policy_path, "w", encoding="utf-8"))
mgr.start(3) # starting polling
time.sleep(3)
print(guard.evaluate_sync(
subject=Subject(id="u1", roles=["reader"]),
action=Action("read"),
resource=Resource(type="doc", id="1", attrs={"visibility": "public"}),
context=Context(),
).effect) # -> "deny"
Quick links
Packaging
- We ship
py.typedso type checkers pick up annotations. - Standard PyPA flow:
python -m build, thentwine uploadto (Test)PyPI.
License
MIT
Metadata
Release files for rbacx 1.18.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| rbacx-1.18.0.tar.gz | 92.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| rbacx-1.18.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 207.9 kB
Release files / rbacx-1.18.0.tar.gz
| Download URL | rbacx-1.18.0.tar.gz |
|---|---|
| Size | 92.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e45232ee9b3328a34819b8869c8aeb18e7d9e1d5a93f370a2ba762435832e1aa
|
|
BLAKE2b-256 checksum How to use checksums |
b8cc05e7b32cd0d060c40c846577eccb43972b6044ca8f1a1e6239af3608bf64
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Apr 12, 2026.
Transparency logRelease files / rbacx-1.18.0-py3-none-any.whl
| Download URL | rbacx-1.18.0-py3-none-any.whl |
|---|---|
| Size | 115.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
9cf251ea3f02f0ae8e3c8337c7ab05d1a2da9be86047f4a3d3eece86357f5ec1
|
|
BLAKE2b-256 checksum How to use checksums |
da905b53bd15e7f8dfb8a70add067b949bf6d2611581b07f962bf0190a9ac615
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Apr 12, 2026.
Transparency log