From 14c0267428c2f84800b4da833d7420235665c671 Mon Sep 17 00:00:00 2001
From: Randomlyclueless <144950366+Randomlyclueless@users.noreply.github.com>
Date: Tue, 23 Jun 2026 12:53:28 +0530
Subject: [PATCH 1/2] fix(#390): Add admin elevation check to fail fast with
clear error message
---
src/Infrastructure/AppRunner.cs | 9 ++++++
src/Infrastructure/Helpers/AdminGuard.cs | 39 ++++++++++++++++++++++++
tests/WinHome.Tests/AdminGuardTests.cs | 32 +++++++++++++++++++
3 files changed, 80 insertions(+)
create mode 100644 src/Infrastructure/Helpers/AdminGuard.cs
create mode 100644 tests/WinHome.Tests/AdminGuardTests.cs
diff --git a/src/Infrastructure/AppRunner.cs b/src/Infrastructure/AppRunner.cs
index 8cdd2415..bef5cdd2 100644
--- a/src/Infrastructure/AppRunner.cs
+++ b/src/Infrastructure/AppRunner.cs
@@ -4,6 +4,13 @@
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
+using WinHome.Infrastructure.Helpers; // <-- add this line
+using WinHome.Interfaces;
+using WinHome.Models;
+using WinHome.Services.Logging;
+using YamlDotNet.Serialization;
+using YamlDotNet.Serialization.NamingConventions;
+
namespace WinHome.Infrastructure;
/// Orchestrates configuration loading, validation, secret resolution, and engine execution.
@@ -36,7 +43,9 @@ public AppRunner(IEngine engine, IConfigValidator validator, ISecretResolver sec
public async Task RunAsync(FileInfo configFile, bool dryRun, string? profile, bool debug, bool diff, bool json, bool force = false, bool continueOnError = false)
{
try
+
{
+ AdminGuard.EnsureAdministrator();
if (!configFile.Exists)
{
_logger.LogError($"[Error] Configuration file not found: {configFile.FullName}");
diff --git a/src/Infrastructure/Helpers/AdminGuard.cs b/src/Infrastructure/Helpers/AdminGuard.cs
new file mode 100644
index 00000000..0bb2a062
--- /dev/null
+++ b/src/Infrastructure/Helpers/AdminGuard.cs
@@ -0,0 +1,39 @@
+using System.Security.Principal;
+using System.Runtime.Versioning;
+
+namespace WinHome.Infrastructure.Helpers;
+
+/// Validates that the current process has administrative privileges.
+[SupportedOSPlatform("windows")]
+public static class AdminGuard
+{
+ internal static Func IsAdministrator = () =>
+ {
+ using var identity = WindowsIdentity.GetCurrent();
+ var principal = new WindowsPrincipal(identity);
+ return principal.IsInRole(WindowsBuiltInRole.Administrator);
+ };
+
+ /// Throws if the current process is not running as Administrator.
+ /// Thrown when not running with admin privileges.
+ public static void EnsureAdministrator()
+ {
+ if (!IsAdministrator())
+ {
+ throw new UnauthorizedAccessException(
+ "Error: WinHome requires Administrative Privileges to manage system configurations. " +
+ "Please re-run this command from an elevated (Administrator) terminal.");
+ }
+ }
+
+ /// Resets the administrator check delegate to its default implementation (used for testing).
+ internal static void ResetAdminCheck()
+ {
+ IsAdministrator = () =>
+ {
+ using var identity = WindowsIdentity.GetCurrent();
+ var principal = new WindowsPrincipal(identity);
+ return principal.IsInRole(WindowsBuiltInRole.Administrator);
+ };
+ }
+}
\ No newline at end of file
diff --git a/tests/WinHome.Tests/AdminGuardTests.cs b/tests/WinHome.Tests/AdminGuardTests.cs
new file mode 100644
index 00000000..16793c3f
--- /dev/null
+++ b/tests/WinHome.Tests/AdminGuardTests.cs
@@ -0,0 +1,32 @@
+using WinHome.Infrastructure.Helpers;
+using Xunit;
+
+namespace WinHome.Tests;
+
+[Collection("SequentialTests")]
+public class AdminGuardTests
+{
+ [Fact]
+ public void EnsureAdministrator_DoesNotThrow_WhenAdmin()
+ {
+ AdminGuard.IsAdministrator = () => true;
+ var ex = Record.Exception(() => AdminGuard.EnsureAdministrator());
+ Assert.Null(ex);
+ }
+
+ [Fact]
+ public void EnsureAdministrator_ThrowsUnauthorizedAccessException_WhenNotAdmin()
+ {
+ AdminGuard.IsAdministrator = () => false;
+ var ex = Assert.Throws(() => AdminGuard.EnsureAdministrator());
+ Assert.Contains("Administrative Privileges", ex.Message);
+ }
+
+ [Fact]
+ public void ResetAdminCheck_RestoresDefaultDelegate()
+ {
+ AdminGuard.IsAdministrator = () => false;
+ AdminGuard.ResetAdminCheck();
+ Assert.NotNull(AdminGuard.IsAdministrator);
+ }
+}
\ No newline at end of file
From 481119cd145a67826ad991a46031cb47419fc32f Mon Sep 17 00:00:00 2001
From: WinHome Test
Date: Wed, 24 Jun 2026 00:36:30 +0530
Subject: [PATCH 2/2] fix: remove duplicate using statements in AppRunner.cs
---
src/Infrastructure/AppRunner.cs | 6 +-----
1 file changed, 1 insertion(+), 5 deletions(-)
diff --git a/src/Infrastructure/AppRunner.cs b/src/Infrastructure/AppRunner.cs
index bef5cdd2..74d5b0ca 100644
--- a/src/Infrastructure/AppRunner.cs
+++ b/src/Infrastructure/AppRunner.cs
@@ -5,11 +5,7 @@
using YamlDotNet.Serialization.NamingConventions;
using WinHome.Infrastructure.Helpers; // <-- add this line
-using WinHome.Interfaces;
-using WinHome.Models;
-using WinHome.Services.Logging;
-using YamlDotNet.Serialization;
-using YamlDotNet.Serialization.NamingConventions;
+
namespace WinHome.Infrastructure;