365Architect

Guardian Runtime — Drift Detection & SIEM Export

Guardian Runtime is the continuous monitoring component of CipherShift365. It detects cryptographic drift in production, exports canonical events to SIEM platforms, and self-protects against impacting the host application.


Table of Contents

  1. What It Does
  2. Why It's Needed
  3. Architecture Overview
  4. Installation
  5. How to Run / Use
  6. User Manual
  7. Developer Manual
  8. Code Examples
  9. API Reference
  10. Configuration Options
  11. Value Showcase

What It Does

Guardian Runtime runs inside your .NET application process and continuously monitors cryptographic activity against a signed baseline:

Capability Description
Drift Engine Compares live crypto events against baseline. Detects new, changed, or resolved findings.
DiagnosticSource Listener Subscribes to .NET DiagnosticSource / EventSource events from System.Security.Cryptography and the Vault SDK. No CLR Profiler required.
Sampling Controller Rate-limits processing to bounded resources. Drops events under load, records when sampling is active.
Health Monitor Self-disables after 3 consecutive faults. Emits a high-severity health event. Host application continues unaffected.
Canonical Events Normalizes all crypto events into a structured CanonicalEvent model traceable back to a CBOM asset.
SIEM Export Native formatters for Microsoft Sentinel, Splunk, and Elastic — no custom parsers needed.
APM Coexistence Uses DiagnosticListener.AllListeners.Subscribe(). No CLR Profiler slot consumed — coexists with Datadog, Dynatrace, New Relic, etc.

Drift Categories Detected

Category What It Means
NewFinding A crypto asset detected that has no corresponding entry in the baseline
RiskChanged An algorithm's risk level has worsened (or improved) since baseline
ConfidenceChanged Detection confidence has changed since baseline
HealthEvent Guardian Runtime self-disabled due to consecutive faults
SamplingActive Event rate exceeded sampling threshold

Why It's Needed

The Problem

Build-time gates (Guardian Gate) prevent violations from reaching production. But what about:

  • Third-party NuGet packages that add crypto at runtime?
  • Feature flags that activate dormant crypto paths?
  • Configuration changes that switch to weaker algorithms?
  • Library updates that silently change cryptographic defaults?
  • Runtime-generated code or dynamically loaded assemblies?

Without runtime monitoring, you have blind spots between gate checks.

The Value Proposition

Stakeholder Benefit
SOC Analysts Canonical events in their native SIEM format — no learning curve
DevOps / SRE Bounded resource usage, self-disabling safety — will never crash the host
Security Engineers Drift detection surfaces configuration drift, library upgrade risks, and feature-flag surprises
Compliance Auditors Drift events provide a continuous audit trail between gate evaluations
Developers No code changes needed. Subscribes to existing .NET diagnostics infrastructure.

Guardian Runtime is the "always-on" counterpart to Guardian Gate's "checkpoint" enforcement. Together they provide defense-in-depth for cryptographic posture.


Architecture Overview

Rendering diagram...

Event Flow

  1. System.Security.Cryptography or the Vault SDK emits a DiagnosticSource event
  2. RuntimeListener's CryptoDiagnosticObserver filters for crypto-related event names
  3. HealthMonitor.TryEnter() gates if the listener is in a faulted state
  4. SamplingController.ShouldProcess() rate-limits to ≤1000 events/second
  5. DriftEngine.Evaluate() compares the finding against the loaded SignedBaseline
  6. If drift is detected, a CanonicalEvent is raised via the onEvent callback
  7. The SIEM exporter formats the event for the target platform

Safety Properties

Property Guarantee
Host impact on fault None — self-disables, host continues
Resource bound ≤1000 events/sec (configurable)
Profiler slot Not used — works alongside APMs
Hash collision resilience Baseline uses LogicalId (content-hash), not filenames
Startup hook No code changes in the host application (if using .NET startup hooks)

Installation

BASH
dotnet add package CipherShift365.Guardian

Programmatic Registration

CSHARP
using CipherShift365.Guardian.Runtime;

