Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Authorization

Nibli ships a built-in authorization surface: a versioned KR policy, fail-closed vocabulary, and a warm Authorizer that answers with ordinary entailment. Allow means the engine returned TRUE for authorized(...) — not a learned score, not an LLM.

Code lives in nibli-auth (native), nibli:engine@0.7.0 authorizer (WASM component export), optional Rust features (axum, async-graphql, juniper), and nibli-auth-py / python/nibli_auth (PyO3).

This page is code-derived (crates, policy file, examples). It is not the Orange AVA book manuscript.

Ontology

PredicatePlacesRole
authorizedagent, action, resourceDecision head — “may perform action on resource”
ownsowner, owned, basisExisting corpus; ownership facts
has_roleagent, roleRole assignment
in_tenantagent, tenantTenancy of a principal
resourceresourceDeclare a protected resource (needed for admin rules)
resource_tenantresource, tenantResource’s tenant
visible_attragent, resource, attrField-level (reserved for finer policies; v0.1 masking uses row can)
agentagentOptional sort for principals

Do not use as auth heads (corpus collisions):

NameWhy
canTin/can (lante), not permission
fieldAgricultural field (foldi)
principal“Chief among” (ralju) — prefer agent

Actions, roles, attrs are KR quoted strings: "read", "update", "admin", "title".

Entities (Agents & Resources) can be specified as:

  • Capitalized Names: Alice, Doc1, User1
  • Quoted Strings: "1", "2", "user_123" (required when IDs are numeric strings or lowercase).
owns(Alice, Doc1).                  # Capitalized name constants
owns("101", "202").                 # Quoted string IDs
authorized(Alice, "update", Doc1).   # query shape
has_role(Carol, "admin").
resource(Doc1).

Fail-Closed Lexicon & Custom Domain Roles (NIBLI_KR §13)

Nibli enforces a fail-closed vocabulary: uncurated predicate names (e.g., has_codename, program_role) are rejected at compile time rather than silently defaulting to an arity-2 guess. This prevents security bugs caused by typos (e.g., is_admim).

To model custom roles, permissions, or domain attributes without changing Rust code:

  • Use has_role(Agent, "custom_permission_or_codename") with quoted string tokens (e.g., "read_application", "create_invoice"). Quoted string tokens can be any arbitrary string.
  • Use in_tenant(Agent, "group_id") for custom tenancy/organization groups.
has_role(Alice, "read_application").
in_tenant(Alice, "org_sales_east").

Builtin policy

File: nibli-auth/policy/auth-0.1.0.nibli
Version constant: nibli_auth::POLICY_VERSION ("0.1.0").

Shipped rules (summary):

  • Ownerowns($a, $r) ⇒ authorized for "read" and "update".
  • Adminhas_role($a, "admin") & resource($r) ⇒ read/update that resource.
  • Tenant — same tenant on agent and resource ⇒ "read" only.

Rule writing tip: every variable that appears in the conclusion should be constrained by a positive body condition (or bound by a ground query). Free conclusion-only variables do not fire under backward chaining here (admin rules therefore mention resource($r)).

Overlay extra KR with Authorizer::load_policy(Some(extra)) or per-call context_kr.

Efficiency model

RequirementMechanism
No engine per requestOne warm authorizer (or one per OS thread)
Policy onceload_policy / thread-local first use
Ephemeral request factscontext_kr asserted then retracted
Cheap hot pathcan / can_any / allowed_fieldsno proof
Batch authorizationcan_any — derives roles/tenants once per batch over context
Proofs opt-inexplain only
Decision cacheKeyed by policy version + agent/action/object + context hash

Multi-threaded servers: the KB uses RefCell and is !Send. Do not put Authorizer in axum::State or Arc<Mutex<_>>. Use:

  • Rust: nibli_auth::tls (thread-local warm policy).
  • Python: same TLS behind nibli_auth_native (each OS thread warms once).

Cross-thread durable owns/roles belong in the app database and should be passed as context_kr each call (see the examples).

UNKNOWN on the hot path is treated as deny (allowed=false); only engine TRUE allows.

Rust API

#![allow(unused)]
fn main() {
use nibli_auth::{Authorizer, tls};

let mut auth = Authorizer::new();
auth.load_policy(None)?;
auth.assert_facts("owns(Alice, Doc1).")?;
let d = auth.can("Alice", "update", "Doc1", "")?;
assert!(d.allowed);

// Multi-thread / axum workers:
let d = tls::can("Alice", "read", "Doc1", "owns(Alice, Doc1).")?;
let batch = tls::can_any("Alice", "read", &["Doc1", "Doc2"], "owns(Alice, Doc1).")?;
let fields = tls::allowed_fields("Alice", "read", "Doc1", "owns(Alice, Doc1).", &["title", "body"])?;
}
  • can — row-level allow/deny (Decision).
  • can_any — batch queryset/row evaluation returning Vec<(String, bool)>.
  • allowed_fields — v0.1: if row can allows, return all candidates (serializer masking).
  • explain — same as can plus optional proof JSON.

Optional Cargo features on nibli-auth: axum (Agent header extractor, require, can_any, field_mask), async-graphql, juniper.

WIT (WASM component)

Package nibli:engine@0.7.0 exports authorizer alongside engine (logical auth surface v0.1 in the single shipping package).

WITNotes
load-policyBuiltin (+ optional extra KR)
can / can-any / allowed-fields / explainSame semantics as Rust
Parameter objectProtected resource id — WIT reserves the keyword resource

Native and guest both wrap the same policy file via nibli-auth. engine.session and authorizer.session do not share a KB in v0.1.

Python API

just build-auth-py    # maturin develop into .venv-auth
just test-auth-py
from nibli_auth import can, can_any, allowed_fields, explain

d = can("Alice", "update", "Doc1", "owns(Alice, Doc1).")
assert d.allowed
results = can_any("Alice", "read", ["Doc1", "Doc2"], "owns(Alice, Doc1).")
fields = allowed_fields("Alice", "read", "Doc1", ["title", "body"], "owns(Alice, Doc1).")

Package layout: extension nibli_auth_native (PyO3); helpers under python/nibli_auth/ (fastapi_ext, optional drf, strawberry_ext, graphene_ext, spectacular).

Identity header in demos: X-Agent (not a production auth scheme).

Examples (same policy)

ExampleCommandPort
auth-axumjust run-auth-axum3001
auth-fastapijust run-auth-fastapi3002

Scenarios: Alice owner, Bob stranger, Carol admin, Dave same-tenant read-only.

Extism

Extism is not the primary interface. A future PDK could wrap the same can / policy model; it is not implemented. Prefer the native Component Model / WIT path and in-process PyO3.

Versioning

LayerVersion
Policy file / POLICY_VERSION0.1.0
WIT packagenibli:engine@0.7.0 (authorizer export)
Crate nibli-authworkspace 0.1.0 (unpublished)

Do not invent predicates outside the fail-closed lexicon. Bump the policy version string when shipping incompatible rule changes.

Tests

just test-auth          # Rust core + axum/graphql/juniper features
just check-auth-axum
just test-auth-py       # requires maturin / .venv-auth (local)
just docs               # includes this chapter