From 2eb6d87b4d361b105f82ac21aa4af3ce1ca23180 Mon Sep 17 00:00:00 2001 From: Oliver Kieselbach Date: Fri, 17 Apr 2026 00:20:24 +0200 Subject: [PATCH] feat(agent): added IntegrityBypassAnalyzer --- .../IntegrityBypassAnalyzerTests.cs | 116 +++++ .../Monitoring/Runtime/AnalyzerManager.cs | 15 + .../Analyzers/IntegrityBypassAnalyzer.cs | 492 ++++++++++++++++++ .../AutopilotMonitor.Agent/buildcounter.txt | 2 +- .../TableStorage/TableConfigRepository.cs | 2 + .../Config/GetAgentConfigFunction.cs | 5 +- .../Models/Config/AgentConfigResponse.cs | 9 + .../Models/Config/TenantConfiguration.cs | 6 + .../components/IntegrityBypassSection.tsx | 268 ++++++++++ .../hooks/useSessionTenantConfig.ts | 5 +- .../app/sessions/[sessionId]/page.tsx | 15 +- .../app/settings/TenantConfigContext.tsx | 10 +- .../agent/sections/SectionAgentAnalyzers.tsx | 3 + .../components/AgentAnalyzersSection.tsx | 32 ++ .../app/settings/types.ts | 1 + 15 files changed, 975 insertions(+), 6 deletions(-) create mode 100644 src/Agent/AutopilotMonitor.Agent.Core.Tests/Monitoring/IntegrityBypassAnalyzerTests.cs create mode 100644 src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Telemetry/Analyzers/IntegrityBypassAnalyzer.cs create mode 100644 src/Web/autopilot-monitor-web/app/sessions/[sessionId]/components/IntegrityBypassSection.tsx diff --git a/src/Agent/AutopilotMonitor.Agent.Core.Tests/Monitoring/IntegrityBypassAnalyzerTests.cs b/src/Agent/AutopilotMonitor.Agent.Core.Tests/Monitoring/IntegrityBypassAnalyzerTests.cs new file mode 100644 index 00000000..b0dd78a7 --- /dev/null +++ b/src/Agent/AutopilotMonitor.Agent.Core.Tests/Monitoring/IntegrityBypassAnalyzerTests.cs @@ -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 +{ + /// + /// 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. + /// + public class IntegrityBypassAnalyzerTests + { + private IntegrityBypassAnalyzer CreateAnalyzer(List 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(); + 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(); + 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(); + var analyzer = CreateAnalyzer(events); + + analyzer.AnalyzeAtStartup(); + + var evt = Assert.Single(events); + var checks = Assert.IsAssignableFrom>(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(checks["setup_scripts"]); + var count = scripts.Cast().Count(); + Assert.Equal(2, count); + } + + [Fact] + public void Severity_IsLowerCaseString() + { + var events = new List(); + var analyzer = CreateAnalyzer(events); + + analyzer.AnalyzeAtStartup(); + + var evt = Assert.Single(events); + var severity = Assert.IsType(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(() => + new IntegrityBypassAnalyzer(null, "t", evt => { }, TestLogger.Instance)); + Assert.Throws(() => + new IntegrityBypassAnalyzer("s", null, evt => { }, TestLogger.Instance)); + Assert.Throws(() => + new IntegrityBypassAnalyzer("s", "t", null, TestLogger.Instance)); + Assert.Throws(() => + new IntegrityBypassAnalyzer("s", "t", evt => { }, null)); + } + } +} diff --git a/src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Runtime/AnalyzerManager.cs b/src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Runtime/AnalyzerManager.cs index 1dd216a5..1ea76c23 100644 --- a/src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Runtime/AnalyzerManager.cs +++ b/src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Runtime/AnalyzerManager.cs @@ -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"); } diff --git a/src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Telemetry/Analyzers/IntegrityBypassAnalyzer.cs b/src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Telemetry/Analyzers/IntegrityBypassAnalyzer.cs new file mode 100644 index 00000000..0669c770 --- /dev/null +++ b/src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Telemetry/Analyzers/IntegrityBypassAnalyzer.cs @@ -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 +{ + /// + /// 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\<SID>\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). + /// + public class IntegrityBypassAnalyzer : IAgentAnalyzer + { + private readonly string _sessionId; + private readonly string _tenantId; + private readonly Action _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 SystemSids = new HashSet(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 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(); + + 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 + { + { "severity", severity.ToString().ToLowerInvariant() }, + { "finding", findingLabel }, + { "findings", findings }, + { "triggered_at", trigger }, + { "enrollment_phase_at_check", EnrollmentPhase.Unknown.ToString() }, + { "checks", new Dictionary + { + { "lab_config", new Dictionary + { + { "key_exists", labConfig.KeyExists }, + { "values", labConfig.Values }, + { "active_bypasses", labConfig.ActiveBypasses } + } + }, + { "mo_setup", new Dictionary + { + { "key_exists", moSetup.KeyExists }, + { "value", moSetup.Value }, + { "flagged", moSetupBypass } + } + }, + { "pchc_upgrade_eligibility", new Dictionary + { + { "users_checked", pchc.UsersChecked }, + { "users_with_flag", pchc.UsersWithFlag }, + { "any_user_with_flag", pchc.AnyUserWithFlag } + } + }, + { "setup_scripts", setupScripts.Select(s => new Dictionary + { + { "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 + { + { "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 }; + } + } + + /// + /// 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. + /// + 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 CheckSetupScripts() + { + var results = new List(); + 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; + } + + /// + /// 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. + /// + 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; + } + } + + /// + /// Returns true if UEFI Secure Boot is enabled, false if disabled, null if not + /// queryable (e.g. BIOS firmware without the registry key). + /// + 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 Values { get; set; } = new Dictionary(); + public List ActiveBypasses { get; set; } = new List(); + } + + private class MoSetupCheckResult + { + public bool KeyExists { get; set; } + public int Value { get; set; } + } + + private class PchcCheckResult + { + public List UsersChecked { get; set; } = new List(); + public List UsersWithFlag { get; set; } = new List(); + 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; } + } + } +} diff --git a/src/Agent/AutopilotMonitor.Agent/buildcounter.txt b/src/Agent/AutopilotMonitor.Agent/buildcounter.txt index d90f2b1d..1f3d8a7a 100644 --- a/src/Agent/AutopilotMonitor.Agent/buildcounter.txt +++ b/src/Agent/AutopilotMonitor.Agent/buildcounter.txt @@ -1 +1 @@ -1026 +1028 diff --git a/src/Backend/AutopilotMonitor.Functions/DataAccess/TableStorage/TableConfigRepository.cs b/src/Backend/AutopilotMonitor.Functions/DataAccess/TableStorage/TableConfigRepository.cs index c53338fe..f70f3d03 100644 --- a/src/Backend/AutopilotMonitor.Functions/DataAccess/TableStorage/TableConfigRepository.cs +++ b/src/Backend/AutopilotMonitor.Functions/DataAccess/TableStorage/TableConfigRepository.cs @@ -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, diff --git a/src/Backend/AutopilotMonitor.Functions/Functions/Config/GetAgentConfigFunction.cs b/src/Backend/AutopilotMonitor.Functions/Functions/Config/GetAgentConfigFunction.cs index 7c64b665..36540dd5 100644 --- a/src/Backend/AutopilotMonitor.Functions/Functions/Config/GetAgentConfigFunction.cs +++ b/src/Backend/AutopilotMonitor.Functions/Functions/Config/GetAgentConfigFunction.cs @@ -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, diff --git a/src/Shared/AutopilotMonitor.Shared/Models/Config/AgentConfigResponse.cs b/src/Shared/AutopilotMonitor.Shared/Models/Config/AgentConfigResponse.cs index 48dce251..fc112d93 100644 --- a/src/Shared/AutopilotMonitor.Shared/Models/Config/AgentConfigResponse.cs +++ b/src/Shared/AutopilotMonitor.Shared/Models/Config/AgentConfigResponse.cs @@ -397,5 +397,14 @@ namespace AutopilotMonitor.Shared.Models /// Default: true /// public bool EnableSoftwareInventoryAnalyzer { get; set; } = false; + + /// + /// 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 + /// + public bool EnableIntegrityBypassAnalyzer { get; set; } = true; } } diff --git a/src/Shared/AutopilotMonitor.Shared/Models/Config/TenantConfiguration.cs b/src/Shared/AutopilotMonitor.Shared/Models/Config/TenantConfiguration.cs index 61bd72fa..8eaf453b 100644 --- a/src/Shared/AutopilotMonitor.Shared/Models/Config/TenantConfiguration.cs +++ b/src/Shared/AutopilotMonitor.Shared/Models/Config/TenantConfiguration.cs @@ -308,6 +308,12 @@ namespace AutopilotMonitor.Shared.Models /// public bool? EnableSoftwareInventoryAnalyzer { get; set; } = null; + /// + /// Whether the IntegrityBypassAnalyzer is enabled for this tenant's devices. + /// null = use agent default (true). + /// + public bool? EnableIntegrityBypassAnalyzer { get; set; } = null; + /// /// 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). diff --git a/src/Web/autopilot-monitor-web/app/sessions/[sessionId]/components/IntegrityBypassSection.tsx b/src/Web/autopilot-monitor-web/app/sessions/[sessionId]/components/IntegrityBypassSection.tsx new file mode 100644 index 00000000..2ab7a37f --- /dev/null +++ b/src/Web/autopilot-monitor-web/app/sessions/[sessionId]/components/IntegrityBypassSection.tsx @@ -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; + 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 = { + critical: 4, + error: 3, + warning: 2, + info: 1, + debug: 0, + trace: -1, +}; + +const severityBadgeColors: Record = { + 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 ( +
+
setExpanded(!expanded)} + className="flex items-center justify-between w-full text-left cursor-pointer" + > +
+ + + +