// Create components
var drift = new DriftEngine(identityService);
var sampling = new SamplingController(maxEventsPerSecond: 1000);
var health = new HealthMonitor();
health.OnFault += e => Console.Error.WriteLine($"GUARDIAN FAULT: {e.Description}");

// Load baseline
drift.LoadBaseline(signedBaseline);

// Configure SIEM export
var listener = new RuntimeListener(drift, sampling, health, onEvent: canonicalEvent =>
{
    // Send to SIEM
    await siemClient.SendAsync(EventExporter.ToSentinelJson(canonicalEvent));
});

listener.Start();

.NET Startup Hook (No Code Changes)

Add to your application's .csproj:

XML
<ItemGroup>
  <RuntimeHostConfigurationOption Include="STARTUP_HOOKS"
    Value="path\to\CipherShift365.Guardian.Runtime.dll" />
</ItemGroup>

This injects RuntimeListener.Start() before Main() without requiring any application code changes.

Dependencies

CipherShift365.Guardian
  ├── CipherShift365.Core.Analysis
  └── CipherShift365.Core.Runtime (shared contracts + types)

How to Run / Use

Minimal Integration

CSHARP
// Setup (once at startup)
var drift = new DriftEngine(identityService);
var sampling = new SamplingController(500); // 500 events/sec
var health = new HealthMonitor();

var listener = new RuntimeListener(drift, sampling, health, onEvent: evt =>
{
    // Log to console, file, or SIEM
    Console.WriteLine($"[DRIFT] {evt.Category}: {evt.Algorithm} — {evt.Description}");
});

listener.Start();

// No further interaction needed — listener runs for the process lifetime

With SIEM Export

CSHARP
var listener = new RuntimeListener(drift, sampling, health, onEvent: evt =>
{
    string json = targetSiem switch
    {
        "sentinel" => EventExporter.ToSentinelJson(evt),
        "splunk"   => EventExporter.ToSplunkJson(evt),
        "elastic"  => EventExporter.ToElasticJson(evt),
        _          => JsonSerializer.Serialize(evt)
    };

    // Send via your SIEM agent's forwarder
    siemForwarder.Send(json);
});

Monitoring Health

CSHARP
// Periodically check if Guardian has faulted
if (health.IsFaulted)
{
    Console.Error.WriteLine(
        $"Guardian disabled at {health.LastFaultAt} after {health.FaultCount} faults. " +
        "Host continues normally but drift detection is unavailable until reset.");
}

User Manual

Understanding Drift

Cryptographic drift is any change in the cryptographic posture of your application relative to the signed baseline:

Drift Type Example Action
NewFinding A NuGet update added ECDSA usage Add to baseline if approved, or remediate
RiskChanged AES-128 was accepted, now DES appears under same LogicalId Investigate and remediate
ConfidenceChanged Detection confidence went from High to Low Verify the finding manually
HealthEvent Guardian faulted 3 times Restart listener or redeploy

Loading a Baseline

The DriftEngine requires a SignedBaseline loaded at startup:

CSHARP
// Load the same signed baseline used by Guardian Gate
byte[] blob = File.ReadAllBytes("baseline.csb");
var sb = BaselineManager.ImportFromDistribution(blob, verifier);
if (sb is null) throw new SecurityException("Baseline rejected.");

drift.LoadBaseline(sb);

If no baseline is loaded, DriftEngine.Evaluate() returns BaselineNotLoaded for every event — drift detection is unavailable but monitoring continues without errors.

SIEM Field Mapping

Microsoft Sentinel

JSON
{
  "TimeGenerated": "2026-06-29T12:00:00.000Z",
  "AssetId": "crypto-ml-dsa-87",
  "Algorithm": "ML-DSA-87",
  "Severity": "QuantumSafe",
  "Category": "NewFinding",
  "CorrelationId": "a1b2c3d4",
  "Description": "New crypto asset detected: ML-DSA-87 (Signature)",
  "Source": "CipherShift365.Guardian"
}

Splunk

JSON
{
  "timestamp": "2026-06-29T12:00:00.000Z",
  "event": "pqc_drift",
  "fields": {
    "asset_id": "crypto-ml-dsa-87",
    "algorithm": "ML-DSA-87",
    "severity": "QuantumSafe",
    "category": "NewFinding",
    "correlation_id": "a1b2c3d4"
  }
}

