Audit mode¶
RBACX can log authorization decisions for observability without enforcing them.
How it works¶
- Pass a
DecisionLogSinkimplementation (e.g.,DecisionLogger) toGuardvialogger_sink=.... - Each decision is emitted to the Python logger
rbacx.audit. Setas_json=Trueto serialize the event as JSON; otherwise a compact text form (decision {...}) is logged. You can also control the log level via thelevelparameter. - Optional sampling: either a single global
sample_rateor smart sampling (smart_sampling=True) with category rates fordeny,permit_with_obligations,permit. - Redactions: pass explicit
redactionsor enable default redactions withuse_default_redactions=True. - Size bound: set
max_env_bytesto cap serialized env after redactions. - Redactions reuse obligation formats (e.g.
"mask_fields","redact_fields") and are applied before the event is logged; if a listed field is missing in the request context, the enforcer will still create it in the loggedenvwith the appropriate placeholder (mask or[REDACTED]). -
Redaction mode:
DecisionLoggerforwardsredact_in_placeto the enforcer: -
redact_in_place=False(default) — redact on a copy ofenv(non-mutating, safer by default). redact_in_place=True— redact in place (fewer allocations; mutates the originalenv). Choose copy-by-default unless you explicitly need in-process sanitization with lower allocation pressure. This aligns with common guidance to avoid sensitive data in logs and use masking/redaction when needed.- Prefer structured logging (e.g., JSON) and include enough context for auditability (what/when/where/source/outcome/actor) per typical audit record guidance.
Example (framework-agnostic)¶
import logging
from rbacx import Guard
from rbacx.logging.decision_logger import DecisionLogger
audit = logging.getLogger("rbacx.audit")
audit.setLevel(logging.INFO)
audit.addHandler(logging.StreamHandler())
decision_logger = DecisionLogger(
as_json=True,
use_default_redactions=True, # opt-in safe defaults
smart_sampling=True, # category-aware sampling
sample_rate=0.05, # fallback for plain permits
category_sampling_rates={"permit": 0.05}, # leave deny/obligations at 1.0 (defaults)
level=logging.INFO,
redact_in_place=False, # set True only if you own the original env
# optional size bound after redactions:
max_env_bytes=64 * 1024,
)
policy = {"algorithm": "deny-overrides", "rules": []}
guard = Guard(policy, logger_sink=decision_logger)