Audit Trail for Regulated Industries
Build a tamper-evident, PII-free audit record of every guard violation — queryable by TenantId, UserId, GuardName, and TraceId — without slowing down workflow execution.
What Gets Audited
Every time a guard returns IsAllowed=false, GuardRailsAuditLogger writes an audit event. The audit logger operates asynchronously in batches — violations never block the execution path.
Audit Event Structure
public class GuardRailViolationAuditEvent
{
public DateTime OccurredAtUtc { get; set; }
public int TenantId { get; set; }
public int? UserId { get; set; }
public string GuardName { get; set; } // e.g. "PiiDetectionGuard"
public GuardRailPhase Phase { get; set; } // Pre=0, Post=1, Error=2
public string OperationId { get; set; } // unique per node execution
public string Reason { get; set; } // human-readable violation description
public string TraceId { get; set; } // distributed trace correlation
public string SourceIp { get; set; }
// NOTE: No Input or Output snapshots.
// The allowlist approach: log metadata, never log the data itself.
}
Input or Output snapshots. This is by design (the "allowlist approach"). If PII was in the input, the fact of the violation — which guard blocked, at what time, for which tenant — is sufficient for compliance evidence. The actual data is never written to the audit store.
Audit Trail Structure
Each GuardRailViolation in the execution result also carries audit-relevant fields:
public class GuardRailViolation
{
public string GuardName { get; set; }
public GuardRailPhase Phase { get; set; }
public string Reason { get; set; }
public long TenantId { get; set; }
public string OperationId { get; set; }
}
Execution Timeline for Forensics
The GuardRailsExecutionTimeline in each execution result provides guard-by-guard forensics:
// Sample timeline output
Guard Timeline:
TimeoutGuard (pre, 2ms) ✓
RateLimitingGuard (pre, 5ms) ✓
PiiDetectionGuard (pre, 12ms) ✗ [BLOCKED: SSN, Email detected]
This tells you exactly which guard blocked, in which phase, how long it took to make the decision, and why.
Compliance Scenarios
GDPR Data Protection Audit
Regulator asks: "Show us evidence that personal data was not processed without lawful basis for TenantId 42 between January and March 2026."
- Query audit log for
TenantId=42,GuardName="PiiDetectionGuard", date range January–March - Each record shows: timestamp, operationId, reason ("PII detected: Email, SSN"), phase (Pre)
- Zero violations = evidence that PII-containing inputs were always blocked
- Some violations = show the violation was blocked, not processed
PCI-DSS Cardholder Data Access Audit
Auditor asks: "Was credit card data ever returned in an API response?"
- Query audit log for
GuardName="PiiRedactionGuard"withReasoncontaining "CreditCard" - Each record shows the guard ran in Post phase and redacted the data
OutputModified=truein execution result proves the redaction happened
SOC 2 Rate Limiting Evidence
Auditor asks: "How do you prevent one customer from monopolizing shared infrastructure?"
- Query audit log for
GuardName="RateLimitingGuard" - Records show per-tenant blocking events with
TenantId, timestamp, operationId - Configuration history shows tenant-scoped limits were in place
Secrets Redaction in Logs
ISecretsRedactor is applied to all log output. It uses regex-based detection to scrub:
- API keys (common formats:
Bearer ...,api_key=...) - Passwords in connection strings
- JWT tokens
This ensures that even if a node logs its own input, any secrets in that input are redacted before they reach the log aggregator.
Non-Blocking Audit Design
GuardRailsAuditLogger writes violations asynchronously in batches. A brief window exists where a violation may not yet be persisted — acceptable for all known regulatory frameworks where near-real-time audit is sufficient. The trade-off is zero performance impact on the execution hot path.