Elastic

JSON
{
  "@timestamp": "2026-06-29T12:00:00.000Z",
  "event": {
    "action": "pqc_drift",
    "category": "NewFinding",
    "severity": 3
  },
  "asset": {
    "id": "crypto-ml-dsa-87",
    "algorithm": "ML-DSA-87"
  },
  "ciphershift": {
    "correlation_id": "a1b2c3d4"
  }
}

Health Monitoring Events

When the HealthMonitor self-disables, it emits a CanonicalEvent with:

  • Algorithm: "HealthMonitor"
  • Severity: QuantumBroken (highest)
  • Category: HealthEvent
  • Description: Includes fault count and final reason
  • RecommendedAction: "Restart the Guardian listener or redeploy. Investigate root cause."

This ensures that silent failures are impossible — a security monitoring tool that stops working should raise alarms, not disappear quietly.


Developer Manual

Class Hierarchy

RuntimeListener (IDisposable)
  ├── Constructor: DriftEngine, SamplingController, HealthMonitor, Action<CanonicalEvent>?
  ├── Start()              — Subscribes to DiagnosticListener.AllListeners
  ├── ProcessCryptoEvent() — Core event processing pipeline
  └── Dispose()            — Unsubscribes all listeners

CryptoDiagnosticObserver : IObserver<DiagnosticListener>
  ├── OnNext()   — Filters for CipherShift365 / ASP.NET sources
  └── Subscribes CryptoEventObserver for crypto-related events

CryptoEventObserver : IObserver<KeyValuePair<string, object?>>
  └── Forwards to RuntimeListener.ProcessCryptoEvent()

DriftEngine
  ├── Constructor: IIdentityService
  ├── LoadBaseline(SignedBaseline)
  ├── Evaluate(Finding) → DriftResult
  └── GetCurrentBaseline() → Baseline?

SamplingController
  ├── Constructor: int maxEventsPerSecond (default 1000)
  ├── ShouldProcess() → bool
  ├── IsSampling : bool
  └── SampleCount : int

HealthMonitor
  ├── IsFaulted : bool  (true after 3 consecutive faults)
  ├── FaultCount : int
  ├── LastFaultAt : DateTimeOffset?
  ├── OnFault : event Action<CanonicalEvent>
  ├── RecordFault(string reason)
  ├── TryEnter() → bool
  └── Reset()

CanonicalEvent (record)
  ├── EventId, AssetId, Algorithm, Severity, Category
  ├── CorrelationId, Timestamp
  └── FindingLocationalId?, BaselineLogicalId?, ObservedLogicalId?, Description?, RecommendedAction?

EventExporter (static)
  ├── ToSentinelJson(CanonicalEvent) → string
  ├── ToSplunkJson(CanonicalEvent)   → string
  └── ToElasticJson(CanonicalEvent)  → string

DriftResult (readonly record struct)
  ├── HasDrift : bool, Category : DriftCategory
  ├── LogicalId, Algorithm, Description
  ├── Risk, Confidence
  └── Static factories: NewFinding(), RiskChanged(), ConfidenceChanged(), NoDrift(), BaselineNotLoaded()

Processing Pipeline (Detailed)

The ProcessCryptoEvent method follows this sequence:

1. HealthMonitor.TryEnter()
   └─ If faulted → drop event immediately
   
2. SamplingController.ShouldProcess()
   └─ If sampling → drop event, record sample count
   
3. Extract algorithm from CryptoEvent
   └─ If null → return (no-op for non-crypto events)
   
4. Construct Finding from event data
   └─ Maps event.Role, event.Algorithm, event.Details

5. DriftEngine.Evaluate(finding)
   └─ Computes LogicalId from (Role, Algorithm, Parameters)
   └─ Compares against baseline entries by LogicalId
   └─ Returns DriftResult with category and description
   
6. If DriftResult.HasDrift == true:
   └─ Construct CanonicalEvent
   └─ Invoke onEvent callback → SIEM export

