feat(agent): added IntegrityBypassAnalyzer

This commit is contained in:
Oliver Kieselbach
2026-04-17 00:20:24 +02:00
parent 2faba55b46
commit 2eb6d87b4d
15 changed files with 975 additions and 6 deletions
@@ -0,0 +1,116 @@
using System;
using System.Collections.Generic;
using System.Linq;
using AutopilotMonitor.Agent.Core.Monitoring.Telemetry.Analyzers;
using AutopilotMonitor.Agent.Core.Tests.Helpers;
using AutopilotMonitor.Shared.Models;
using Xunit;
namespace AutopilotMonitor.Agent.Core.Tests.Monitoring
{
/// <summary>
/// Sanity tests for IntegrityBypassAnalyzer. The analyzer reads HKLM/HKU, Win32_Tpm
/// and the real filesystem — we don't mock those here. Instead we verify the event
/// contract (type / phase / severity / required payload shape) against whatever the
/// test host reports. On any healthy dev machine this should emit a single
/// "integrity_bypass_analysis" event with Phase=Unknown and the documented payload.
/// </summary>
public class IntegrityBypassAnalyzerTests
{
private IntegrityBypassAnalyzer CreateAnalyzer(List<EnrollmentEvent> sink)
{
return new IntegrityBypassAnalyzer(
sessionId: "test-session",
tenantId: "test-tenant",
emitEvent: evt => sink.Add(evt),
logger: TestLogger.Instance);
}
[Fact]
public void AnalyzeAtStartup_EmitsSingleIntegrityBypassEvent_WithUnknownPhase()
{
var events = new List<EnrollmentEvent>();
var analyzer = CreateAnalyzer(events);
analyzer.AnalyzeAtStartup();
var evt = Assert.Single(events);
Assert.Equal("integrity_bypass_analysis", evt.EventType);
Assert.Equal(EnrollmentPhase.Unknown, evt.Phase);
Assert.Equal("IntegrityBypassAnalyzer", evt.Source);
Assert.Equal("test-session", evt.SessionId);
Assert.Equal("test-tenant", evt.TenantId);
Assert.NotNull(evt.Data);
Assert.True(evt.Data.ContainsKey("severity"));
Assert.True(evt.Data.ContainsKey("finding"));
Assert.True(evt.Data.ContainsKey("findings"));
Assert.True(evt.Data.ContainsKey("triggered_at"));
Assert.Equal("startup", evt.Data["triggered_at"]);
Assert.True(evt.Data.ContainsKey("checks"));
}
[Fact]
public void AnalyzeAtShutdown_EmitsSingleIntegrityBypassEvent_WithShutdownTrigger()
{
var events = new List<EnrollmentEvent>();
var analyzer = CreateAnalyzer(events);
analyzer.AnalyzeAtShutdown();
var evt = Assert.Single(events);
Assert.Equal("integrity_bypass_analysis", evt.EventType);
Assert.Equal(EnrollmentPhase.Unknown, evt.Phase);
Assert.Equal("shutdown", evt.Data["triggered_at"]);
}
[Fact]
public void Payload_ContainsDocumentedCheckSections()
{
var events = new List<EnrollmentEvent>();
var analyzer = CreateAnalyzer(events);
analyzer.AnalyzeAtStartup();
var evt = Assert.Single(events);
var checks = Assert.IsAssignableFrom<Dictionary<string, object>>(evt.Data["checks"]);
Assert.True(checks.ContainsKey("lab_config"));
Assert.True(checks.ContainsKey("mo_setup"));
Assert.True(checks.ContainsKey("pchc_upgrade_eligibility"));
Assert.True(checks.ContainsKey("setup_scripts"));
Assert.True(checks.ContainsKey("correlation"));
// Setup scripts entry is always a list of 2 (SetupComplete + ErrorHandler)
var scripts = Assert.IsAssignableFrom<System.Collections.IEnumerable>(checks["setup_scripts"]);
var count = scripts.Cast<object>().Count();
Assert.Equal(2, count);
}
[Fact]
public void Severity_IsLowerCaseString()
{
var events = new List<EnrollmentEvent>();
var analyzer = CreateAnalyzer(events);
analyzer.AnalyzeAtStartup();
var evt = Assert.Single(events);
var severity = Assert.IsType<string>(evt.Data["severity"]);
Assert.Equal(severity, severity.ToLowerInvariant());
// Must be one of the documented bucket labels
Assert.Contains(severity, new[] { "trace", "debug", "info", "warning", "error", "critical" });
}
[Fact]
public void NullConstructorArgs_Throw()
{
Assert.Throws<ArgumentNullException>(() =>
new IntegrityBypassAnalyzer(null, "t", evt => { }, TestLogger.Instance));
Assert.Throws<ArgumentNullException>(() =>
new IntegrityBypassAnalyzer("s", null, evt => { }, TestLogger.Instance));
Assert.Throws<ArgumentNullException>(() =>
new IntegrityBypassAnalyzer("s", "t", null, TestLogger.Instance));
Assert.Throws<ArgumentNullException>(() =>
new IntegrityBypassAnalyzer("s", "t", evt => { }, null));
}
}
}
@@ -70,6 +70,21 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Runtime
_logger.Info("SoftwareInventoryAnalyzer disabled by remote config");
}
if (analyzerConfig.EnableIntegrityBypassAnalyzer)
{
_analyzers.Add(new IntegrityBypassAnalyzer(
_configuration.SessionId,
_configuration.TenantId,
_emitEvent,
_logger
));
_logger.Info("IntegrityBypassAnalyzer registered");
}
else
{
_logger.Info("IntegrityBypassAnalyzer disabled by remote config");
}
_logger.Info($"Analyzers initialized: {_analyzers.Count} active");
}
@@ -0,0 +1,492 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Management;
using AutopilotMonitor.Agent.Core.Logging;
using AutopilotMonitor.Shared.Models;
using Microsoft.Win32;
namespace AutopilotMonitor.Agent.Core.Monitoring.Telemetry.Analyzers
{
/// <summary>
/// Detects Windows 11 "unsupported hardware" installations where the PC Health Check /
/// Setup-time hardware gates were bypassed via registry flags, and flags suspicious
/// post-OOBE setup scripts. Such devices may complete enrollment successfully yet lack
/// TPM-backed features (WHfB, BitLocker TPM protector, Device Health Attestation,
/// Credential Guard) — a silent quality risk not otherwise surfaced by enrollment_complete.
///
/// Checks performed:
/// 1. HKLM\SYSTEM\Setup\LabConfig\Bypass{TPM,SecureBoot,RAM,Storage,Disk,CPU}Check (install-time)
/// 2. HKLM\SYSTEM\Setup\MoSetup\AllowUpgradesWithUnsupportedTPMOrCPU (in-place upgrade)
/// 3. HKU\&lt;SID&gt;\SOFTWARE\Microsoft\PCHC\UpgradeEligibility (WU upgrade bypass, per-user)
/// 4. %WINDIR%\Setup\Scripts\SetupComplete.cmd + ErrorHandler.cmd (suspicious post-OOBE hooks)
///
/// Correlation:
/// Bypass flags are cross-checked against current TPM and SecureBoot state; a bypass
/// combined with a missing security feature is escalated to "critical".
///
/// Emits a single "integrity_bypass_analysis" event at startup and at shutdown,
/// enabling delta detection (e.g. scripts that appear between enrollment start and end).
/// </summary>
public class IntegrityBypassAnalyzer : IAgentAnalyzer
{
private readonly string _sessionId;
private readonly string _tenantId;
private readonly Action<EnrollmentEvent> _emitEvent;
private readonly AgentLogger _logger;
public string Name => "IntegrityBypassAnalyzer";
// LabConfig bypass value names. Both BypassStorageCheck and BypassDiskCheck are observed
// in the wild — scripts differ, both should be captured.
private static readonly string[] LabConfigBypassValues = new[]
{
"BypassTPMCheck",
"BypassSecureBootCheck",
"BypassRAMCheck",
"BypassStorageCheck",
"BypassDiskCheck",
"BypassCPUCheck"
};
// Registry base SIDs that are NOT real user profiles
private static readonly HashSet<string> SystemSids = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
".DEFAULT",
"S-1-5-18", // LocalSystem
"S-1-5-19", // LocalService
"S-1-5-20" // NetworkService
};
public IntegrityBypassAnalyzer(
string sessionId,
string tenantId,
Action<EnrollmentEvent> emitEvent,
AgentLogger logger)
{
_sessionId = sessionId ?? throw new ArgumentNullException(nameof(sessionId));
_tenantId = tenantId ?? throw new ArgumentNullException(nameof(tenantId));
_emitEvent = emitEvent ?? throw new ArgumentNullException(nameof(emitEvent));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public void AnalyzeAtStartup()
{
_logger.Info($"{Name}: Running startup analysis");
RunAnalysis("startup");
}
public void AnalyzeAtShutdown()
{
_logger.Info($"{Name}: Running shutdown analysis");
RunAnalysis("shutdown");
}
// -----------------------------------------------------------------------
// Core analysis
// -----------------------------------------------------------------------
private void RunAnalysis(string trigger)
{
try
{
var labConfig = CheckLabConfig();
var moSetup = CheckMoSetupAllowUnsupported();
var pchc = CheckPchcUpgradeEligibility();
var setupScripts = CheckSetupScripts();
var tpmPresent = CheckTpmPresentAndEnabled();
var secureBootEnabled = CheckSecureBootEnabled();
// --------- Severity aggregation ---------
var severity = EventSeverity.Info;
var findings = new List<string>();
bool anyLabConfigBypass = labConfig.ActiveBypasses.Count > 0;
bool labConfigKeyExists = labConfig.KeyExists;
bool moSetupBypass = moSetup.Value == 1;
bool pchcBypass = pchc.AnyUserWithFlag;
bool scriptsPresent = setupScripts.Any(s => s.Exists);
// Critical: bypass + corresponding feature missing
if (labConfig.ActiveBypasses.Contains("BypassTPMCheck") && tpmPresent.HasValue && tpmPresent.Value == false)
{
severity = Escalate(severity, EventSeverity.Critical);
findings.Add("tpm_bypass_with_missing_tpm");
}
if (labConfig.ActiveBypasses.Contains("BypassSecureBootCheck") && secureBootEnabled.HasValue && secureBootEnabled.Value == false)
{
severity = Escalate(severity, EventSeverity.Critical);
findings.Add("secureboot_bypass_with_disabled_secureboot");
}
if (moSetupBypass && ((tpmPresent.HasValue && tpmPresent.Value == false) || (secureBootEnabled.HasValue && secureBootEnabled.Value == false)))
{
severity = Escalate(severity, EventSeverity.Critical);
findings.Add("upgrade_bypass_with_missing_feature");
}
// Warning: bypass active but feature currently still OK, or scripts present, or PCHC flag
if (anyLabConfigBypass && severity < EventSeverity.Warning)
{
severity = Escalate(severity, EventSeverity.Warning);
findings.Add("labconfig_bypass_active");
}
if (moSetupBypass && severity < EventSeverity.Warning)
{
severity = Escalate(severity, EventSeverity.Warning);
findings.Add("mosetup_upgrade_bypass_active");
}
if (pchcBypass)
{
severity = Escalate(severity, EventSeverity.Warning);
findings.Add("pchc_upgrade_eligibility_forced");
}
if (scriptsPresent)
{
severity = Escalate(severity, EventSeverity.Warning);
findings.Add("suspicious_setup_scripts_present");
}
// Info: key exists but nothing active
if (severity == EventSeverity.Info)
{
if (labConfigKeyExists)
findings.Add("labconfig_key_present_inactive");
else
findings.Add("no_bypass_indicators");
}
string findingLabel = findings.FirstOrDefault() ?? "no_bypass_indicators";
string message = severity >= EventSeverity.Warning
? $"{Name}: Integrity bypass indicators detected ({string.Join(",", findings)})"
: $"{Name}: No integrity bypass indicators";
_logger.Info(
$"{Name}: severity={severity}, findings=[{string.Join(",", findings)}], " +
$"labConfigBypasses={labConfig.ActiveBypasses.Count}, " +
$"moSetupBypass={moSetupBypass}, pchcBypass={pchcBypass}, " +
$"scriptsPresent={scriptsPresent}, tpm={FormatNullableBool(tpmPresent)}, " +
$"secureBoot={FormatNullableBool(secureBootEnabled)}");
var data = new Dictionary<string, object>
{
{ "severity", severity.ToString().ToLowerInvariant() },
{ "finding", findingLabel },
{ "findings", findings },
{ "triggered_at", trigger },
{ "enrollment_phase_at_check", EnrollmentPhase.Unknown.ToString() },
{ "checks", new Dictionary<string, object>
{
{ "lab_config", new Dictionary<string, object>
{
{ "key_exists", labConfig.KeyExists },
{ "values", labConfig.Values },
{ "active_bypasses", labConfig.ActiveBypasses }
}
},
{ "mo_setup", new Dictionary<string, object>
{
{ "key_exists", moSetup.KeyExists },
{ "value", moSetup.Value },
{ "flagged", moSetupBypass }
}
},
{ "pchc_upgrade_eligibility", new Dictionary<string, object>
{
{ "users_checked", pchc.UsersChecked },
{ "users_with_flag", pchc.UsersWithFlag },
{ "any_user_with_flag", pchc.AnyUserWithFlag }
}
},
{ "setup_scripts", setupScripts.Select(s => new Dictionary<string, object>
{
{ "path", s.Path },
{ "exists", s.Exists },
{ "size_bytes", s.SizeBytes },
{ "last_modified_utc", s.LastModifiedUtc.HasValue
? s.LastModifiedUtc.Value.ToString("O")
: null }
}).ToList()
},
{ "correlation", new Dictionary<string, object>
{
{ "tpm_present_and_enabled", tpmPresent },
{ "secure_boot_enabled", secureBootEnabled }
}
}
}
}
};
_emitEvent(new EnrollmentEvent
{
SessionId = _sessionId,
TenantId = _tenantId,
EventType = "integrity_bypass_analysis",
Severity = severity,
Source = Name,
Phase = EnrollmentPhase.Unknown,
Message = message,
Data = data
});
}
catch (Exception ex)
{
_logger.Error($"{Name}: Analysis failed unexpectedly", ex);
}
}
private static EventSeverity Escalate(EventSeverity current, EventSeverity candidate)
=> candidate > current ? candidate : current;
private static string FormatNullableBool(bool? value)
=> value.HasValue ? value.Value.ToString() : "unknown";
// -----------------------------------------------------------------------
// Individual checks
// -----------------------------------------------------------------------
private LabConfigCheckResult CheckLabConfig()
{
var result = new LabConfigCheckResult();
try
{
using (var key = Registry.LocalMachine.OpenSubKey(@"SYSTEM\Setup\LabConfig", writable: false))
{
if (key == null)
{
_logger.Debug($"{Name}: HKLM\\SYSTEM\\Setup\\LabConfig not present");
return result;
}
result.KeyExists = true;
foreach (var valueName in LabConfigBypassValues)
{
var raw = key.GetValue(valueName);
int intValue = 0;
if (raw != null)
{
try { intValue = Convert.ToInt32(raw); }
catch { intValue = 0; }
}
result.Values[valueName] = intValue;
if (intValue == 1)
{
result.ActiveBypasses.Add(valueName);
_logger.Debug($"{Name}: LabConfig {valueName}=1 (active bypass)");
}
}
}
}
catch (Exception ex)
{
_logger.Warning($"{Name}: Failed to read LabConfig registry: {ex.Message}");
}
return result;
}
private MoSetupCheckResult CheckMoSetupAllowUnsupported()
{
try
{
using (var key = Registry.LocalMachine.OpenSubKey(@"SYSTEM\Setup\MoSetup", writable: false))
{
if (key == null)
return new MoSetupCheckResult { KeyExists = false, Value = 0 };
var raw = key.GetValue("AllowUpgradesWithUnsupportedTPMOrCPU");
if (raw == null)
return new MoSetupCheckResult { KeyExists = true, Value = 0 };
int intValue = 0;
try { intValue = Convert.ToInt32(raw); } catch { intValue = 0; }
return new MoSetupCheckResult { KeyExists = true, Value = intValue };
}
}
catch (Exception ex)
{
_logger.Warning($"{Name}: Failed to read MoSetup registry: {ex.Message}");
return new MoSetupCheckResult { KeyExists = false, Value = 0 };
}
}
/// <summary>
/// Enumerates HKEY_USERS and checks SOFTWARE\Microsoft\PCHC\UpgradeEligibility for each
/// real user profile. Agent runs as SYSTEM — loaded hives include all interactive user
/// profiles that Windows has mounted. SIDs for .DEFAULT and system accounts are skipped.
/// Profiles whose hive is not currently loaded are not visible here; that is acceptable —
/// the relevant user during enrollment is the primary logged-in user whose hive is loaded.
/// </summary>
private PchcCheckResult CheckPchcUpgradeEligibility()
{
var result = new PchcCheckResult();
try
{
using (var users = Registry.Users)
{
foreach (var sid in users.GetSubKeyNames())
{
if (string.IsNullOrEmpty(sid)) continue;
if (SystemSids.Contains(sid)) continue;
// Skip the Classes-hive companions
if (sid.EndsWith("_Classes", StringComparison.OrdinalIgnoreCase)) continue;
result.UsersChecked.Add(sid);
try
{
using (var pchcKey = users.OpenSubKey($@"{sid}\SOFTWARE\Microsoft\PCHC", writable: false))
{
if (pchcKey == null) continue;
var raw = pchcKey.GetValue("UpgradeEligibility");
if (raw == null) continue;
int intValue = 0;
try { intValue = Convert.ToInt32(raw); } catch { intValue = 0; }
if (intValue == 1)
{
result.UsersWithFlag.Add(sid);
_logger.Debug($"{Name}: PCHC UpgradeEligibility=1 for SID {sid}");
}
}
}
catch (Exception ex)
{
_logger.Debug($"{Name}: Failed to read PCHC for SID {sid}: {ex.Message}");
}
}
}
}
catch (Exception ex)
{
_logger.Warning($"{Name}: Failed to enumerate HKEY_USERS: {ex.Message}");
}
return result;
}
private List<SetupScriptCheckResult> CheckSetupScripts()
{
var results = new List<SetupScriptCheckResult>();
var windir = Environment.GetEnvironmentVariable("WINDIR");
if (string.IsNullOrEmpty(windir))
windir = @"C:\Windows";
var candidates = new[]
{
Path.Combine(windir, @"Setup\Scripts\SetupComplete.cmd"),
Path.Combine(windir, @"Setup\Scripts\ErrorHandler.cmd")
};
foreach (var path in candidates)
{
var entry = new SetupScriptCheckResult { Path = path, Exists = false };
try
{
if (File.Exists(path))
{
entry.Exists = true;
var fi = new FileInfo(path);
entry.SizeBytes = fi.Length;
entry.LastModifiedUtc = fi.LastWriteTimeUtc;
_logger.Debug($"{Name}: Setup script present: {path} ({fi.Length} bytes)");
}
}
catch (Exception ex)
{
_logger.Debug($"{Name}: Failed to stat {path}: {ex.Message}");
}
results.Add(entry);
}
return results;
}
/// <summary>
/// Returns true if a TPM is present and enabled, false if definitely absent/disabled,
/// null if indeterminate (query failed). Uses Win32_Tpm in root\CIMV2\Security\MicrosoftTpm.
/// </summary>
private bool? CheckTpmPresentAndEnabled()
{
try
{
using (var searcher = new ManagementObjectSearcher(
@"root\CIMV2\Security\MicrosoftTpm",
"SELECT IsEnabled_InitialValue, IsActivated_InitialValue FROM Win32_Tpm"))
{
foreach (ManagementObject tpm in searcher.Get())
{
var isEnabled = tpm["IsEnabled_InitialValue"] != null
&& Convert.ToBoolean(tpm["IsEnabled_InitialValue"]);
// At least one Tpm instance exists — return its enabled state
return isEnabled;
}
}
// No Win32_Tpm instance returned — no TPM present
return false;
}
catch (Exception ex)
{
_logger.Debug($"{Name}: TPM WMI query failed: {ex.Message}");
return null;
}
}
/// <summary>
/// Returns true if UEFI Secure Boot is enabled, false if disabled, null if not
/// queryable (e.g. BIOS firmware without the registry key).
/// </summary>
private bool? CheckSecureBootEnabled()
{
try
{
using (var key = Registry.LocalMachine.OpenSubKey(
@"SYSTEM\CurrentControlSet\Control\SecureBoot\State", writable: false))
{
if (key == null) return null;
var raw = key.GetValue("UEFISecureBootEnabled");
if (raw == null) return null;
return Convert.ToInt32(raw) == 1;
}
}
catch (Exception ex)
{
_logger.Debug($"{Name}: SecureBoot registry read failed: {ex.Message}");
return null;
}
}
// -----------------------------------------------------------------------
// Private result types
// -----------------------------------------------------------------------
private class LabConfigCheckResult
{
public bool KeyExists { get; set; }
public Dictionary<string, int> Values { get; set; } = new Dictionary<string, int>();
public List<string> ActiveBypasses { get; set; } = new List<string>();
}
private class MoSetupCheckResult
{
public bool KeyExists { get; set; }
public int Value { get; set; }
}
private class PchcCheckResult
{
public List<string> UsersChecked { get; set; } = new List<string>();
public List<string> UsersWithFlag { get; set; } = new List<string>();
public bool AnyUserWithFlag => UsersWithFlag.Count > 0;
}
private class SetupScriptCheckResult
{
public string Path { get; set; }
public bool Exists { get; set; }
public long SizeBytes { get; set; }
public DateTime? LastModifiedUtc { get; set; }
}
}
}
@@ -1 +1 @@
1026
1028
@@ -424,6 +424,7 @@ namespace AutopilotMonitor.Functions.DataAccess.TableStorage
{ "SendTraceEvents", config.SendTraceEvents },
{ "EnableLocalAdminAnalyzer", config.EnableLocalAdminAnalyzer },
{ "EnableSoftwareInventoryAnalyzer", config.EnableSoftwareInventoryAnalyzer },
{ "EnableIntegrityBypassAnalyzer", config.EnableIntegrityBypassAnalyzer },
{ "LocalAdminAllowedAccountsJson", config.LocalAdminAllowedAccountsJson },
{ "BootstrapTokenEnabled", config.BootstrapTokenEnabled },
{ "UnrestrictedModeEnabled", config.UnrestrictedModeEnabled },
@@ -503,6 +504,7 @@ namespace AutopilotMonitor.Functions.DataAccess.TableStorage
SendTraceEvents = entity.GetBoolean("SendTraceEvents") ?? true,
EnableLocalAdminAnalyzer = entity.GetBoolean("EnableLocalAdminAnalyzer"),
EnableSoftwareInventoryAnalyzer = entity.GetBoolean("EnableSoftwareInventoryAnalyzer"),
EnableIntegrityBypassAnalyzer = entity.GetBoolean("EnableIntegrityBypassAnalyzer"),
LocalAdminAllowedAccountsJson = entity.GetString("LocalAdminAllowedAccountsJson"),
BootstrapTokenEnabled = entity.GetBoolean("BootstrapTokenEnabled") ?? false,
UnrestrictedModeEnabled = entity.GetBoolean("UnrestrictedModeEnabled") ?? false,
@@ -141,7 +141,7 @@ namespace AutopilotMonitor.Functions.Functions.Config
var response = req.CreateResponse(HttpStatusCode.OK);
await response.WriteAsJsonAsync(new AgentConfigResponse
{
ConfigVersion = 24, // Configurable ModernDeploymentHarmlessEventIds (noise suppression)
ConfigVersion = 25, // IntegrityBypassAnalyzer flag
UploadIntervalSeconds = 10,
SelfDestructOnComplete = tenantConfig.SelfDestructOnComplete ?? true,
KeepLogFile = tenantConfig.KeepLogFile ?? false,
@@ -165,7 +165,8 @@ namespace AutopilotMonitor.Functions.Functions.Config
{
EnableLocalAdminAnalyzer = tenantConfig.EnableLocalAdminAnalyzer ?? true,
LocalAdminAllowedAccounts = tenantConfig.GetLocalAdminAllowedAccounts(),
EnableSoftwareInventoryAnalyzer = tenantConfig.EnableSoftwareInventoryAnalyzer ?? false
EnableSoftwareInventoryAnalyzer = tenantConfig.EnableSoftwareInventoryAnalyzer ?? false,
EnableIntegrityBypassAnalyzer = tenantConfig.EnableIntegrityBypassAnalyzer ?? true
},
LatestAgentSha256 = adminConfig.LatestAgentSha256,
LatestAgentExeSha256 = adminConfig.LatestAgentExeSha256,
@@ -397,5 +397,14 @@ namespace AutopilotMonitor.Shared.Models
/// Default: true
/// </summary>
public bool EnableSoftwareInventoryAnalyzer { get; set; } = false;
/// <summary>
/// Whether to run the IntegrityBypassAnalyzer at startup and shutdown.
/// Detects Windows 11 TPM/SecureBoot/CPU/RAM/Disk install-time bypass keys,
/// the MoSetup upgrade bypass flag, the per-user PCHC UpgradeEligibility flag,
/// and suspicious post-OOBE SetupComplete/ErrorHandler scripts.
/// Default: true
/// </summary>
public bool EnableIntegrityBypassAnalyzer { get; set; } = true;
}
}
@@ -308,6 +308,12 @@ namespace AutopilotMonitor.Shared.Models
/// </summary>
public bool? EnableSoftwareInventoryAnalyzer { get; set; } = null;
/// <summary>
/// Whether the IntegrityBypassAnalyzer is enabled for this tenant's devices.
/// null = use agent default (true).
/// </summary>
public bool? EnableIntegrityBypassAnalyzer { get; set; } = null;
/// <summary>
/// JSON-serialized list of additional local account names that are considered expected
/// on a newly enrolled device (merged with built-in defaults on the agent).
@@ -0,0 +1,268 @@
"use client";
import { useMemo, useState } from "react";
import type { EnrollmentEvent } from "@/types";
interface IntegrityBypassSectionProps {
events: EnrollmentEvent[];
expanded: boolean;
setExpanded: (v: boolean) => void;
}
interface LabConfigPayload {
key_exists?: boolean;
values?: Record<string, number>;
active_bypasses?: string[];
}
interface MoSetupPayload {
key_exists?: boolean;
value?: number;
flagged?: boolean;
}
interface PchcPayload {
users_checked?: string[];
users_with_flag?: string[];
any_user_with_flag?: boolean;
}
interface SetupScriptEntry {
path?: string;
exists?: boolean;
size_bytes?: number;
last_modified_utc?: string | null;
}
interface CorrelationPayload {
tpm_present_and_enabled?: boolean | null;
secure_boot_enabled?: boolean | null;
}
interface ChecksPayload {
lab_config?: LabConfigPayload;
mo_setup?: MoSetupPayload;
pchc_upgrade_eligibility?: PchcPayload;
setup_scripts?: SetupScriptEntry[];
correlation?: CorrelationPayload;
}
interface AnalysisPayload {
severity?: string;
finding?: string;
findings?: string[];
triggered_at?: string;
checks?: ChecksPayload;
}
const severityOrder: Record<string, number> = {
critical: 4,
error: 3,
warning: 2,
info: 1,
debug: 0,
trace: -1,
};
const severityBadgeColors: Record<string, string> = {
critical: "bg-red-100 text-red-700 border-red-200",
error: "bg-red-100 text-red-700 border-red-200",
warning: "bg-amber-100 text-amber-700 border-amber-200",
info: "bg-blue-100 text-blue-700 border-blue-200",
clean: "bg-emerald-100 text-emerald-700 border-emerald-200",
};
function pickDominant(events: EnrollmentEvent[]): { shutdown: EnrollmentEvent | null; startup: EnrollmentEvent | null; any: EnrollmentEvent | null } {
const analyses = events
.filter((e) => e.eventType === "integrity_bypass_analysis")
.sort((a, b) => a.sequence - b.sequence);
const shutdown = [...analyses].reverse().find((e) => (e.data as AnalysisPayload | undefined)?.triggered_at === "shutdown") ?? null;
const startup = analyses.find((e) => (e.data as AnalysisPayload | undefined)?.triggered_at === "startup") ?? null;
const any = shutdown ?? startup ?? (analyses.length > 0 ? analyses[analyses.length - 1] : null);
return { shutdown, startup, any };
}
function severityLabel(sev?: string, findings?: string[]): string {
if (!sev) return "unknown";
const lower = sev.toLowerCase();
if (lower === "info" && findings && findings.includes("no_bypass_indicators")) return "clean";
return lower;
}
function describeTpm(v: boolean | null | undefined): string {
if (v === true) return "TPM enabled";
if (v === false) return "TPM missing / disabled";
return "TPM status unknown";
}
function describeSecureBoot(v: boolean | null | undefined): string {
if (v === true) return "SecureBoot enabled";
if (v === false) return "SecureBoot disabled";
return "SecureBoot status unknown";
}
export default function IntegrityBypassSection({ events, expanded, setExpanded }: IntegrityBypassSectionProps) {
const { shutdown, startup, any } = useMemo(() => pickDominant(events), [events]);
const primary = shutdown ?? any;
if (!primary) return null;
const payload = (primary.data ?? {}) as AnalysisPayload;
const severity = severityLabel(payload.severity, payload.findings);
const badgeClass = severityBadgeColors[severity] ?? "bg-gray-100 text-gray-700 border-gray-200";
const activeBypasses = payload.checks?.lab_config?.active_bypasses ?? [];
const labValues = payload.checks?.lab_config?.values ?? {};
const moSetupFlagged = payload.checks?.mo_setup?.flagged === true;
const pchcFlagged = payload.checks?.pchc_upgrade_eligibility?.any_user_with_flag === true;
const pchcUsers = payload.checks?.pchc_upgrade_eligibility?.users_with_flag ?? [];
const setupScripts = (payload.checks?.setup_scripts ?? []).filter((s) => s.exists === true);
const corr = payload.checks?.correlation ?? {};
// Delta summary (startup vs shutdown) — focus on the facts that change in practice
const startupPayload = (startup?.data ?? {}) as AnalysisPayload | undefined;
const startupActive = startupPayload?.checks?.lab_config?.active_bypasses ?? [];
const newlyAppeared = activeBypasses.filter((b) => !startupActive.includes(b));
const newScripts = setupScripts.filter((s) => {
const startScripts = startupPayload?.checks?.setup_scripts ?? [];
return !startScripts.some((ss) => ss.path === s.path && ss.exists === true);
});
return (
<div className="bg-white shadow rounded-lg p-6 mb-6">
<div
onClick={() => setExpanded(!expanded)}
className="flex items-center justify-between w-full text-left cursor-pointer"
>
<div className="flex items-center space-x-2">
<svg className="w-6 h-6 text-rose-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" />
</svg>
<h2 className="text-xl font-semibold text-gray-900">Integrity Bypass Analyzer</h2>
<span className={`inline-flex items-center px-2 py-0.5 rounded-full text-xs font-semibold border ${badgeClass}`}>
{severity.toUpperCase()}
</span>
{payload.finding && (
<span className="text-xs text-gray-500 font-mono">{payload.finding}</span>
)}
</div>
<svg className={`w-5 h-5 text-gray-400 transition-transform duration-200 ${expanded ? 'rotate-90' : ''}`} fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
</svg>
</div>
{expanded && (
<div className="mt-4 space-y-4 text-sm text-gray-700">
{/* Correlation row — always visible to give context */}
<div className="flex flex-wrap gap-2">
<span className="inline-flex items-center px-2 py-1 rounded bg-gray-50 border border-gray-200 text-xs">
{describeTpm(corr.tpm_present_and_enabled)}
</span>
<span className="inline-flex items-center px-2 py-1 rounded bg-gray-50 border border-gray-200 text-xs">
{describeSecureBoot(corr.secure_boot_enabled)}
</span>
{startup && shutdown && (
<span className="inline-flex items-center px-2 py-1 rounded bg-gray-50 border border-gray-200 text-xs text-gray-500">
Startup + Shutdown checks available
</span>
)}
</div>
{/* LabConfig bypass keys */}
<div>
<h3 className="text-sm font-semibold text-gray-800 mb-1">LabConfig — Install-time bypass keys</h3>
{!payload.checks?.lab_config?.key_exists ? (
<p className="text-xs text-gray-500">HKLM\SYSTEM\Setup\LabConfig not present — normal on standard Windows installs.</p>
) : activeBypasses.length === 0 ? (
<p className="text-xs text-gray-500">Key exists but no bypass values set to 1 (possible manipulated install medium, currently dormant).</p>
) : (
<ul className="space-y-1">
{Object.entries(labValues).map(([name, value]) => {
const active = value === 1;
const newlyActive = active && newlyAppeared.includes(name);
return (
<li key={name} className="flex items-center gap-2">
<span className={`inline-block w-2 h-2 rounded-full ${active ? 'bg-rose-500' : 'bg-gray-300'}`} />
<span className={`font-mono text-xs ${active ? 'text-rose-700' : 'text-gray-500'}`}>{name} = {value}</span>
{newlyActive && (
<span className="text-xs px-1.5 py-0.5 rounded bg-amber-100 text-amber-700 border border-amber-200">appeared during enrollment</span>
)}
</li>
);
})}
</ul>
)}
</div>
{/* MoSetup upgrade bypass */}
<div>
<h3 className="text-sm font-semibold text-gray-800 mb-1">MoSetup — In-place upgrade bypass</h3>
<p className="text-xs">
<span className="font-mono">AllowUpgradesWithUnsupportedTPMOrCPU</span>{" "}
{moSetupFlagged ? (
<span className="text-rose-700 font-semibold">= 1 (bypass active)</span>
) : (
<span className="text-gray-500">not set / = 0</span>
)}
</p>
</div>
{/* PCHC UpgradeEligibility */}
<div>
<h3 className="text-sm font-semibold text-gray-800 mb-1">PCHC — Windows Update eligibility bypass</h3>
{pchcFlagged ? (
<div>
<p className="text-xs text-rose-700">
<span className="font-mono">HKU\&lt;SID&gt;\SOFTWARE\Microsoft\PCHC\UpgradeEligibility = 1</span> found for {pchcUsers.length} user{pchcUsers.length === 1 ? '' : 's'}:
</p>
<ul className="ml-3 mt-1 space-y-0.5">
{pchcUsers.map((sid) => (
<li key={sid} className="font-mono text-xs text-gray-600">{sid}</li>
))}
</ul>
</div>
) : (
<p className="text-xs text-gray-500">No user hive has UpgradeEligibility = 1.</p>
)}
</div>
{/* Setup scripts */}
<div>
<h3 className="text-sm font-semibold text-gray-800 mb-1">Setup Scripts — %WINDIR%\Setup\Scripts\</h3>
{setupScripts.length === 0 ? (
<p className="text-xs text-gray-500">No SetupComplete.cmd / ErrorHandler.cmd present.</p>
) : (
<ul className="space-y-1">
{setupScripts.map((s) => {
const isNew = newScripts.some((ns) => ns.path === s.path);
return (
<li key={s.path ?? ''} className="flex items-center gap-2">
<span className="inline-block w-2 h-2 rounded-full bg-amber-500" />
<span className="font-mono text-xs text-amber-800">{s.path}</span>
<span className="text-xs text-gray-500">({s.size_bytes ?? 0} bytes{s.last_modified_utc ? `, modified ${new Date(s.last_modified_utc).toISOString().replace('T', ' ').substring(0, 19)} UTC` : ""})</span>
{isNew && (
<span className="text-xs px-1.5 py-0.5 rounded bg-amber-100 text-amber-700 border border-amber-200">appeared during enrollment</span>
)}
</li>
);
})}
</ul>
)}
</div>
{/* Findings list */}
{payload.findings && payload.findings.length > 0 && payload.findings[0] !== "no_bypass_indicators" && (
<div>
<h3 className="text-sm font-semibold text-gray-800 mb-1">Findings</h3>
<ul className="list-disc list-inside text-xs text-gray-600 ml-2 space-y-0.5">
{payload.findings.map((f) => (
<li key={f} className="font-mono">{f}</li>
))}
</ul>
</div>
)}
</div>
)}
</div>
);
}
@@ -7,6 +7,7 @@ import { authenticatedFetch } from "@/lib/authenticatedFetch";
interface UseSessionTenantConfigReturn {
showScriptOutput: boolean;
enableSoftwareInventoryAnalyzer: boolean;
enableIntegrityBypassAnalyzer: boolean;
}
/**
@@ -20,6 +21,7 @@ export function useSessionTenantConfig(
): UseSessionTenantConfigReturn {
const [showScriptOutput, setShowScriptOutput] = useState(true);
const [enableSoftwareInventoryAnalyzer, setEnableSoftwareInventoryAnalyzer] = useState(false);
const [enableIntegrityBypassAnalyzer, setEnableIntegrityBypassAnalyzer] = useState(true);
useEffect(() => {
if (!sessionTenantId) return;
@@ -32,10 +34,11 @@ export function useSessionTenantConfig(
if (cancelled) return;
setShowScriptOutput(cfg.showScriptOutput ?? true);
setEnableSoftwareInventoryAnalyzer(cfg.enableSoftwareInventoryAnalyzer ?? false);
setEnableIntegrityBypassAnalyzer(cfg.enableIntegrityBypassAnalyzer ?? true);
} catch { /* non-fatal */ }
})();
return () => { cancelled = true; };
}, [sessionTenantId, getAccessToken]);
return { showScriptOutput, enableSoftwareInventoryAnalyzer };
return { showScriptOutput, enableSoftwareInventoryAnalyzer, enableIntegrityBypassAnalyzer };
}
@@ -28,6 +28,7 @@ import PhaseTimeline from "./components/PhaseTimeline";
import EventTimeline from "./components/EventTimeline";
import AnalysisResultsSection from "./components/AnalysisResultsSection";
import VulnerabilityReportSection from "./components/VulnerabilityReportSection";
import IntegrityBypassSection from "./components/IntegrityBypassSection";
import AdminOverrideModal from "./components/AdminOverrideModal";
import ReportSessionModal from "./components/ReportSessionModal";
import { usePageSections } from "../../../hooks/usePageSections";
@@ -51,6 +52,7 @@ export default function SessionDetailPage() {
const [reportSubmitting, setReportSubmitting] = useState(false);
const [analysisExpanded, setAnalysisExpanded] = useState(true);
const [vulnerabilityReportExpanded, setVulnerabilityReportExpanded] = useState(false);
const [integrityBypassExpanded, setIntegrityBypassExpanded] = useState(false);
const [phaseTimelineExpanded, setPhaseTimelineExpanded] = useState(true);
const [perfExpanded, setPerfExpanded] = useState(true);
const [timelineExpanded, setTimelineExpanded] = useState(true);
@@ -103,7 +105,7 @@ export default function SessionDetailPage() {
const { session, setSession, sessionTenantId, loading } = detail;
const events = eventsApi.events;
const { analysisResults, loadingAnalysis, vulnerabilityReport, fetchAnalysisResults, fetchVulnerabilityReport } = analysis;
const { showScriptOutput, enableSoftwareInventoryAnalyzer } = tenantConfig;
const { showScriptOutput, enableSoftwareInventoryAnalyzer, enableIntegrityBypassAnalyzer } = tenantConfig;
const {
filteredEvents,
appSummaryStats,
@@ -512,6 +514,17 @@ export default function SessionDetailPage() {
</div>
)}
{/* Integrity Bypass — only when analyzer is enabled for this tenant and the session has produced at least one event */}
{!isGatherRulesSession && enableIntegrityBypassAnalyzer && (
<div id="section-integrity-bypass">
<IntegrityBypassSection
events={events}
expanded={integrityBypassExpanded}
setExpanded={setIntegrityBypassExpanded}
/>
</div>
)}
{/* Performance Metrics (from performance_snapshot events) */}
{!isGatherRulesSession && (
<div id="section-performance">
@@ -92,6 +92,8 @@ interface TenantConfigContextValue {
setNewAllowedAccount: (v: string) => void;
enableSoftwareInventoryAnalyzer: boolean;
setEnableSoftwareInventoryAnalyzer: (v: boolean) => void;
enableIntegrityBypassAnalyzer: boolean;
setEnableIntegrityBypassAnalyzer: (v: boolean) => void;
handleSaveAgentAnalyzers: () => void;
handleResetAgentAnalyzers: () => void;
@@ -307,6 +309,7 @@ export function TenantConfigProvider({ children }: { children: React.ReactNode }
const [localAdminAllowedAccounts, setLocalAdminAllowedAccounts] = useState<string[]>([]);
const [newAllowedAccount, setNewAllowedAccount] = useState("");
const [enableSoftwareInventoryAnalyzer, setEnableSoftwareInventoryAnalyzer] = useState(false);
const [enableIntegrityBypassAnalyzer, setEnableIntegrityBypassAnalyzer] = useState(true);
// Unrestricted mode
const [unrestrictedMode, setUnrestrictedMode] = useState(false);
@@ -413,6 +416,7 @@ export function TenantConfigProvider({ children }: { children: React.ReactNode }
setLocalAdminAllowedAccounts([]);
}
setEnableSoftwareInventoryAnalyzer(data.enableSoftwareInventoryAnalyzer ?? false);
setEnableIntegrityBypassAnalyzer(data.enableIntegrityBypassAnalyzer ?? true);
setUnrestrictedMode(data.unrestrictedMode ?? false);
// Parse SAS expiry and fire notification to bell if needed
@@ -599,6 +603,7 @@ export function TenantConfigProvider({ children }: { children: React.ReactNode }
enableLocalAdminAnalyzer,
localAdminAllowedAccountsJson: localAdminAllowedAccounts.length > 0 ? JSON.stringify(localAdminAllowedAccounts) : "",
enableSoftwareInventoryAnalyzer,
enableIntegrityBypassAnalyzer,
unrestrictedMode,
};
@@ -646,7 +651,8 @@ export function TenantConfigProvider({ children }: { children: React.ReactNode }
slaNotifyOnSuccessRateBreach, slaSuccessRateNotifyThreshold, slaNotifyOnDurationBreach,
slaNotifyOnAppInstallBreach, slaNotifyOnConsecutiveFailures, slaConsecutiveFailureThreshold,
diagnosticsBlobSasUrl, diagnosticsUploadMode, tenantDiagPaths,
enableLocalAdminAnalyzer, localAdminAllowedAccounts, enableSoftwareInventoryAnalyzer, unrestrictedMode,
enableLocalAdminAnalyzer, localAdminAllowedAccounts, enableSoftwareInventoryAnalyzer,
enableIntegrityBypassAnalyzer, unrestrictedMode,
]);
// -----------------------------------------------------------------------
@@ -837,6 +843,7 @@ export function TenantConfigProvider({ children }: { children: React.ReactNode }
} catch { setLocalAdminAllowedAccounts([]); }
setNewAllowedAccount("");
setEnableSoftwareInventoryAnalyzer(config.enableSoftwareInventoryAnalyzer ?? false);
setEnableIntegrityBypassAnalyzer(config.enableIntegrityBypassAnalyzer ?? true);
}, [config]);
const handleSaveNotifications = useCallback(() => saveConfiguration("notifications"), [saveConfiguration]);
@@ -1191,6 +1198,7 @@ export function TenantConfigProvider({ children }: { children: React.ReactNode }
localAdminAllowedAccounts, setLocalAdminAllowedAccounts,
newAllowedAccount, setNewAllowedAccount,
enableSoftwareInventoryAnalyzer, setEnableSoftwareInventoryAnalyzer,
enableIntegrityBypassAnalyzer, setEnableIntegrityBypassAnalyzer,
handleSaveAgentAnalyzers, handleResetAgentAnalyzers,
// Unrestricted mode
@@ -10,6 +10,7 @@ export function SectionAgentAnalyzers() {
localAdminAllowedAccounts, setLocalAdminAllowedAccounts,
newAllowedAccount, setNewAllowedAccount,
enableSoftwareInventoryAnalyzer, setEnableSoftwareInventoryAnalyzer,
enableIntegrityBypassAnalyzer, setEnableIntegrityBypassAnalyzer,
handleSaveAgentAnalyzers, handleResetAgentAnalyzers,
savingSection,
} = useTenantConfig();
@@ -26,6 +27,8 @@ export function SectionAgentAnalyzers() {
setNewAllowedAccount={setNewAllowedAccount}
enableSoftwareInventoryAnalyzer={enableSoftwareInventoryAnalyzer}
setEnableSoftwareInventoryAnalyzer={setEnableSoftwareInventoryAnalyzer}
enableIntegrityBypassAnalyzer={enableIntegrityBypassAnalyzer}
setEnableIntegrityBypassAnalyzer={setEnableIntegrityBypassAnalyzer}
onSave={handleSaveAgentAnalyzers}
onReset={handleResetAgentAnalyzers}
saving={savingSection === "agentAnalyzers"}
@@ -11,6 +11,8 @@ interface AgentAnalyzersSectionProps {
setNewAllowedAccount: (value: string) => void;
enableSoftwareInventoryAnalyzer: boolean;
setEnableSoftwareInventoryAnalyzer: (value: boolean) => void;
enableIntegrityBypassAnalyzer: boolean;
setEnableIntegrityBypassAnalyzer: (value: boolean) => void;
onSave: () => Promise<void> | void;
onReset: () => void;
saving: boolean;
@@ -39,6 +41,8 @@ export default function AgentAnalyzersSection({
setNewAllowedAccount,
enableSoftwareInventoryAnalyzer,
setEnableSoftwareInventoryAnalyzer,
enableIntegrityBypassAnalyzer,
setEnableIntegrityBypassAnalyzer,
onSave,
onReset,
saving,
@@ -193,6 +197,34 @@ export default function AgentAnalyzersSection({
</div>
</div>
{/* ── Divider ─────────────────────────────────────── */}
<div className="border-t border-gray-200" />
{/* ── Integrity Bypass Analyzer ───────────────────── */}
<div>
<h3 className="text-sm font-semibold text-gray-700 uppercase tracking-wider mb-3">Integrity Bypass Analyzer</h3>
<p className="text-sm text-gray-500 mb-4">
Detects Windows 11 &quot;unsupported hardware&quot; installations where PC Health Check / Setup-time gates were bypassed
(LabConfig TPM / SecureBoot / CPU / RAM / Disk bypass keys, MoSetup upgrade flag, PCHC UpgradeEligibility).
Also flags suspicious post-OOBE <code className="px-1 bg-gray-100 rounded">SetupComplete.cmd</code> / <code className="px-1 bg-gray-100 rounded">ErrorHandler.cmd</code> scripts.
Results are correlated against current TPM and SecureBoot state.
</p>
{/* Enable toggle */}
<div className="flex items-center justify-between p-4 rounded-lg border border-gray-200 hover:border-rose-200 transition-colors">
<div>
<p className="font-medium text-gray-900">Enable Integrity Bypass Analyzer</p>
<p className="text-sm text-gray-500">Run at enrollment start and completion to surface devices with bypassed Win11 hardware gates</p>
</div>
<button
onClick={() => setEnableIntegrityBypassAnalyzer(!enableIntegrityBypassAnalyzer)}
className={`relative inline-flex h-6 w-11 flex-shrink-0 items-center rounded-full transition-colors ${enableIntegrityBypassAnalyzer ? 'bg-rose-500' : 'bg-gray-300'}`}
>
<span className={`inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${enableIntegrityBypassAnalyzer ? 'translate-x-6' : 'translate-x-1'}`} />
</button>
</div>
</div>
{/* Save / Reset */}
<SaveResetBar onSave={onSave} onReset={onReset} saving={saving} />
</div>
@@ -67,6 +67,7 @@ export interface TenantConfiguration {
enableLocalAdminAnalyzer?: boolean;
localAdminAllowedAccountsJson?: string;
enableSoftwareInventoryAnalyzer?: boolean;
enableIntegrityBypassAnalyzer?: boolean;
// Bootstrap token
bootstrapTokenEnabled?: boolean;
// Unrestricted mode