Skip to main content

systemprompt_security/authz/audit/
mod.rs

1//! Audit sink for authorization decisions.
2//!
3//! Every decision made *inside core* (webhook fault, default deny,
4//! unrestricted allow) flows through an [`AuthzAuditSink`] so it lands in the
5//! same `governance_decisions` table the extension's `POST /govern/authz`
6//! handler writes to. Successful webhook round-trips are audited by the
7//! extension itself (single writer per code path); core's sink only records
8//! decisions the extension never sees.
9//!
10//! [`NullAuditSink`] is the bootstrap default — it exists so unit tests and
11//! pre-database bootstrap stages can install hooks without a `DbPool`.
12//! Production replaces it with [`DbAuditSink`] once the database is available.
13//!
14//! Copyright (c) systemprompt.io — Business Source License 1.1.
15//! See <https://systemprompt.io> for licensing details.
16
17mod db_sink;
18mod repository;
19
20use async_trait::async_trait;
21
22use super::types::{AuthzDecision, AuthzRequest};
23
24pub use db_sink::DbAuditSink;
25pub use repository::{
26    AUDIT_WRITE_FAILED_TOTAL, GovernanceDecisionRecord, GovernanceDecisionRepository,
27    GovernanceWarningRow, insert_governance_decision, list_governance_warnings,
28    list_trace_ids_with_decision,
29};
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub enum AuthzSource {
33    WebhookFault,
34    DenyAllDefault,
35    AllowAllUnrestricted,
36    ExtensionHook,
37    RuleBased,
38}
39
40impl AuthzSource {
41    pub const fn policy(self) -> &'static str {
42        match self {
43            Self::WebhookFault => "authz_hook_fault",
44            Self::DenyAllDefault => "authz_default_deny",
45            Self::AllowAllUnrestricted => "authz_unrestricted",
46            Self::ExtensionHook => "authz_extension_hook",
47            Self::RuleBased => "authz_rule_based",
48        }
49    }
50}
51
52/// `#[async_trait]`: this trait is consumed as `Arc<dyn AuthzAuditSink>` by
53/// every hook implementation, so it must be `dyn`-compatible — native
54/// `async fn` in traits is not yet object-safe.
55#[async_trait]
56pub trait AuthzAuditSink: Send + Sync + std::fmt::Debug {
57    async fn record(&self, req: &AuthzRequest, decision: &AuthzDecision, source: AuthzSource);
58}
59
60#[derive(Debug, Default, Clone, Copy)]
61pub struct NullAuditSink;
62
63#[async_trait]
64impl AuthzAuditSink for NullAuditSink {
65    async fn record(&self, _req: &AuthzRequest, _decision: &AuthzDecision, _source: AuthzSource) {}
66}