Adding a New SIEM Exporter

CSHARP
public static class EventExporter
{
    public static string ToYourSiemJson(CanonicalEvent e)
    {
        return JsonSerializer.Serialize(new
        {
            time = e.Timestamp,
            // ... map fields to your SIEM's schema
        });
    }
}

The DiagnosticSource Advantage

Unlike CLR Profilers (which consume the single available profiling slot and break APM tools), Guardian Runtime uses DiagnosticListener.AllListeners.Subscribe():

Approach Pros Cons
CLR Profiler Deepest instrumentation Consumes the profiler slot. Conflicts with APMs.
Mono.Cecil weaving Broad coverage Requires build integration. Brittle across updates.
DiagnosticSource (Guardian Runtime) Zero config, coexists with APMs, works in-proc Relies on libraries emitting events (Vault SDK does; BCL does)

Thread Safety

  • DriftEngine._observed uses ConcurrentDictionary<LogicalId, Finding> — safe for concurrent writes
  • SamplingController uses Interlocked.Increment — lock-free
  • HealthMonitor._faulted is volatile bool — safe for multi-threaded access
  • RuntimeListener is not designed for concurrent Start() calls — call once at startup

Code Examples

Example 1: Console-Based Drift Monitor

CSHARP
var drift = new DriftEngine(identity);
var sampling = new SamplingController(500);
var health = new HealthMonitor();

// Self-disable on fault
health.OnFault += e => Console.Error.WriteLine($"GUARDIAN DISABLED: {e.Description}");

// Drift to console
var listener = new RuntimeListener(drift, sampling, health, onEvent: e =>
{
    Console.WriteLine($"[{e.Timestamp:HH:mm:ss}] [{e.Category}] {e.Algorithm}: {e.Description}");
    if (e.RecommendedAction is not null)
        Console.WriteLine($"  → Action: {e.RecommendedAction}");
});

listener.Start();
Console.WriteLine("Guardian Runtime active. Press any key to stop.");
Console.ReadKey();
listener.Dispose();

Example 2: Sentinel Integration with HttpClient

CSHARP
var drift = new DriftEngine(identity);
drift.LoadBaseline(signedBaseline);
var sampling = new SamplingController(1000);
var health = new HealthMonitor();

var httpClient = new HttpClient();
httpClient.BaseAddress = new Uri(Environment.GetEnvironmentVariable("SENTINEL_DCR_URL")!);

var listener = new RuntimeListener(drift, sampling, health, async evt =>
{
    var json = EventExporter.ToSentinelJson(evt);
    var content = new StringContent(json, Encoding.UTF8, "application/json");
    await httpClient.PostAsync("/api/logs", content);
});

listener.Start();

Example 3: Splunk HEC Forwarder

CSHARP
var listener = new RuntimeListener(drift, sampling, health, async evt =>
{
    var splunkPayload = EventExporter.ToSplunkJson(evt);
    var request = new HttpRequestMessage(HttpMethod.Post, "https://splunk-hec:8088/services/collector")
    {
        Content = new StringContent(splunkPayload, Encoding.UTF8, "application/json"),
        Headers = { { "Authorization", $"Splunk {hecToken}" } }
    };
    await httpClient.SendAsync(request);
});

Example 4: Resilience — Self-Disable and Recovery

CSHARP
// Guardian Runtime is resilient by design
var health = new HealthMonitor();
var listener = new RuntimeListener(drift, sampling, health);

// Simulate a fault in an external callback
health.OnFault += e =>
{
    // Forward the health event to your alerting system
    alertingService.SendCritical(e.Description!);

    // After 3 faults, Guardian self-disables
    // Your application continues running normally
    // Only drift detection stops — not your business logic
};

listener.Start();

// Later, if you want to reset:
if (health.IsFaulted)
{
    health.Reset();
    listener.Start(); // Re-enable monitoring
}

Example 5: Custom Drift Handler with Filtering