Integrity Bypass Analyzer

+ + {severity.toUpperCase()} + + {payload.finding && ( + {payload.finding} + )} +
+ + + +
+ + {expanded && ( +
+ {/* Correlation row — always visible to give context */} +
+ + {describeTpm(corr.tpm_present_and_enabled)} + + + {describeSecureBoot(corr.secure_boot_enabled)} + + {startup && shutdown && ( + + Startup + Shutdown checks available + + )} +
+ + {/* LabConfig bypass keys */} +
+

LabConfig — Install-time bypass keys

+ {!payload.checks?.lab_config?.key_exists ? ( +

HKLM\SYSTEM\Setup\LabConfig not present — normal on standard Windows installs.

+ ) : activeBypasses.length === 0 ? ( +

Key exists but no bypass values set to 1 (possible manipulated install medium, currently dormant).

+ ) : ( +
    + {Object.entries(labValues).map(([name, value]) => { + const active = value === 1; + const newlyActive = active && newlyAppeared.includes(name); + return ( +
  • + + {name} = {value} + {newlyActive && ( + appeared during enrollment + )} +
  • + ); + })} +
+ )} +
+ + {/* MoSetup upgrade bypass */} +
+

MoSetup — In-place upgrade bypass

+

+ AllowUpgradesWithUnsupportedTPMOrCPU{" "} + {moSetupFlagged ? ( + = 1 (bypass active) + ) : ( + not set / = 0 + )} +

+
+ + {/* PCHC UpgradeEligibility */} +
+

PCHC — Windows Update eligibility bypass

+ {pchcFlagged ? ( +
+

+ HKU\<SID>\SOFTWARE\Microsoft\PCHC\UpgradeEligibility = 1 found for {pchcUsers.length} user{pchcUsers.length === 1 ? '' : 's'}: +

+
    + {pchcUsers.map((sid) => ( +
  • {sid}
  • + ))} +
+
+ ) : ( +

No user hive has UpgradeEligibility = 1.

+ )} +
+ + {/* Setup scripts */} +
+

Setup Scripts — %WINDIR%\Setup\Scripts\

+ {setupScripts.length === 0 ? ( +

No SetupComplete.cmd / ErrorHandler.cmd present.

+ ) : ( +
    + {setupScripts.map((s) => { + const isNew = newScripts.some((ns) => ns.path === s.path); + return ( +
  • + + {s.path} + ({s.size_bytes ?? 0} bytes{s.last_modified_utc ? `, modified ${new Date(s.last_modified_utc).toISOString().replace('T', ' ').substring(0, 19)} UTC` : ""}) + {isNew && ( + appeared during enrollment + )} +
  • + ); + })} +
+ )} +
+ + {/* Findings list */} + {payload.findings && payload.findings.length > 0 && payload.findings[0] !== "no_bypass_indicators" && ( +
+

Findings

+
    + {payload.findings.map((f) => ( +
  • {f}
  • + ))} +
+
+ )} +
+ )} +
+ ); +} diff --git a/src/Web/autopilot-monitor-web/app/sessions/[sessionId]/hooks/useSessionTenantConfig.ts b/src/Web/autopilot-monitor-web/app/sessions/[sessionId]/hooks/useSessionTenantConfig.ts index e9719155..c228b94b 100644 --- a/src/Web/autopilot-monitor-web/app/sessions/[sessionId]/hooks/useSessionTenantConfig.ts +++ b/src/Web/autopilot-monitor-web/app/sessions/[sessionId]/hooks/useSessionTenantConfig.ts @@ -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 }; } diff --git a/src/Web/autopilot-monitor-web/app/sessions/[sessionId]/page.tsx b/src/Web/autopilot-monitor-web/app/sessions/[sessionId]/page.tsx index 29e9e837..923a1602 100644 --- a/src/Web/autopilot-monitor-web/app/sessions/[sessionId]/page.tsx +++ b/src/Web/autopilot-monitor-web/app/sessions/[sessionId]/page.tsx @@ -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() { )} + {/* Integrity Bypass — only when analyzer is enabled for this tenant and the session has produced at least one event */} + {!isGatherRulesSession && enableIntegrityBypassAnalyzer && ( +
+ +
+ )} + {/* Performance Metrics (from performance_snapshot events) */} {!isGatherRulesSession && (
diff --git a/src/Web/autopilot-monitor-web/app/settings/TenantConfigContext.tsx b/src/Web/autopilot-monitor-web/app/settings/TenantConfigContext.tsx index 30f49053..cf630849 100644 --- a/src/Web/autopilot-monitor-web/app/settings/TenantConfigContext.tsx +++ b/src/Web/autopilot-monitor-web/app/settings/TenantConfigContext.tsx @@ -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([]); 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 diff --git a/src/Web/autopilot-monitor-web/app/settings/agent/sections/SectionAgentAnalyzers.tsx b/src/Web/autopilot-monitor-web/app/settings/agent/sections/SectionAgentAnalyzers.tsx index 3222b935..ba771738 100644 --- a/src/Web/autopilot-monitor-web/app/settings/agent/sections/SectionAgentAnalyzers.tsx +++ b/src/Web/autopilot-monitor-web/app/settings/agent/sections/SectionAgentAnalyzers.tsx @@ -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"} diff --git a/src/Web/autopilot-monitor-web/app/settings/components/AgentAnalyzersSection.tsx b/src/Web/autopilot-monitor-web/app/settings/components/AgentAnalyzersSection.tsx index 6fef97cc..3d1d892c 100644 --- a/src/Web/autopilot-monitor-web/app/settings/components/AgentAnalyzersSection.tsx +++ b/src/Web/autopilot-monitor-web/app/settings/components/AgentAnalyzersSection.tsx @@ -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; 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({
+ {/* ── Divider ─────────────────────────────────────── */} +
+ + {/* ── Integrity Bypass Analyzer ───────────────────── */} +
+

Integrity Bypass Analyzer

+

+ Detects Windows 11 "unsupported hardware" 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 SetupComplete.cmd / ErrorHandler.cmd scripts. + Results are correlated against current TPM and SecureBoot state. +

+ + {/* Enable toggle */} +
+
+

Enable Integrity Bypass Analyzer

+

Run at enrollment start and completion to surface devices with bypassed Win11 hardware gates

+
+ +
+
+ {/* Save / Reset */}
diff --git a/src/Web/autopilot-monitor-web/app/settings/types.ts b/src/Web/autopilot-monitor-web/app/settings/types.ts index fd28ddef..83d00904 100644 --- a/src/Web/autopilot-monitor-web/app/settings/types.ts +++ b/src/Web/autopilot-monitor-web/app/settings/types.ts @@ -67,6 +67,7 @@ export interface TenantConfiguration { enableLocalAdminAnalyzer?: boolean; localAdminAllowedAccountsJson?: string; enableSoftwareInventoryAnalyzer?: boolean; + enableIntegrityBypassAnalyzer?: boolean; // Bootstrap token bootstrapTokenEnabled?: boolean; // Unrestricted mode