pub struct AuditLogger {
config: AuditConfig,
entries: Mutex<Vec<AuditEntry>>,
last_hmac: Mutex<String>,
}Expand description
Audit logger for SOC2/GDPR compliance.
§HMAC Chain Integrity
When configured with an HMAC key, the logger maintains a cryptographic chain where each entry’s signature includes the previous entry’s signature. This creates an append-only log that detects tampering or deletion.
The chain starts with GENESIS_HMAC as the “previous” value for the
first entry. Each subsequent entry includes the HMAC of the previous entry.
Fields§
§config: AuditConfig§entries: Mutex<Vec<AuditEntry>>§last_hmac: Mutex<String>Last HMAC in the chain (for signing new entries).
Implementations§
Source§impl AuditLogger
impl AuditLogger
Sourcepub fn with_config(config: AuditConfig) -> Self
pub fn with_config(config: AuditConfig) -> Self
Creates a new audit logger with custom config.
Sourcepub fn log(&self, event: &MemoryEvent)
pub fn log(&self, event: &MemoryEvent)
Logs an audit event.
Sourcepub fn log_entry(&self, entry: AuditEntry)
pub fn log_entry(&self, entry: AuditEntry)
Logs a custom audit entry.
If an HMAC key is configured, the entry is signed and chained to the previous entry before storage.
Sourcefn sign_entry(&self, entry: AuditEntry) -> AuditEntry
fn sign_entry(&self, entry: AuditEntry) -> AuditEntry
Signs an entry with HMAC if configured, updating chain state.
Sourcepub fn log_capture(&self, memory_id: &str, namespace: &str)
pub fn log_capture(&self, memory_id: &str, namespace: &str)
Logs a capture event.
Sourcepub fn log_recall(&self, query: &str, result_count: usize)
pub fn log_recall(&self, query: &str, result_count: usize)
Logs a search/recall event.
Sourcepub fn log_redaction(&self, memory_id: &str, redaction_types: &[String])
pub fn log_redaction(&self, memory_id: &str, redaction_types: &[String])
Logs a redaction event.
Sourcepub fn log_pii_detection(&self, pii_types: &[String], context: Option<&str>)
pub fn log_pii_detection(&self, pii_types: &[String], context: Option<&str>)
Logs a PII detection event for GDPR/SOC2 compliance.
Records when PII is detected in content, including the types of PII found and the count. The actual PII values are NOT logged to avoid storing sensitive data in audit logs.
Sourcepub fn log_pii_disclosure(
&self,
destination: &str,
pii_types: &[String],
data_subject_id: Option<&str>,
purpose: &str,
legal_basis: &str,
)
pub fn log_pii_disclosure( &self, destination: &str, pii_types: &[String], data_subject_id: Option<&str>, purpose: &str, legal_basis: &str, )
Logs a PII disclosure event for GDPR/SOC2 compliance.
Records when data containing PII is disclosed to external systems such as:
- LLM providers (Anthropic,
OpenAI, Ollama, etc.) - Remote sync destinations
- External APIs
The actual PII values are NOT logged to avoid storing sensitive data. Only metadata about the disclosure is recorded.
§Arguments
destination- The external system receiving the data (e.g., “anthropic”, “openai”)pii_types- Types of PII being discloseddata_subject_id- Optional identifier of the data subject (anonymized)purpose- The purpose of the disclosurelegal_basis- Legal basis for disclosure (e.g., “consent”, “legitimate_interest”)
Sourcepub fn log_bulk_pii_disclosure(
&self,
destination: &str,
record_count: usize,
pii_categories: &[String],
purpose: &str,
legal_basis: &str,
)
pub fn log_bulk_pii_disclosure( &self, destination: &str, record_count: usize, pii_categories: &[String], purpose: &str, legal_basis: &str, )
Logs a bulk PII disclosure event for batch operations.
Similar to log_pii_disclosure but for bulk operations involving multiple
data subjects or records.
§Arguments
destination- The external system receiving the datarecord_count- Number of records being disclosedpii_categories- Categories of PII being disclosedpurpose- The purpose of the disclosurelegal_basis- Legal basis for disclosure
Sourcepub fn log_denied(&self, action: &str, reason: &str)
pub fn log_denied(&self, action: &str, reason: &str)
Logs an access denied event.
Sourcepub fn recent_entries(&self, limit: usize) -> Vec<AuditEntry>
pub fn recent_entries(&self, limit: usize) -> Vec<AuditEntry>
Returns recent audit entries.
Sourcepub fn entries_since(&self, since: DateTime<Utc>) -> Vec<AuditEntry>
pub fn entries_since(&self, since: DateTime<Utc>) -> Vec<AuditEntry>
Returns all entries since a given timestamp.
Sourcepub fn verify_chain(&self) -> Result<()>
pub fn verify_chain(&self) -> Result<()>
Verifies the HMAC chain integrity of all entries.
Returns Ok(()) if all entries are valid and properly chained,
or an error describing the first invalid entry found.
§Errors
Returns an error if:
- No HMAC key is configured
- An entry has an invalid or missing signature
- The chain is broken (entry’s
previous_hmacdoesn’t match prior signature)
Sourcepub const fn is_signing_enabled(&self) -> bool
pub const fn is_signing_enabled(&self) -> bool
Returns whether HMAC signing is enabled.
Sourcefn event_to_entry(&self, event: &MemoryEvent) -> AuditEntry
fn event_to_entry(&self, event: &MemoryEvent) -> AuditEntry
Converts a MemoryEvent to an AuditEntry.
Sourcefn append_to_file(&self, path: &Path, entry: &AuditEntry) -> Result<()>
fn append_to_file(&self, path: &Path, entry: &AuditEntry) -> Result<()>
Appends an entry to the log file.
§Security
- Path canonicalization is performed to prevent TOCTOU race conditions where a symlink could be modified between path validation and file open.
- On Unix, file permissions are set atomically to 0o600 (owner read/write only)
at file creation time using
OpenOptionsExt::mode()to prevent race conditions.
Sourcefn canonicalize_path(path: &Path) -> Result<PathBuf>
fn canonicalize_path(path: &Path) -> Result<PathBuf>
Canonicalizes a path, handling non-existent files by canonicalizing the parent.
Source§impl AuditLogger
impl AuditLogger
Sourcepub fn generate_access_review(
&self,
period_start: DateTime<Utc>,
period_end: DateTime<Utc>,
) -> AccessReviewReport
pub fn generate_access_review( &self, period_start: DateTime<Utc>, period_end: DateTime<Utc>, ) -> AccessReviewReport
Generates an access review report for the specified period.
§Arguments
period_start- Start of the review periodperiod_end- End of the review period
§Example
use chrono::{Duration, Utc};
use subcog::security::AuditLogger;
let logger = AuditLogger::new();
let end = Utc::now();
let start = end - Duration::days(30);
let report = logger.generate_access_review(start, end);
println!("Total events: {}", report.total_events);Trait Implementations§
Auto Trait Implementations§
impl !Freeze for AuditLogger
impl RefUnwindSafe for AuditLogger
impl Send for AuditLogger
impl Sync for AuditLogger
impl Unpin for AuditLogger
impl UnwindSafe for AuditLogger
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
§impl<T> FutureExt for T
impl<T> FutureExt for T
§fn with_context(self, otel_cx: Context) -> WithContext<Self>
fn with_context(self, otel_cx: Context) -> WithContext<Self>
§fn with_current_context(self) -> WithContext<Self>
fn with_current_context(self) -> WithContext<Self>
§impl<T> Instrument for T
impl<T> Instrument for T
§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more§impl<T> IntoRequest<T> for T
impl<T> IntoRequest<T> for T
§fn into_request(self) -> Request<T>
fn into_request(self) -> Request<T>
T in a tonic::Request§impl<L> LayerExt<L> for L
impl<L> LayerExt<L> for L
§fn named_layer<S>(&self, service: S) -> Layered<<L as Layer<S>>::Service, S>where
L: Layer<S>,
fn named_layer<S>(&self, service: S) -> Layered<<L as Layer<S>>::Service, S>where
L: Layer<S>,
Layered].