CSHARP
var listener = new RuntimeListener(drift, sampling, health, onEvent: e =>
{
    // Only alert on high-severity drift
    if (e.Severity == RiskLevel.QuantumBroken || e.Severity == RiskLevel.QuantumVulnerable)
    {
        // Send to alerting system
        pagerDuty.Trigger(new Alert
        {
            Severity = e.Severity.ToString(),
            Source = "CipherShift365.Guardian",
            Summary = e.Description ?? $"Drift: {e.Category}",
            Details = new
            {
                e.AssetId, e.Algorithm, e.CorrelationId,
                Action = e.RecommendedAction
            }
        });
    }

    // Always log to SIEM regardless of severity
    siemForwarder.Send(EventExporter.ToElasticJson(e));
});

API Reference

RuntimeListener

Member Type Description
RuntimeListener(DriftEngine, SamplingController, HealthMonitor, Action<CanonicalEvent>?) Constructor Creates a listener. onEvent receives drift events.
Start() Method Subscribes to DiagnosticListener.AllListeners. Idempotent (no-op if already started).
Dispose() Method Unsubscribes all listeners. Safe to call multiple times.

DriftEngine

Member Type Description
DriftEngine(IIdentityService) Constructor Requires identity service for LogicalId computation
LoadBaseline(SignedBaseline) Method Loads a baseline for comparison. Call before Evaluate().
Evaluate(Finding) Method Compares a finding against the baseline. Returns DriftResult.
GetCurrentBaseline() Method Returns Baseline? from the loaded signed baseline

SamplingController

Property / Method Type Description
SamplingController(int maxEventsPerSecond = 1000) Constructor Sets the rate limit
ShouldProcess() bool Returns true if event should be processed; false if it should be dropped
IsSampling bool true when sampling is active (events being dropped)
MaxEventsPerSecond int Configured rate limit
SampleCount int Number of events dropped in the current window

HealthMonitor

Property / Method / Event Type Description
IsFaulted bool true after 3 consecutive faults. Listener stops processing.
FaultCount int Total number of recorded faults
LastFaultAt DateTimeOffset? Timestamp of the most recent fault
RecordFault(string reason) Method Records a fault. Auto-disables at count 3.
TryEnter() bool Returns false if faulted (events should be dropped)
Reset() Method Clears fault state and count
OnFault event Action<CanonicalEvent> Fires when self-disabling triggers

DriftResult

Property Type Description
HasDrift bool true if drift was detected
Category DriftCategory Type of drift
LogicalId LogicalId Content-based identifier
Algorithm string Algorithm name
Description string? Human-readable description
Risk RiskLevel Current risk level
Confidence Confidence Current confidence level
Observed Finding? The newly observed finding
BaselineEntry Finding? The matching baseline entry (if any)
Static Factory Returns Use Case
NewFinding(LogicalId, Finding) DriftResult No baseline entry exists
RiskChanged(LogicalId, Finding, Finding) DriftResult Risk level differs from baseline
ConfidenceChanged(LogicalId, Finding, Finding) DriftResult Confidence differs from baseline
NoDrift(LogicalId, string) DriftResult Finding matches baseline
BaselineNotLoaded(LogicalId, string) DriftResult No baseline available

DriftCategory

Value Description
NewFinding Crypto asset detected with no baseline entry
ResolvedFinding (Reserved) Finding present in baseline but not observed
RiskChanged Risk level changed since baseline
ConfidenceChanged Detection confidence changed since baseline
BaselineDrift (Reserved) General baseline mismatch
HealthEvent Guardian Runtime health status change
SamplingActive Event sampling activated due to rate limit

CanonicalEvent

Property Type Description
EventId string Unique event identifier
AssetId string CBOM asset identifier for traceability
Algorithm string Crypto algorithm name
Severity RiskLevel Severity level
Category DriftCategory Type of drift
CorrelationId string Cross-event correlation ID
Timestamp DateTimeOffset Event timestamp (UTC)
FindingLocationalId string? Source location of the finding
BaselineLogicalId string? Baseline's logical identifier
ObservedLogicalId string? Observed logical identifier
Description string? Human-readable event description
RecommendedAction string? Suggested remediation step

EventExporter (Static)

