Continuous authorization for multi-agent AI systems.
Ruhusa means permission in Swahili.
LLMs decide what action to propose. Ruhusa determines whether they have permission to execute it.
Authority should narrow as agents delegate—not expand.
Ruhusa is an open-source authorization framework and research artifact for continuous, least-privilege authorization across AI agents, tools, and multi-agent workflows.
The framework separates agent reasoning from authorization:
Agent / LLM
|
| proposes an action
v
Ruhusa
|
| deterministic authorization
v
ALLOW | DENY | REQUIRE_APPROVAL
|
v
Protected Tool / API / Resource
Ruhusa is not an agent framework, workflow engine, identity provider, LLM gateway, or general-purpose IAM system. Its purpose is to provide a small, inspectable authorization boundary in which trusted policy and provenance determine whether an agent-proposed action may proceed.
Current package version: 0.7.0
Current milestone: v0.7 — production interfaces and durable persistence
Release status: stable release
v0.7 preserves the frozen v0.6 research behavior while adding stable dependency protocols and optional PostgreSQL-backed durable security state.
PostgreSQL-backed implementations are available for grants, revocations, invocations, trusted tool registrations, execution lifecycle, and audit.
Ruhusa remains pre-1.0. Public APIs and guarantees may still evolve before 1.0.
Ruhusa currently includes research implementations for:
- deterministic default-deny authorization
- least-privilege, multi-hop delegation
- delegation-chain identity continuity
- task-bound authority
- cross-task replay protection
- action, resource, and argument constraints
- human-approval decisions
- continuous grant revocation
- fail-closed policy and security-store failures
- hash-chained authorization audit records
- trusted canonical grant issuance via
InMemoryGrantStore - canonical grant-content integrity checks
- invocation provenance via
InMemoryInvocationStore - operation-bound invocation records
- invocation expiry
- tool identity and implementation identity via
InMemoryToolRegistry - canonical invocation verification for delegated and direct requests
- fail-closed handling of missing canonical tool identity when tool verification is required
- execution lifecycle state via
InMemoryExecutionStore - atomic process-local execution claims and
ExecutionPermit - replay blocking after a claimed invocation is completed
- safe release when failure is known to occur before an external side effect
- fail-closed
UNKNOWNstate for uncertain external outcomes - execution-time authorization revalidation before protected side effects
- terminal
CANCELLEDstate when live authority becomes invalid before execution - stale-claim quarantine from
CLAIMEDto fail-closedUNKNOWN - explicit
UNKNOWNreconciliation toCOMPLETEDorAVAILABLEthrough trusted recovery infrastructure - process-local single-winner reconciliation semantics
- stale-permit protection across recovered execution attempts
- adversarial attack benchmarks
Production persistence capabilities include:
- stable persistence protocols independent of concrete backends
- optional PostgreSQL installation through
ruhusa[postgres] - immutable PostgreSQL grant registration
- monotonic PostgreSQL revocation
- immutable PostgreSQL invocation provenance
- immutable trusted tool implementation registration
- database-authoritative execution claims
- cross-process single-winner execution semantics
- stale-permit execution fencing
- durable
UNKNOWNexecution recovery - serialized PostgreSQL audit writes
- tamper-evident PostgreSQL audit-chain verification
- fail-closed PostgreSQL backend failure behavior
Not every configuration provides the same security guarantees. Self-asserted identity fields in weak mode remain intentionally benchmarked as forgeable.
Ruhusa uses an attack-first development process.
Define attack
|
v
Run against baseline
|
+---- blocked ----> document existing invariant
|
+---- succeeds ---> record GAP
|
v
identify root cause
|
v
implement smallest
targeted control
|
v
rerun attack
A passing test can represent either a blocked attack or a successfully reproduced vulnerability. See docs/attack-benchmarks.md for the GAP, BLOCKS, and CONTROL conventions.
The central architectural distinction is between agent-controlled claims and trusted authorization state.
UNTRUSTED / SELF-ASSERTED
--------------------------------
LLM reasoning
AuthorizationRequest fields
caller identity claims
tool identity claims
action/resource/arguments
presented delegation objects
TRUSTED / CANONICAL
--------------------------------
Ruhusa authorization core
PolicyStore
GrantStore
RevocationStore
InvocationStore
ToolRegistry
ExecutionStore
AuditLog
Reference implementations:
- in-memory stores for local/research use
- PostgreSQL stores for durable production state
trusted orchestration state
A recurring research finding is:
Security-relevant identity claims must be grounded in trusted provenance, not merely represented as matching strings inside an agent-controlled request.
This principle emerged first with grant provenance in v0.4 and now extends to invocation and tool identity in v0.5.
Without an invocation store, Ruhusa may evaluate self-asserted request fields such as:
invoking_principal_id
tool_id
implementation_id
These checks can detect missing or obviously unregistered values, but a compromised executing agent can forge values that look legitimate.
Weak mode is therefore a compatibility and consistency mode, not a trusted provenance boundary.
With a trusted InvocationStore, a trusted orchestration layer creates a canonical InvocationRecord that binds:
invoker
executor
task
action
resource
arguments digest
tool identity
implementation identity
recorded_at
expires_at
Canonical invocation verification applies to both delegated and direct/non-delegated requests.
For delegated requests, Ruhusa additionally verifies that the canonical invoking principal matches the grantor of the leaf delegation grant.
When a tool registry is configured, canonical tool identity must be present and trusted for the requested action.
The current Ruhusa.authorize() flow is approximately:
AuthorizationRequest
|
v
1. Task validity
|
v
2. Structural delegation validation
- chain origin
- identity continuity
- task binding
- temporal validity
- scope attenuation
|
v
3. Canonical invocation verification
- applies to direct and delegated requests
- invoker
- executor
- task
- action
- resource
- arguments digest
- expiry
|
v
4. Tool identity verification
- canonical runtime identity
- fail closed if required identity is missing
- registered implementation
- action permitted by implementation
|
v
5. Canonical grant provenance
|
v
6. Revocation
|
v
7. Effective delegated scope
|
v
8. Policy evaluation
|
v
ALLOW | DENY | REQUIRE_APPROVAL
|
v
Audit decision
See docs/architecture.md for the detailed architecture.
The current benchmark contains 44 implemented experiments across delegation, provenance, tool identity, execution lifecycle, execution-time authority, and recovery.
Experiments 18–27 preserve the v0.5 exact-replay baseline and add a separate execution lifecycle.
Key observed results:
Exp 18 exact replay through authorize() GAP
Exp 19 second execution claim BLOCKS
Exp 20 concurrent process-local claim race BLOCKS
Exp 21 replay after completion BLOCKS
Exp 22 known pre-side-effect release and retry CONTROL
Exp 23 uncertain external outcome retry BLOCKS
Exp 24 stale/forged execution permit BLOCKS
Exp 25 expired execution authority BLOCKS
Exp 26 authorization DENY does not consume execution CONTROL
Exp 27 execution-store failure BLOCKS
The research distinction is:
Operation-bound provenance is not execution uniqueness.
Ruhusa.authorize() intentionally remains non-consuming so the v0.5 baseline stays reproducible. Side-effecting integrations opt into the execution lifecycle through ExecutionController.
Experiments 28–35 test whether authority that was valid when execution was claimed is still valid immediately before an external side effect.
Observed results:
Exp 28 revocation before execution claim BLOCKS
Exp 29 revocation after claim without revalidation GAP
Exp 30 post-claim revocation with revalidation BLOCKS
Exp 31 task expiry after claim BLOCKS
Exp 32 policy change after claim BLOCKS
Exp 33 stale/forged permit at revalidation BLOCKS
Exp 34 execution-state failure during revalidation BLOCKS
Exp 35 revocation after successful revalidation GAP
Experiment 35 is intentionally preserved as a residual TOCTOU boundary. v0.6-B narrows the authorization-to-use window but does not make authorization state atomic with a remote side effect.
The research distinction is:
Authorization-time validity is not execution-time validity, and execution-time revalidation is not atomic authorization plus side effect.
Experiments 36–44 evaluate stale execution claims and explicit recovery from UNKNOWN.
Observed results:
Exp 36 stale CLAIMED execution becomes UNKNOWN BLOCKS unsafe retry
Exp 37 live claim cannot be recovered before threshold BLOCKS claim stealing
Exp 38 confirmed side effect resolves UNKNOWN COMPLETED / replay blocked
Exp 39 confirmed no side effect permits a fresh claim CONTROL
Exp 40 reconciliation outside UNKNOWN BLOCKS
Exp 41 concurrent reconciliation BLOCKS / one winner
Exp 42 old permit after recovery BLOCKS
Exp 43 invalid stale-recovery window BLOCKS invalid configuration
Exp 44 empty reconciliation reason BLOCKS malformed recovery
v0.6-C establishes a fail-closed recovery lifecycle, but it does not authenticate the source of a reconciliation outcome. reconcile_unknown() is therefore a trusted-infrastructure API: an agent must not be allowed to self-assert SIDE_EFFECT_CONFIRMED or SIDE_EFFECT_NOT_APPLIED.
The research distinction is:
Execution-attempt uniqueness does not imply side-effect uniqueness, and safe recovery requires trustworthy knowledge of the external outcome.
Frozen v0.5.0 release baseline:
ruff format: 27 files left unchanged
ruff check: All checks passed
pytest: 91 passed
build: dist/ruhusa-0.5.0.tar.gz
build: dist/ruhusa-0.5.0-py3-none-any.whl
Validated v0.6-C development baseline before the release-version bump:
ruff check: All checks passed
pytest: 118 passed
build: dist/ruhusa-0.5.0.tar.gz
build: dist/ruhusa-0.5.0-py3-none-any.whl
The final v0.6.0 release validation is recorded below.
ruff check: All checks passed
pytest: 118 passed
build: dist/ruhusa-0.6.0.tar.gz
build: dist/ruhusa-0.6.0-py3-none-any.whl
The v0.6 threat model is frozen at docs/threat-model/v0.6.md.
The repository separates architecture, security assumptions, and experimental evidence:
docs/architecture.md— how Ruhusa is structureddocs/threat-model.md— current trust assumptions, threats, and security claimsdocs/attack-benchmarks.md— executable adversarial experiments and outcomesdocs/threat-model/v0.4.md— frozen v0.4 threat-model snapshotdocs/threat-model/v0.5.md— frozen v0.5 threat-model snapshotdocs/threat-model/v0.6.md— frozen v0.6 threat-model snapshotdocs/research/v0.6-A-execution-lifecycle.md— execution-lifecycle research notedocs/research/v0.6-B-execution-time-authority.md— execution-time authority research notedocs/research/v0.6-C-idempotency-recovery.md— uncertain-execution recovery research notedocs/architecture/v0.1.md— historical v0.1 architecture
- Python 3.12+
- PostgreSQL for durable production persistence
uvfor repository development
Core installation:
pip install ruhusaPostgreSQL persistence:
pip install "ruhusa[postgres]"The core package does not require PostgreSQL dependencies.
Clone and install:
git clone https://github.com/Claire56/ruhusa.git
cd ruhusa
uv syncRun the test suite:
uv run pytestRun the attack benchmarks:
uv run pytest tests/test_replanning_attacks.py -v
uv run pytest tests/test_tool_identity_attacks.py -v
uv run pytest tests/test_execution_lifecycle_attacks.py -v
uv run pytest tests/test_execution_time_authority_attacks.py -v
uv run pytest tests/test_idempotency_recovery.py -vFormat and lint:
uv run ruff format .
uv run ruff check .Build:
uv buildBefore committing:
uv run ruff format .
uv run ruff check .
uv run pytest
uv buildAt its simplest:
from ruhusa import Ruhusa
gate = Ruhusa(policy_store=policies)
decision = gate.authorize(request)
if decision.allowed:
execute_tool()Strong provenance requires a trusted orchestration layer to populate canonical runtime state rather than allowing executing agents to self-assert that state.
For side-effecting integrations using the v0.6 execution lifecycle:
from ruhusa import ExecutionController
controller = ExecutionController(gate)
claim = controller.begin(request)
if claim.allowed and claim.permit is not None:
live = controller.revalidate_before_execution(request, claim.permit)
if live.allowed:
# Execute the protected side effect here.
controller.complete(claim.permit)If the external outcome becomes uncertain after the request is sent, use mark_unknown() rather than automatically retrying. v0.6 does not claim atomic authorization plus external side effect or exactly-once downstream execution.
Established default-deny authorization, scoped delegation, human-approval decisions, fail-closed policy evaluation, and hash-chained audit logging.
Added mid-workflow revocation, fail-closed revocation checks, and earlier emergency revocation semantics.
Bound grants to originating tasks and blocked cross-task replay and chain splicing.
Introduced adversarial replanning tests, discovered the fresh-grant-remint gap, and added canonical grant issuance and content-integrity verification.
Added trusted invocation provenance, tool/implementation identity, operation binding, direct-request mediation, and fail-closed canonical tool identity verification.
The v0.5 milestone intentionally retains one documented limitation: exact same-operation invocation replay is not prevented by one-shot authorization consumption.
v0.6-A adds execution claims, replay controls, completion/unknown/cancelled lifecycle state, and process-local concurrency protection.
v0.6-B adds execution-time revalidation so revocation, task expiry, and policy changes that occur after a claim can be observed immediately before use.
v0.6-C adds fail-closed stale-claim recovery and explicit reconciliation of UNKNOWN outcomes. A confirmed external effect becomes COMPLETED; confirmed non-execution may return the invocation to AVAILABLE for a newly authorized claim.
The remaining boundary is deliberate: Ruhusa does not make authorization, recovery state, and a remote side effect transactionally atomic. It also does not authenticate reconciliation evidence, provide durable distributed recovery, or guarantee downstream idempotency or exactly-once execution.
Introduced stable persistence protocols and optional PostgreSQL-backed implementations for security state.
v0.7 adds database-authoritative execution fencing, durable uncertain execution recovery, serialized audit-chain persistence, and explicit backend failure contracts while preserving the frozen v0.6 research artifact.
The working research question is:
Under what workflow transformations does authorization cease to represent the authority originally delegated by a principal, and what runtime invariants are required to preserve that authority across delegation, revocation, replanning, concurrency, tool invocation, and information propagation?
The research progression now extends beyond provenance into execution semantics:
identity claim != provenance
provenance != complete mediation
operation binding != execution uniqueness
authorization-time validity != execution-time validity
execution-time revalidation != atomic authorization + side effect
Current and future research areas include:
- authorization/execution atomicity
- downstream idempotency and side-effect deduplication
- distributed concurrency and durable execution state
- authenticated/provenanced recovery evidence
- durable reconciliation of uncertain outcomes
- authority leases / epochs and TOCTOU
- authorization propagation across branch/merge workflows
- multi-agent collusion
- descendant revocation
- durable approval evidence
- cryptographic agent and tool identity
- information provenance and derived-data authority
- LangGraph, MCP, and A2A integrations
- external PDP and IAM integrations
See CONTRIBUTING.md.
See SECURITY.md.
See docs/production/release-readiness.md for the v0.7 production deployment requirements and explicit non-guarantees.
Apache License 2.0.
