Repository navigation
Expand file tree
/
Copy pathSentinelCopilotTelemetryBridge.cs
More file actions
138 lines (125 loc) · 5.79 KB
/
Copy pathSentinelCopilotTelemetryBridge.cs
File metadata and controls
138 lines (125 loc) · 5.79 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using MissionController.Core.Enums;
using MissionController.Core.Interfaces;
using MissionController.Core.Models;
namespace MissionController.Infrastructure.Windows.Copilot;
/// <summary>
/// Bridges Copilot safety events, execution telemetry, and kernel violation alerts directly
/// into Microsoft Sentinel and SIEM infrastructure.
/// </summary>
public sealed class SentinelCopilotTelemetryBridge
{
private readonly ISiemForwarder _siemForwarder;
public SentinelCopilotTelemetryBridge(ISiemForwarder siemForwarder)
{
_siemForwarder = siemForwarder ?? throw new ArgumentNullException(nameof(siemForwarder));
}
public async Task<bool> StreamCopilotEventAsync(
CopilotToolCallContext context,
CopilotExecutionResult result,
SiemConfiguration sentinelConfig,
CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(context);
ArgumentNullException.ThrowIfNull(result);
ArgumentNullException.ThrowIfNull(sentinelConfig);
try
{
// Build CEF event for SIEM / Sentinel
string eventClassId = result.PolicyBlocked
? "COPILOT_POLICY_BLOCKED"
: (result.IsSuccess ? "COPILOT_TOOL_SUCCESS" : "COPILOT_TOOL_FAILED");
int severity = result.PolicyBlocked ? 9 : (result.IsSuccess ? 3 : 6);
if (result.Violations.Count > 0)
{
severity = 10; // Maximum security severity for active kernel/policy violations
}
var extensions = new Dictionary<string, string>
{
["cs1"] = context.AgentId,
["cs1Label"] = "CopilotAgentId",
["cs2"] = context.ToolName,
["cs2Label"] = "InvokedTool",
["cs3"] = context.ExecutableOrCommand,
["cs3Label"] = "TargetExecutable",
["cs4"] = context.SessionId.ToString("D"),
["cs4Label"] = "CopilotSessionId",
["suser"] = context.OperatorIdentity,
["outcome"] = result.PolicyBlocked ? "Blocked" : (result.IsSuccess ? "Success" : "Failure"),
["externalId"] = result.MerkleEntryHash ?? "UNHASHED",
["msg"] = result.PolicyBlocked
? $"[SPLAT SECURITY BLOCK] Copilot agent '{context.AgentId}' attempted blocked action: {result.StandardError}"
: $"Copilot tool '{context.ToolName}' executed. ExitCode={result.ExitCode} Violations={result.Violations.Count}"
};
// A CEF custom-number field is emitted only when the substrate actually measured it.
// An absent key reads as "not reported" in the SIEM; an empty or zero-valued cn1 reads
// as a measurement of zero, which would put a fabricated data point in the analyst's
// dashboard under the same schema as a real one.
if (result.ResourceMetrics != null)
{
if (result.ResourceMetrics.TotalCpuKernelTimeMs is { } kernelMs)
{
extensions["cn1"] = kernelMs.ToString();
extensions["cn1Label"] = "KernelTimeMs";
}
if (result.ResourceMetrics.TotalCpuUserTimeMs is { } userMs)
{
extensions["cn2"] = userMs.ToString();
extensions["cn2Label"] = "UserTimeMs";
}
if (result.ResourceMetrics.PeakMemoryBytes is { } peakBytes)
{
extensions["cn3"] = peakBytes.ToString();
extensions["cn3Label"] = "PeakMemoryBytes";
}
}
if (result.Violations.Count > 0)
{
var vList = new List<string>();
foreach (var v in result.Violations)
{
vList.Add($"[{v.Severity}] {v.ViolationType}: {v.Description} (Offending='{v.OffendingValue}')");
}
extensions["cs5"] = string.Join(" | ", vList);
extensions["cs5Label"] = "ViolationsList";
}
var cefEvent = new CefEvent(
DeviceVendor: "PlattenIndustries",
DeviceProduct: "SPLAT_Copilot_Safety_Substrate",
DeviceVersion: "1.0",
DeviceEventClassId: eventClassId,
Name: $"Microsoft Copilot Action: {context.ToolName}",
Severity: severity,
ExtensionFields: extensions);
// Also format audit entry for native Sentinel REST ingestion
var auditEntry = new AuditLogEntry(
Id: 0,
TimestampUtc: result.TimestampUtc,
MissionId: context.SessionId,
TemplateId: $"copilot-tool-{context.ToolName}",
Action: eventClassId,
Details: JsonSerializer.Serialize(extensions),
OperatorIdentity: context.OperatorIdentity,
EntryHash: result.MerkleEntryHash ?? Guid.NewGuid().ToString("N"),
PreviousHash: "0000000000000000000000000000000000000000000000000000000000000000");
if (sentinelConfig.Protocol == SiemProtocol.MicrosoftSentinel)
{
await _siemForwarder.ForwardAuditEntryAsync(auditEntry, sentinelConfig, cancellationToken);
}
else
{
await _siemForwarder.ForwardCefEventAsync(cefEvent, sentinelConfig, cancellationToken);
}
return true;
}
catch
{
// Do not fault the caller if SIEM telemetry is momentarily unreachable, but log/fail as appropriate
return false;
}
}
}