Method Returns Target
ToSentinelJson(CanonicalEvent) string Microsoft Sentinel custom log
ToSplunkJson(CanonicalEvent) string Splunk HEC event endpoint
ToElasticJson(CanonicalEvent) string Elasticsearch index

Configuration Options

Sampling Rate

CSHARP
// Default: 1000 events per second
var sampling = new SamplingController(maxEventsPerSecond: 1000);

// Low-resource environments: 100 events per second
var sampling = new SamplingController(maxEventsPerSecond: 100);

// High-throughput services: 5000 events per second
var sampling = new SamplingController(maxEventsPerSecond: 5000);

Health Monitor Threshold

Threshold Behavior
Fault count < 3 Events logged but processing continues
Fault count = 3 Self-disable. Health event emitted. TryEnter() returns false.
After Reset() cleared, processing can resume on next Start()

The threshold of 3 is hardcoded to prevent flapping (one transient error shouldn't disable monitoring) while being aggressive enough to catch persistent issues.

SIEM Configuration

SIEM Endpoint Type Authentication
Microsoft Sentinel DCR Log Ingestion API Entra ID (Managed Identity or Service Principal)
Splunk HTTP Event Collector (HEC) HEC Token
Elastic Elasticsearch _bulk API API Key or Basic Auth

Memory & Performance

Metric Bound
Maximum events per second maxEventsPerSecond (default 1000)
DriftEngine concurrent entries ConcurrentDictionary — grows with unique LogicalIds
Canonical Event allocation One allocation per drift event (not per crypto operation)
DiagnosticSource overhead Negligible — subscription-based, not polling

The DriftEngine._observed dictionary grows cumulatively over the process lifetime. For long-running services (days/weeks), consider periodically purging entries that have been in a steady state:

CSHARP
// Optional: periodic cleanup (not built-in)
if (DateTimeOffset.UtcNow - lastPurge > TimeSpan.FromHours(24))
{
    // Custom purge logic if needed
}

DiagnosticSource Event Filtering

By default, CryptoDiagnosticObserver subscribes to sources starting with:

  • "CipherShift365" (case-insensitive)
  • "Microsoft.AspNetCore" (case-insensitive)

And filters events containing:

  • "Crypto", "Sign", or "Encapsulate" (case-insensitive)

This minimizes overhead from non-crypto diagnostic events.


Value Showcase

Before Guardian Runtime

Q: "Has the production deployment drifted from our crypto baseline?"
A: "We don't know. We run a gate at build time. What happens at runtime is anyone's guess."
Risk: HIGH — unknown runtime behavior, no telemetry

After Guardian Runtime

Q: "Has the production deployment drifted from our crypto baseline?"
A: "Guardian Runtime has detected 0 drift events in the last 24 hours.
    Baseline v12 loaded, 47 crypto events processed, 0 sampling drops.
    Health: green. Full event log available in Sentinel."
Risk: LOW — continuous verification with real-time SIEM visibility

Quantitative Impact

Metric Without Guardian Runtime With Guardian Runtime
Time to detect runtime crypto change Never (no monitoring) <1 second
SIEM integration effort Custom parser development required Native JSON formatting, zero custom parsers
Host application risk N/A Zero — self-disabling, bounded resources
APM tool compatibility N/A Full — no CLR Profiler slot consumed
Cross-team visibility Siloed in individual teams' logs Canonical events in centralized SIEM

Compliance Alignment

Framework How Guardian Runtime Helps
CNSA 2.0 Continuous verification that only approved algorithms are used at runtime
NSM-10 Ongoing monitoring of quantum-resistance posture in operational systems
NIST SP 800-137 Continuous monitoring for information security
FedRAMP Continuous Monitoring Cryptographic drift events feed into the continuous monitoring strategy
MITRE ATT&CK (T1573 — Encrypted Channel) Detects unexpected cryptographic usage patterns

Next: See Knowledge Base for the signed+encrypted KB package format that drives both Gate and Runtime.

Share

Keyboard Shortcuts

⌘ K
Open search
/
Focus search
?
Show shortcuts
b
Toggle bookmark
Alt+←
Previous page
Alt+→
Next page
Esc
Close overlay